7. Extending troff using Common Lisp
.eval does only one thing: It allows you to place arbitrary Lisp code until the following .endeval, and the text written to standard output by this Lisp code is substituted for the .eval ... .endeval. The usefulness of this tactic will be apparent from an example. Consider the following troff document, tau.ms:
The ratio of the circumference of a circle to
its radius is \(*t \(~=
;following prints tau, because cos(tau/2) = -1
(princ (* 2 (acos -1)))
groff -z -U -ms tau.ms
(The -z avoids generating output, because we are not ready for it yet. The -U runs in “unsafe” mode, i.e., it allows the writing of aux files.) You will find that the groff call produces a message like the following:
.trofftemp.lisp missing for this run
Rerun groff with -U
Call groff again as follows:
groff -U -ms tau.ms > tau.ps
tau.ps will now look like:
The ratio of the circumference of a circle to its radius is τ ≈ 6.2831855.
The first groff call produces a Lisp file .trofftemp.lisp.3 The second groff call invokes Lisp to create an auxiliary file for each .eval that gets sourced back into the document.
It is clear that Lisp code via .eval can serve as a very powerful second extension language for troff. This benefit is available even when the document is processed by troff2page: We could run troff2page on the same document, tau.ms, and the resulting tau.html will show the same content.
Furthermore, we can embed .eval-enclosed Lisp code inside an .if (or .ie or .el) statement so that it gets executed only for troff or only for troff2page. (Recall we used the number register \n[.troff2page] for this purpose on page 3.) In particular, by calling Lisp code only for troff2page, the user has the means to extend troff2page to deal with things that the core program as supplied does not.
Note that when troff2page sees .eval-enclosed Lisp code, it runs it in a Lisp environment that has not only the basic Common Lisp language but also includes the procedures and data structures defined in troff2page. These extras are not available (and are arguably not useful) to the .eval code seen by troff.
Defining color names using HSL
For a more substantial example of .eval’s use, consider defining color names using the HSL (hue/saturation/lightness) scheme rather than the RGB and CMYK schemes provided by groff. For instance, we would like to define the color Venetian red using its HSL spec (357°, 49%, 24%), which to many is a more intuitive description than RGB (91, 31, 34).
While there is an algorithm to convert HSL to RGB, implementing it using groff’s limited arithmetic is tedious. Instead, we’ll define a much easier Lisp procedure to do the same, and put it inside an .eval:
(defun between-0-and-1 (n)
(cond ((< n 0) (+ n 1))
((> n 1) (- n 1))
(defun tc-to-c (tc p q)
(cond ((< tc 1/6)
(+ p (* (- q p) 6 tc)))
((and (<= 1/6 tc) (< tc 1/2))
((and (<= 1/2 tc) (< tc 2/3))
(+ p (* (- q p) 6 (- 2/3 tc))))
(defun hsl-to-rgb (h s l)
(setq h (/ (mod h 360) 360))
(let* ((q (cond ((< l 1/2) (* l (+ 1 s)))
(t (+ l s (* -1 l s)))))
(p (- (* 2 l) q))
(tr (between-0-and-1 (+ h 1/3)))
(tg (between-0-and-1 h))
(tb (between-0-and-1 (- h 1/3))))
(values (tc-to-c tr p q)
(tc-to-c tg p q)
(tc-to-c tb p q))))
(defun def-hsl-color (name h s l)
(multiple-value-bind (r g b) (hsl-to-rgb h s l)
(format t ".defcolor ~a rgb ~a ~a ~a~%" name r g b)))
Here, the Lisp procedure def-hsl-color takes an HSL spec and writes out the equivalent RGB groff definition. (The troff2page distribution provides def-hsl-color in the macro file defhsl.tmac.)
We can call this Lisp procedure inside another .eval to define venetianred using its HSL spec:
(def-hsl-color "venetianred" 357 .49 .24)
The color name venetianred can now be used like any other groff color name:
Prismacolor’s burnt ochre pencil is a close match for Derwent’s
\fB\m[venetianred]Venetian red\m\fP, and either can be used to
emulate the sanguine chalk drawings of the Old Masters.
Prismacolor’s burnt ochre pencil is a close match for Derwent’s Venetian red, and either can be used to emulate the sanguine chalk drawings of the Old Masters.
Extending troff2page only
troff2page treats troff’s .ig environment, whenever it uses ## as ender, as containing Lisp code that can be used to extend troff2page. This syntactic overloading of .ig ## is an innovation introduced by Oliver Laumann’s unroff.
Any Lisp code enclosed within .ig ## ... .## will be processed by troff2page but not by troff, which treats it as a multiline comment of course. Note that .ig ## does not pipe its stdout back into the document, as .eval does. This is to maintain the invariant that as far as output is concerned, .ig ##, like other .igs, is always a comment. However, you can add Lisp code within .ig ## to influence how troff2page — but not troff! — processes the rest of the document.
For example, let’s define a \*[url] string register that simply typesets its URL argument within angle brackets.
.ds url \(la\fC\\$1\fP\(ra
This is adequate for the print output. For troff2page though, we’d like to re-define this macro, in Lisp, to create a hyperlink. We enclose this re-definition in a .ig ##, which not only allows it to be in Lisp, but also makes it apply only when troff2page reads it:
The procedures defstring, link-start, and link-stop are defined in the troff2page code.
.ig ## can be used to specify settings that are relevant only when troff2page is used on a document, e.g., stylesheet changes. troff2page uses the output-port *css-port* to write out style information. The user can also write to this port, e.g.,
(format *css-port* "
color: rgb(61,35,39); /* chocolate */
This sets the HTML headers with a foreground color of chocolate.4