7.  Extending troff using Common Lisp

To invoke Common Lisp from troff, we use the macro .eval with its closer .endeval. These are defined in the supplied troff macro file eval4troff.tmac, which you should put in your GROFF_TMAC_PATH.

.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, pi.1:

    .mso eval4troff.tmac
    .
    The ratio of the circumference of a circle to
    its diameter is \(*p \(~=
    .eval
    ;following prints pi, because cos(pi) = -1
    (princ (acos -1))
    (princ ".")
    (terpri)
    .endeval

Run troff (actually groff, and in unsafe mode too) on pi.1:

    groff -z -U -ms pi.1

(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
    pi.1:4: can’t open ‘.trofftemp-1.tmp’: No such file or directory

Call groff again as follows:

    groff -U -ms pi.1 > pi.ps

pi.ps will now look like:

The ratio of the circumference of a circle to its diameter is π ≈ 3.1415927.

The first groff call produces a Lisp file .trofftemp.lisp.3 The second groff call invokes Lisp to create an auxilary file for each .eval that gets sourced back into the document.

It is clear that Common 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, pi.1, and the resulting pi.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.

‌.ig ##

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 CL 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. As far as the output is concerned, .ig ##, like other .igs, remains a comment. However, you can add CL code within .ig ## to influence how troff2page processes the rest of the document. For example, in the troff macro file url.tmac supplied in the distribution, a \*[url] string register is defined that simply typesets its URL argument within angle brackets for troff.

    .ds url \(la\fC\\$1\fP\(ra

For troff2page, we re-define this macro, in Common 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:

    .ig ##
    (defstring "url"
      (lambda (url)
        (concatenate ’string
          (link-start url)
          url
          (link-stop))))
    .##

The procedures defstring, link-start, and link-stop are defined in the troff2page code.


3 This name may be different based on your setting of \*[AUXF] -- see page 2.