.\" $Revision: 1.12 $
.
.if !\n(.U .so tmac.hyper
.
.ds Ve 1.0
.ds Sc http://www-swiss.ai.mit.edu/scheme-home.html
.ds Md .
.
..fp 5 C
..pl 11i
.
.de Es
.ie n .DS I 3n
.el .DS
.nr sF \\n(.f
.ft 5
.ps -1
.vs -1
..
.
.de Ee
.ft \\n(sF
.ps
.vs
.DE
..
.
.de El
.sp .6
..
.
.nr P 0
.
.de Ps
.nr P 1 1
.SH
..
.de Pe
.nr P 0 0
..
.de Pr
.ds xx "
.if \\n(.$>=2 .as xx " \f2\\$2\fP
.if \\n(.$>=3 .as xx " \f2\\$3\fP
.if \\n(.$>=4 .as xx " \f2\\$4\fP
.if \\n(.$>=5 .as xx " \f2\\$5\fP
.if \\n(.$>=6 .as xx " \f2\\$6\fP
.if \\n(.$>=7 .as xx " \f2\\$7\fP
.if \\n(.$>=8 .as xx " \f2\\$8\fP
.if \\n(.$>=9 .as xx " \f2\\$9\fP
.if !\\nP .SH
.if \\n+P>2 .br
(\\$1\\*(xx)
..
.de Pa
.ds xx "
.if \\n(.$>=3 .as xx " \f2\\$3\fP
.if \\n(.$>=4 .as xx " \f2\\$4\fP
.if \\n(.$>=5 .as xx " \f2\\$5\fP
.if \\n(.$>=6 .as xx " \f2\\$6\fP
.if \\n(.$>=7 .as xx " \f2\\$7\fP
.if \\n(.$>=8 .as xx " \f2\\$8\fP
.if \\n(.$>=9 .as xx " \f2\\$9\fP
.if !\\nP .SH
.if \\n+P>2 .br
.Ha \\$1 "(\\$2\\*(xx)"
..
..bp
.TL
unroff \*(Ve Programmer's Manual
.AU
Oliver Laumann
.AB no
.I unroff
is a programmable, extensible troff translator that useful for
converting documents with embedded troff markup into another
format.
Although
.I unroff
has been designed with higher-level, structure-oriented target
languages (such as SGML) in mind, it fully supports all constructs
and idiosyncrasies of ordinary troff, so that even low-level
formatting requests can be handled correctly if desired.
.PP
Translation rules for a specific output format and knowledge about
existing troff macro packages are not hard-wired in
.I unroff ,
instead, the translation is controlled by a user-supplied set
of procedures written in the
.Hr -url \*(Sc "\f2Scheme\fP programming language" .
.Hr "\f2Scheme\fP programming language."
Interpretation of the procedures is facilitated by a full Scheme
interpreted embedded in
.I unroff .
This manual describes the Scheme primitives provided by
.I unroff
that can be used to customize the translation rules implemented
by existing back-ends and to write new ones for new output formats.
.AE
.NH
Additional Documentation
.PP
For a general overview of
.I unroff
and a description from the user's perspective, please read the
.Hr -url \*(Md/unroff.1.html "manual page"
.Hr "manual page"
.I unroff (1)
that accompanies the distribution.
In addition, there exists one manual page for each output format
for which a back-end is provided, and another one for each
combination of output format and troff macro package explaining
the translation rules associated with the individual macros.
For example, the back-end for the Hypertext Markup Language (HTML)
that is part of the distribution and that supports the
.B \-man
and
.B \-ms
macros comes with these manual pages:
.Es
.Hr -url \*(Md/unroff-html.1.html unroff-html(1)
.Hr -url \*(Md/unroff-html-man.1.html unroff-html-man(1)
.Hr -url \*(Md/unroff-html-ms.1.html unroff-html-ms(1)
.Hr unroff-html(1)
.Hr unroff-html-man(1)
.Hr unroff-html-ms(1)
.Ee
.PP
This text assumes familiarity with the basic troff and Scheme concepts.
For a troff manual, refer to the documentation provided by
your UNIX system's vendor.
As
.I unroff
supports a number of troff extensions introduced by the free
.I groff
formatter (which is part of the GNU project), you may want to read the
manual page
.I troff (1)
that is included in the groff distribution.
.PP
.I unroff
is centered around
.I Elk ,
the Scheme-based Extension Language Kit.
For a description of the Elk-specific Scheme language features
please refer to the documentation included in the Elk distribution
(which is freely available).
An overview of Elk can be found in:
Oliver Laumann and Carsten Bormann, Elk: The Extension Language Kit,
.I "USENIX Computing Systems" ,
vol. 7, no. 4, pp. 419\-449, 1994.
The Scheme language is described in several textbooks; and the
Revised^4 Report on the Algorithmic Language Scheme, on which
the IEEE Standard for Scheme is based, can be downloaded from
several major FTP sites.
.NH
Where to Place Scheme Code?\&
.PP
.I unroff
accepts Scheme code in a number of places.
First, a several Scheme files are loaded on startup:
.Es
scm/troff.scm
scm/\f2format\fP/common.scm
scm/\f2format\fP/\f2package\fP.scm
~/.unroff
.Ee
.PP
The first three path names are relative to a site-specific library
directory where the files have been installed by the system
administrator.
``troff.scm'' contains definitions that are independent of the
actual output format and troff macro-package; and the
file ``.unroff'' (loaded from the caller's home directory) typically
contains Scheme code to define user-preferences and to tailor
and extend the translation rules implemented by the files loaded
from a central location.
See the
.Hr -url \*(Md/unroff.1.html "manual page"
.Hr "manual page"
.I unroff (1)
for more information.
.PP
Additional files with user-supplied Scheme definitions
(e.\|g. translation rules for user-defined macros) can be passed to
.I unroff
by mentioning them in the command line.
In general, troff input files and Scheme source files can be mixed
arbitrarily when calling
.I unroff .
Finally, Scheme code can be embedded directly in the troff documents
by means of the new ``.##'' troff request and the corresponding
extension to the ``.ig'' request as explained in the
.Hr -url \*(Md/unroff.1.html "manual page" .
.Hr "manual page."
Such inline Scheme code is executed on-the-fly when it is encountered
by the parser while processing the document.
.NH
.Ha .events "Events and Event Handling"
.PP
.I unroff
interprets a troff document as a sequence of chunks of normal
text and interspersed ``events''.
Plain text is usually just copied to the current output (a file or
standard output).
The output produced for an event is determined by an ``event
handler'' (usually a Scheme procedure) that can be associated
with each event.
If no event handler can be found for an event encountered in the
currently processed document (with a few exceptions), a warning message
is displayed and the input that triggered the event is skipped
(in case of requests and macros) or treated like normal text.
For events such as troff requests, a separate Scheme procedure
can be defined for each request, and the name of the request that
triggered the event is then passed to the procedure as an argument.
An event handling procedure can be defined for
.if !\n(.U .RS
.IP \(bu
each troff request, including requests that perform intrinsic troff
functions, such as ``.de'' and ``.if''
.IP \(bu
each troff macro, whether user-defined or part of a macro
package
.IP \(bu
each troff string
.IP \(bu
each number register
.IP \(bu
each special character
.IP \(bu
each escape sequence
.IP \(bu
each character (to provide character translations)
.IP \(bu
each inline equation enclosed by the current
.I eqn (1)
delimiter characters
.IP \(bu
each end of sentence (defined as a period, exclamation mark, or
question mark, followed by a newline).
.if !\n(.U .RE
.PP
When invoked, every Scheme procedure associated with one of
the above events receives one or more arguments.
For example, a procedure registered for the escape sequence `\eh'
(horizontal space) is passed the name of the escape sequence
(the letter `h') as well as the argument to `\eh' (i.\|e. the amount
of space).
Likewise, event handling procedures for requests and macros are
called with the name of the request or macro as well as any
arguments specified in the troff input.
The exact arguments passed to each type of event handler will be
explained below.
.PP
A Scheme procedure associated with an event must return a string
which is then output in place of whatever input triggered the
event.
Here, and in a number of other places, a Scheme symbol or a Scheme
is accepted as an alternative to a string return value.
Event handling procedures are free to directly produce output
in addition to returning it as a result.
As procedures associated with events frequently just return a
fixed text, the text itself may be defined as the event handler
in place of the procedure to save the overhead of the procedure
call.
.PP
Predefined Scheme procedures are supplied for events such as the
requests ``.de'', ``.nr'', ``.ds'', and the corresponding escape
sequences `\en' and `\e*' to support user-defined macros, strings,
and number registers.
In any case, specific event handlers registered for macros,
strings, and number registers supersede any user-supplied
definitions.
Thus, the author of a document can attach a
special translation rule to a macro, string, or number register
defined in the document to take effect when the document is processed by
.I unroff .
This is particularly important for high-level, structure-oriented
target languages like SGML, as the the micro-formatting
used by typical, more complex troff macros and by many low-level requests
may not be expressible in such languages.
As a case in point, it would obviously be impossible to translate, for
example, the ``.IP'' macro defined by the ``ms'' package to a
language such as HTML just by looking at the definition of the macro.
For this reason,
.I unroff
does not really load the actual macro definitions for a troff macro
package selected via the ``\-m'' option; instead, an event handler
is defined for each macro exported by the package to generate
whatever represents the corresponding macro's function in the
target language.
.NH
Defining Event Handlers
.PP
In the following list of Scheme primitives, the argument
.I name
denotes the name of a troff request, macro, escape sequence
etc.  (without any initial period or escape character) and can be
supplied in form of a Scheme string, a Scheme symbol, or
a Scheme character:
.Es
(defrequest "ti" ...)
.El
(defrequest 'sp  ...)
.El
(defescape  #\eh  ...)
.Ee
(the primitives
.I defrequest
and
.I defescape
will be introduced in a moment).
An argument named
.I handler
is either a procedure (usually a lambda expression) which returns
a string, a symbol, or a character; or
.I handler
can itself be specified as a string, symbol, or character.
In addition, the literal ``#f'' (false) can be supplied as a
.I handler
argument to remove any event handler that is currently associated with
that event.
Each of the ``def'' primitives listed below returns the handler
that was previously associated with the corresponding event,
or ``#f'' if the event was not handled.
.Pr defrequest name handler
.PP
Associates the given handler with the given troff request.
If
.I handler
is a procedure, it is passed the request's name and arguments
as strings when called later.
Passing the name of the request as the first argument aids in
associating the same procedure with several different requests.
.I unroff
does not limit the number of arguments to requests, thus,
an event handling procedure for a requests that takes a variable
number of arguments could be defined like this:
.Es
(defrequest 'rm
  (lambda (rm . args) ...))
.Ee
.LP
If the request is invoked with fewer arguments than the procedure
has formal arguments, the remaining arguments are bound to
the empty string.
If the request is invoked with
.I more
arguments than the procedure has formal arguments, the last lambda
variable is assigned a string consisting of the (space-delimited)
arguments left over after the other formal arguments have been bound to
the other actual arguments.
However, if
.I handler
has only one formal argument, an error message is displayed when the
request is called with any arguments at all and the event is skipped.
For example, consider the following handler for the (non-existing)
request ``xx'':
.Es
(defrequest 'xx
  (lambda (name a b) ...))
.Ee
The procedure's arguments
.I a
and
.I b
will be bound as follows when the request is invoked:
.Es
\&.xx foo              name="xx"  a="foo"  b=""
.El
\&.xx foo bar baz      name="xx"  a="foo"  b="bar baz"
.Ee
.Pr defmacro name handler
.PP
Associates
.I handler
with the given troff macro, superseding
any definition for this macro established by the ordinary ``.de''
request.
The only difference between
.I defrequest
and
.I defmacro
is the way arguments are bound in case
.I handler
is a procedure
(troff employs slightly different rules when parsing the call
to a request and a macro invocation).
The quote character can be used in the latter case to surround
arguments containing spaces, while quote characters are treated as
normal characters in requests, which allows for the following
remarkable troff idiom:
.Es
\&.ds xy "hello
.Ee
In contrast to event handlers defined for requests, the formal
arguments of a handler procedure associated with a macro must
match the actual arguments in the normal way, that is, as if
the procedure were invoked from within Scheme.
A warning message is displayed if the number of macro arguments
does not match the number of formal procedure arguments, and
the event is skipped.
.Pr defspecial name handler
.PP
Associates
.I handler
with the special character whose name is
.I name .
The name must have a length of 2.
In addition, an empty name can be specified to define a
``fallback'' handler that is called for special characters
for which no handler exists.
Like all event handler procedures,
.I handler
can have arbitrary side-effects in addition to returning a
result; for example, the procedure may display a warning message
if the special character cannot be represented in the target
language and an approximation must be rendered instead.
.Pr defstring name handler
.PP
Associates a handler with the specified troff string.
As
.I unroff
provides a default handler for the request ``.ds'' to implement
used-defined strings,
.I defstring
is primarily used to give definitions for strings exported by
troff macro packages.
.Pr defnumreg name handler
.PP
This request behaves like
.I defstring ,
except that it works on number registers.
Note that the Scheme primitive
.I number\(mi>string
may have to be used by
.I handler
(if it is a procedure) to convert a numeric result into a string
that can be returned from the handler.
.LP
In troff input, number registers as well as strings, special
characters, and escape sequences can be denoted using the groff
``long name'' syntax, unless troff compatibility has been enabled:
.Es
\en[numreg]   \en[string]   \ef[font]   \e[em]   ...
.Ee
.Pr defescape name handler
.PP
Associates an event handler with an escape sequence.
.I name
must have a length of 1, unless the empty string is
given to define a ``fallback'' event handler (as with
.I defspecial ).
Handlers defined for certain escape sequences are passed
a second argument in addition to the name of the escape sequence.
This is true for all escape sequences that have an argument
according to the troff specification:
.Es
\eb  \ec  \ef  \eh  \ek  \el  \en  \eo  \es  \ev  \ew  \ex  \ez
\e*  \e$  \e"
.Ee
In addition, handlers for these groff escape sequences are passed an
additional argument unless troff compatibility is enabled:
.Es
\eA  \eC  \eL  \eN  \eR  \eV  \eY  \eZ
.Ee
The form of an escape sequence argument is determined by the
troff specification and cannot be programmed; for example, the
handler for `\ez' is passed a character or a special character,
and the handler for `\e"' is invoked with the rest of the current
input line sans the terminating newline.
(The latter can be used to translate troff comments.)
.LP
Handlers registered for the escape sequences `\en' and '\es' are
passed an optional third argument, one of the Scheme characters
#\e+ and #\e\(mi, if the escape sequence argument begins with a sign.
The sign is then stripped from the actual argument.
.LP
As `\en' and `\e*' are treated as ordinary escape sequences,
handlers can be defined for them to achieve some form of fallback
for number register and strings.
.I unroff
provides suitable default handlers for `\en', `\e*', and '\e$' as part
of the implementation of user-defined number registers, strings,
and macros.
These handlers can be overridden if desired.
.Pr defchar name handler
.PP
Associates
.I handler
with a character.
.I name
must have a length of 1.
Each time the specified character is encountered in the troff
input, the result (or value) of
.I handler
is output in place of the character.
Character translations are not applied to the result of event
handlers; event procedures can use the Scheme primitive
.Hr -symbolic .translate \f2translate\fP
.Hr \f2translate\fP
(as described below) to execute the character translations
established by calls to
.I defchar
if desired.
.LP
.I defchar
currently has a number of weaknesses.
The argument cannot be a special character
(that is,
.I name
must be a plain character), and the mechanism cannot be used
to achieve true
.I output
translations as with the troff request ``.tr'' or the groff
request ``.char''.
.Pr defsentence handler
.PP
Defines a handler to be consulted on end of sentence.
If
.I handler
is a procedure, it is passed the punctuation mark ending the
sentence as its argument (in form of a Scheme character).
In any case, if an event handler has been specified, its result
(or value) is output in place of the end-of-sentence mark and
the newline character following it.
.Pr defequation handler
.PP
Defines a handler for
.I eqn
inline equations.
If
.I handler
is a procedure, it is passed the contents of the inline equation
(with the delimiters stripped) as an argument.
When an inline equation is encountered in the troff input and a handler
has been defined for inline equations, the handler's result (or value)
is output in place of the equation.
.LP
For inline equations to be recognized, delimiters must be defined first
by passing
.I eqn
input that includes a ``delim'' directive to the Scheme primitive
.Hr -symbolic .filter-eqn-line \f2filter-eqn-line\fP
.Hr \f2filter-eqn-line\fP
(explained below), as is usually done
by the event handler associated with the request ``.EQ''.
.NH
Querying Event Handlers
.PP
In addition to associating event handlers with events by means
of the ``def'' primitives, several primitives exist to query
the currently defined handler for a given event:
.Ps
.Pr requestdef name
.Pr macrodef name
.Pr specialdef name
.Pr stringdef name
.Pr numregdef name
.Pr escapedef name
.Pr chardef name
.Pr sentencedef
.Pr equationdef
.Pe
.PP
Observe that the name of each primitive is derived from the name
of the corresponding ``def'' primitive by exchanging the word
``def'' and the rest of the name.
Each
.I name
argument is subject to the constraints described under the
corresponding ``def'' primitive above.
Each primitive returns whatever object has been registered as
the event handler (procedure, string, symbol, character);
or #f if no handler has been defined for the event.
.NH
Event Procedures with Side-Effects
.PP
Besides the basic events described in the
.Hr -symbolic .events "preceding sections" ,
.Hr "preceding sections,"
another group of\*-slightly different\*-events exist and can
be handled by user-defined Scheme procedures.
These events are not related to troff functions, but to a number of
other conditions that are encountered when processing documents:
.if !\n(.U .RS
.IP \(bu
the end of an input line
.IP \(bu
the beginning of a troff input file processed by
.I unroff
.IP \(bu
the end of a troff input file
.IP \(bu
startup of the program
.IP \(bu
termination of the program
.IP \(bu
a keyword/value option encountered in the command line.
.if !\n(.U .RE
.PP
Among other tasks, these events can be used to generate a prologue and
epilogue for each input file.
In contrast to the events described in the previous section, handlers for
these events are called solely for their side-effects.
Each event handler must be a Scheme procedure.
Their results are ignored, thus the procedures must have side-effects
to be useful.
Another difference is that more than one event handler can be associated
with each request.
A numeric
.I level
(a small integer number) is specified together with each event handler,
and when the corresponding event is triggered, all procedures
defined for this event are executed in increasing order as indicated by
their levels.
.Pr defevent event level handler
.PP
Associates the procedure
.I handler
with an event and returns the previous event handler registered
for this combination of event and level.
.I level
is an integer between 0 and 99;
.I handler
is a procedure, or the literal #f to remove a previously defined handler.
.I event
indicates the type of event and is one of the following Scheme symbols:
.I line
(end of input line),
.I prolog
(beginning of input file),
.I epilog
(end of input file),
.I start
(program start),
.I exit
(program termination),
.I option
(keyword/value command line option).
.LP
Procedures defined for the events
.I prolog
and
.I epilog
are called with two string arguments:
the path name (as specified by the user) and the file name component of
the troff input file whose processing has just begun or finished,
or the string ``stdin'' if
.I unroff
is taking its input from standard input.
Procedures defined for the event
.I option
are passed the option's name and value as strings.
All other event procedures are invoked without arguments.
.I unroff
provides a default handler for
.I option
(see the
.Hr -symbolic .options "primitives for options"
.Hr "primitives for options"
below).
.LP
Example:
.Es
(defevent 'exit 50     ; cleanup on exit
  (lambda ()
    ...))
.Ee
The handler defined in this way will be executed on termination,
after any handlers with levels 0\-49.
.Pr eventdef event level
.PP
Returns the procedure defined as a handler for
.I event
and
.I level ,
or #f if no such handler exists.
See
.I defevent
above for a description of the arguments.
.NH
How Troff Input is Processed
.PP
To be able to write non-trivial event handling procedures, it helps
to have a look at how troff input is processed, especially since
the parser of
.I unroff
works somewhat differently than ordinary troff.
In particular, the parser cannot blindly rescan the result of
handlers for escape sequences or special characters, as these
handlers will probably generate text in the
.I "target language"
that cannot be interpreted as troff input any longer.
Here is a brief overview of the parsing process.
.PP
Each input line is first scanned for references to troff strings and
number registers (this scanning pass will later be referred to as the
``expansion phase'').
For each `\e*' or `\en' sequence found in the input line,
.I unroff
checks whether a handler for the string or number register has
been defined with
.I defstring
or
.I defnumreg ,
and if this is the case, replaces the string or number register
reference by the result (or value) of the handler.
Otherwise, if a handler for the escape sequence `\e*' or `\en'
proper has been defined, that handler is called.
Otherwise the reference is left untouched and scanning resumes
behind it\**.
.FS
Although the result of specific event handlers defined for
strings is not rescanned, the handler for `\e*' that is supplied by
.I unroff
to implement user-defined strings does rescan the contents of
a string when it is expanded.
.FE
Comments are recognized in this phase, too, by calling the handler
for the `\e"' escape sequence if there is one.
.PP
Next, the parser checks whether the result of the first phase
is a request or macro invocation (that is, begins with a period
or an apostrophe).
If this is the case, the arguments are parsed mimicking the
behavior of ordinary troff.
The rules for macro arguments are employed if
a handler has been defined
for the token after the period with
.I defmacro ,
else the rules for requests are used.
The handler for the macro or request is then used, or applied
to the arguments if it is a procedure.
.PP
If the input line does not contain a request or macro invocation,
it is scanned a second time to take care of escape sequences
and special characters (for lack of a better term, we will call
this phase ``escape parsing'').
Every escape character reference, special character, and inline
equation is replaced by the result (or value) of the event
handler registered for it, or left in place if there is no handler.
Character translations defined by means of
.I defchar
are also executed in this phase.
.PP
Finally, the result of the escape parsing phase or of the request or
macro invocation is checked whether it constitutes the end of a
sentence, and if so, the handler for this event is called
(actually, in the former case, the check is applied before
.I and
after the escape parsing and must succeed both times).
As the final step the line is output, and any handlers for the
.I line
event are invoked.
.PP
An important thing to note is that the arguments passed to a handler
defined for a request or macro are not scanned for escape sequences
and special characters.
Therefore event procedures must explicitly parse their arguments if
desired by calling the Scheme primitive
.Hr -symbolic .parse \f2parse\fP
.Hr \f2parse\fP
(which will be described in the next section).
Consider, for example, an event procedure associated with a
macro ``IP'':
.Es
(defmacro 'IP
  (lambda (IP tag . indent)
    ...))
.Ee
and a call to the macro with an argument containing a
special character:
.Es
\&.IP \e(bu
.Ee
As the argument to the event procedure is only scanned for
strings and number registers, the variable
.I tag
will be bound to the string ``\e(bu''.
Applying
.I parse
to the argument will turn it into whatever is the target language
representation for the special character ``\e(bu'' (that is, the
result of the event handler for the special character).
Whether or not arguments will have to be parsed depends on the
particular request or macro; the procedure implementing the request
``.tm'', for instance, will print its ``raw'' argument (a sample
event handler for the request ``.tm'' is supplied by
.I unroff ).
.NH
Calling the Parser
.PP
The following Scheme primitives are used by event procedures for
requests, macros, and escape characters to parse their arguments
or to parse lines of text that have been read from an input source.
Each of the primitives can be invoked with zero or more arguments
of type string, symbol, or character.
The arguments are concatenated to form a Scheme string which is then
passed to the parser, and the result is returned as a new string.
.Pa .parse parse . args
.PP
This primitive feeds its arguments to the ``escape parsing''
pass as described in the previous section.
It scans its arguments for special characters and escape
sequences and replaces them by the corresponding event values
(or results), and it executes character translations.
.Pa .translate translate . args
.PP
Like
.I parse
above, except that only output character translations (defined by calls to
.I defchar )
are executed.
.Pr parse-expand . args
.PP
This primitive applies the ``expansion parsing'' phase (as described in the
previous section) to its arguments.
Compared to
.I parse ,
.I parse-expand
is only used rarely, as input lines read in the normal way are
scanned for string and number register references anyway.
The sample implementation supplied by
.I unroff
for the requests ``.ds'', ``.as'', and '\e*' makes use of this primitive
to rescan the contents of user-defined strings upon interpolation.
.Pr parse-line . args
.PP
This primitive parses an entire input line, which may contain a call
to a request or macro, as described in the previous section.
The line made up by the primitive's arguments is treated exactly as
it if were read from an input file, although it need not have a
terminating newline.
Two places where this primitive is required are the handler for
the request ``.so'' and the code that expands user-defined macros.
.Pr parse-copy-mode . args
.PP
The primitive
.I parse-copy-mode
parses its arguments in a manner similar to troff ``copy mode''.
In this mode, escape sequences beginning with '\e$' are dealt
with (by calling their event procedures), the sequence `\e\e'
is replaced by a single `\e', and each occurrence of `\e.'
is replaced by a period.
Macro bodies are parsed in copy mode during macro definition and again
when the macros are expanded.
.PP
The sample implementation of user-defined macros supplied by
.I unroff
defines suitable event handlers for the usual
.Es
\e$1  \e$2 ...
.Ee
escape sequences (there is no limit to the number of arguments,
and the groff long name convention may be used to denote an
argument number), and in addition for the groff extensions
.Es
\e$0   \e$*   \e$@
.Ee
as explained in the
.Hr -url \*(Md/unroff.1.html "manual page"
.Hr "manual page"
.I unroff (1).
.Ps
.Pr parse-expression expr fail scale
.Pr parse-expression-rest expr fail scale
.Pe
.PP
These primitives evaluate the numeric expression specified by
the string argument
.I expr
and return the result as an exact number.
The usual troff expression syntax, operators, and scale
indicators are supported.
If an error occurs during evaluation (for instance, if
.I expr
is not a syntactically valid expression),
a warning message is displayed and
.I fail
(which may be an arbitrary Scheme object) is returned.
The character argument
.I scale
is the default scale indicator, for example `#\em', or `#\eu'
for basic units.
.PP
The primitive
.I parse-expression-rest
is identical to
.I parse-expression ,
except that its return value is a cons cell whose car consists
of the result of the evaluation and whose cdr is the rest of
.I expr
starting at the character position where parsing of the
expression stopped.
In other words, the primitive evaluates the portion of
.I expr
that constitutes a valid expression, and it returns the result
and whatever is left over.
Warning messages are also suppressed, except if an overflow occurs
during evaluation.
.I parse-expression-rest
is useful for tasks like parsing the argument of the escape
sequences `\el' and `\eL' where an expression is immediately
followed by another character.
Examples:
.Es
(parse-expression "(2+8)/5" 0 #\eu)     \(rh  2
(parse-expression "foo" #f #\eu)        \(rh  #f; prints warning
.El
(parse-expression-rest "1+1" #f #\eu)         \(rh  (2 . "")
(parse-expression-rest "(2+8)/5foo" 0 #\eu)   \(rh  (2 . "foo")
(parse-expression-rest "15\e&-" 0 #\eu)        \(rh  (15 . "\e&-")
.Ee
.Pr char-expression-delimiter? char
.PP
Returns #t if the character argument
.I char
is valid as the first character of a numeric expression (e.\|g. a digit),
otherwise #f.
.Ps
.Pr set-scaling! scale factor divisor
.Pr get-scaling scale
.Pe
.PP
These primitives set and read the scale factor and divisor for
the specified scale indicator.
.I scale
is the scale indicator (a character);
.I factor
and
.I divisor
are integers.
.I get-scaling
returns the scaling for the specified scale indicator as a pair
of integers.
The factors and divisors are initially set to 1 for all scale
indicators; they must be assigned useful values by each back-end.
.NH
Streams
.PP
Input, output, and storage of text lines in
.I unroff
are centered around a new Scheme data type named
.I stream
and a set of primitives that work on streams.
A stream can act as a source (input stream) or as a sink (output
stream) for lines of text.
Streams not only serve as the basis for input and output operations
and for the exchange of text with shell commands, but can also be used
to temporarily buffer lines of text (e.g. footnotes or tables of
contents) and to implement user-defined macros in a simple way.
Each input or output stream can be connected to one of the
following three types of
.I targets :
.if !\n(.U .RS
.IP \(bu
a file, or the program's standard input or standard output
.IP \(bu
a UNIX pipe connected to a shell running a shell command
.IP \(bu
an internal
.I buffer
whose lifetime is limited to that of the current invocation of
.I unroff .
.if !\n(.U .RE
.PP
Buffers act similar to (initially empty) files, except that
they are not visible from the outside and that they are destroyed
automatically on exit of the program.
Once a buffer has been filled with text through an output stream,
it can be reopened and read through an input stream multiple times.
However, if a buffer is currently written through an output stream,
no more streams may refer to the same buffer.
As the contents of buffers kept in memory, input and output operations
on buffers are fast.
The sample implementation of user-defined macros utilizes buffers
to store the macro bodies; a macro can then be expanded simply
by redirecting the current input source to the corresponding buffer
temporarily.
.PP
Both the parser and all input and output primitives operate on a
.I "current input stream"
and a
.I "current output stream" ;
input and output is always performed using these two streams.
On startup,
.I unroff
initializes the current output stream to either point to
standard output or to a newly created output file (usually depending on
the value of the
.B document
option).
If the current output stream is assigned the literal #f,
output is sent to standard output\**.
.FS
While #f indicates ``standard output'' when assigned to
the current output stream, it is an error to call an input primitive
after #f has been assigned to the current
.I input
stream.
This may be considered a mis-feature; the current input and
output streams should be treated similarly with respect to
standard input and standard output.
.FE
Likewise, for each input file mentioned in the command line,
a stream pointing to that file is created and assigned to
the current input stream before the parser starts processing
the file.
The rest of this section lists the Scheme primitives operating
on streams.
.Pr stream? obj
.PP
The type predicate for the new data type.
It returns #t if
.I obj
is a member of the type
.I stream ,
otherwise #f.
.Ps
.Pr input-stream
.Pr output-stream
.Pe
.PP
Returns the current input stream, or output stream respectively.
.Ps
.Pr open-input-stream target
.Pr open-output-stream target
.Pr append-output-stream target
.Pe
.PP
These primitives create a new input stream or output stream pointing
to the specified target.
The argument
.I target
is a string or a symbol.
If the target is enclosed in square brackets, it names a buffer;
if it begins with the pipe symbol `|', a pipe to a shell running
the rest of the target as a shell command is established; otherwise
.I target
is interpreted as a file name.
.I append-output-stream
rewinds to the end of the specified output buffer or file before
the first output operation; it acts like
.I open-output-stream
in case of a pipe.
Examples:
.Es
(let* ((buffer (open-output-stream '[temp]))
       (pipe   (open-input-stream "|ls -l /usr/lib/tmac"))
       (file   (open-input-stream "/etc/passwd")))
  ...)
.Ee
.Ps
.Pr set-input-stream! stream
.Pr set-output-stream! stream
.Pe
.PP
These primitives make the specified stream the
.I current
input stream (or output stream respectively).
.I stream
must be the result of a call to one of the three primitives that
open a stream, or #f.
An error is signaled if
.I set-input-stream!
is applied to an output stream or vice versa, or if the stream
has been closed in the meantime.
.Pr close-stream stream
.PP
Closes the specified stream.
An error is signaled if the stream is still the current input
stream or current output stream.
Once an output stream pointing to a buffer has been closed, the
buffer can be reopened for reading.
A stream that is no longer reachable is closed automatically
during the next run of the garbage collector.
.Ps
.Pr stream-buffer? stream
.Pr stream-file? stream
.Pr stream-pipe? stream
.Pe
.PP
These predicates return #t if the specified stream points to a
buffer, a file, or a pipe respectively, otherwise #f.
.Pr stream-target stream
.PP
This primitive returns the target to which the specified stream
points.
The return value is a string.
In case of a pipe, the target is truncated at the first space,
that is, only the command name is included.
The target of the current input stream (together with the current
line number) is displayed as a prefix of error messages and
can also be obtained through the primitive
.Hr -symbolic .substitute \f2substitute\fP
.Hr \f2substitute\fP
described below.
.Pr stream-position stream
.PP
Returns the current character position of the specified output stream,
that is, the offset at which the next character will be written.
The return value for input streams is currently always zero.
This primitive is useful in conjunction with
.Hr -symbolic .file-insertions \f2file-insertions\fP
.Hr \f2file-insertions\fP
(described below).
.Pr stream\(mistring target
.PP
This primitive opens an input string to the specified target,
reads from the stream until end-of-stream is reached, closes
the stream, and returns the concatenation of all the lines that
have been read as a string\**.
.FS
.I stream\(mi>string
is a misnomer, because the argument of the primitive is not
a stream, nor does the primitive actually
.I convert
a stream to a string as suggested by the `\(mi>' sign.
.FE
.NH
Input and Output Primitives
.PP
.I unroff
provides one new input primitive and one new output primitive that
work with the current input stream and current output stream (and a
third primitive which is just an optimization of the latter, as
well as a few auxiliary functions).
.Pr emit . args
.PP
.I emit
is the only stream-based output primitive.
It receives any number of strings, symbols, and characters,
concatenates its arguments, and sends the resulting string to
the current output stream (to standard output if the the current
output stream has been assigned #f).
.I emit
is primarily used in situations where text has to
be output without rescanning it and without applying any
character translations.
It is also used from within the event procedures that are called
for their side-effects, for example, by the
.I prolog
and
.I epilog
event procedures to generate a header and trailer for each
output file.
The primitive returns the empty symbol so that it can be called
as the last form in an event procedure whose result is used.
.PP
Example:
the new troff request for transparent output, as explained in the
.Hr -url \*(Md/unroff.1.html "manual page"
.Hr "manual page"
.I unroff (1),
can be implement like this:
.Es
(defrequest '>>
  (lambda (>> code)
    (emit code #\enewline)))
.Ee
.Pr read-line
.PP
This primitive reads the next input line from the current input
stream and returns it as a string.
An error is signaled if the current input stream has been bound
to #f, which is the case, for example, when
.I unroff
has been called with the option
.B \-t
to start an interactive top level.
If an incomplete last line (i.\|e. a line without a terminating
newline) is returned by the target pointed to by the current
input stream, a newline is appended.
Thus,
.I read-line
always returns at least a string containing a newline character.
.Pr read-line-expand
.PP
This primitive is nothing more than an optimization for
.Es
(parse-expand (read-line))
.Ee
which has been provided to speed up frequently used functions like
macro expansion.
.Pr unread-line string
.PP
This primitive pushes back an input line to the current input
stream, which will then be returned by the next call to
.I read-line
or
.I read-line-expand ,
or it will be read by the parser in the normal way when processing
the current input file.
.I string
need not have a terminating newline.
Strings pushed back by multiple calls to
.I unread-line
are coalesced and returned as a whole by the next input operation.
.Pr error-port
.PP
Returns a Scheme output port that is bound to the program's
standard error output.
This primitive is used by the default Scheme error handler provided
by
.I unroff
and by the
.I warn
utility function\**.
.FS
The primitive
.I error-port
should actually be provided by Elk proper to avoid having to
reinvent it for each extensible application.
.FE
Note that
.I error-port
returns an ordinary Scheme port, not a stream.
.NH
String Functions
.PP
Most of the string handling primitives described in this section
could as well have been implemented in Scheme based on the standard
Scheme string primitives.
They are provided as built-in primitives by
.I unroff
mainly as optimizations or because writing them as Scheme
procedures would have been significantly more cumbersome.
All the string functions return new strings, that is, they
do not modify their arguments.
.Pr concat . args
.PP
.I concat
can be called with any number of Scheme strings, symbols, and
characters.
The primitive concatenates its arguments and returns the result
as a string.
.Pr spread
.PP
This primitive is identical to
.I concat ,
except that it delimits its arguments by a space character.
For example, the event procedure for a macro that just
returns a line consisting of its arguments could be define like this:
.Es
(defmacro 'X
  (lambda (X . words)
    (parse (apply spread words) #\enewline)))
.Ee
.Pr repeat-string num string
.PP
Returns a string consisting of the string argument
.I string
repeated
.I num
times.
.Pr string-prune-left string prefix fail
.PP
This primitive checks whether
.I string
starts with the given string prefix, and if so, returns the rest of
.I string
beginning at the first character position after the initial prefix.
If the strings do not match,
.I fail
is returned (which may an arbitrary object).
Example:
.Es
(string-prune-left "+foo" "+" #f)     \(rh  "foo"
(string-prune-left "gulp" "+" #f)     \(rh  #f
.Ee
.Pr string-prune-right string suffix fail
.PP
This primitive is identical to
.I string-prune-left ,
except that it checks for a suffix rather than a prefix,
that is, whether
.I string
ends with
.I suffix .
.Pr string-compose string1 string2
.PP
If the argument
.I string2
begins with a plus sign,
.I string-compose
returns the concatenation of
.I string1
and
.I string2
with the initial plus sign stripped.
If
.I string2
begins with a minus sign,
it returns a string consisting of
.I string1
with all characters occurring in
.I string2
removed.
Otherwise,
.I string-compose
just returns
.I string2 .
This primitive is used for the implementation of the option type
.I dynstring .
.Pr parse-pair string
.PP
If
.I string
consists of two parts separated and enclosed by an arbitrary delimiter
character,
.I parse-pair
returns a cons cell holding the two substrings.
Otherwise, it returns #f.
Example:
.Es
(parse-pair "'foo'bar'")    \(rh  ("foo" . "bar")
(parse-pair "hello")        \(rh  #f
.Ee
.Pr parse-triple string
.PP
This primitive is identical to
.I parse-pair ,
except that it breaks up a three-part string rather than a
two-part string and returns an improper list whose car, cadr,
and cddr consist of the three substrings\**.
.FS
The primitive
.I parse-triple
should probably return a proper list rather than an improper list.
.FE
.I parse-pair
and
.I parse-triple
are useful mainly for parsing the arguments to troff requests such
as ``.if'' and ``.tl''.
.Pa .substitute substitute string . args
.PP
This primitive returns a copy of
.I string
in which each sequence of a percent sign, a
.I "substitution specifier" ,
and another percent sign is replaced by another string according
to the specifier.
Two adjacent percent signs are replaced by a single percent sign.
The following list describes all substitution specifiers together
with their respective replacements.
.IP \f3macros\fP
The name of the troff macro package whose macros are recognized,
that is, the argument to the option
.B \-m
(or the empty string if none was specified).
.IP \f3format\fP
The output format, that is, the argument to the option
.B \-f
(or the default output format if the option was omitted).
.IP \f3directory\fP
The name of the library directory from which
.I unroff
loads its Scheme files.
.IP \f3progname\fP
The name of the running program (this is used as a prefix in
error messages and warning messages).
.IP \f3filepos\fP
A space character followed by the target of the current input
stream, a colon, the number of the last input line read from
the stream, and another colon.
If the current input stream is bound to #f, the empty string
is substituted.
This specifier is useful for displaying error messages or warning messages.
.IP \f3tmpname\fP
A file name that can be used for a temporary file.
Each use of this specifier creates a new, unique file name.
.IP \f3version\fP
The program's major and minor version numbers separated by a period.
.IP \f3weekday\fP
The abbreviated weekday name.
.IP \f3weekday+\fP
The full weekday name.
.IP \f3weekdaynum\fP
The weekday (0\-6, Sunday is 0).
.IP \f3monthname\fP
The abbreviated month name.
.IP \f3monthname+\fP
The full monthname.
.IP \f3day\fP
The day of the month (01\-31).
.IP \f3month\fP
The month (01\-12).
.IP \f3year\fP
The year.
.IP \f3date\fP
The date (in the local environment's representation).
.IP \f3time\fP
The time (in the local environment's representation).
.IP "a positive number \f2n\fP"
The
.I n th
additional argument in the call to the
.I substitute
primitive, which must be a string.
.IP "a \f2string\fP"
.I string
is interpreted as the name of an environment variable,
and the value of this variable is substituted (or the empty
string if the environment variable is undefined).
.LP
Examples:
.Es
(substitute "%date% %HOME%")     \(rh  "04/09/95 /home/kbs/net"
.El
(substitute "%progname%:%filepos% %1%" "hello")
				 \(rh "unroff: manual.ms:21: hello"
.El
(load (substitute "%directory%/scm/%format%/m%macros%.scm"))
.Ee
.NH
Tables
.PP
.I unroff
provides simple hash tables as a new first class data type
.I table .
Each table entry associates an arbitrary Scheme object with
a key (a Scheme string or symbol).
Tables are useful for various purposes; for example, the Scheme code
delivered with
.I unroff
maintains hash tables to store information about number registers,
options, fonts, and for other bookkeeping tasks.
.Pr table? obj
.PP
The type predicate for the new type; it returns #t if
.I obj
is a member of the type
.I table ,
otherwise #f.
.Pr make-table size
.PP
Returns a new table of the specified size.
.I size
is a positive integer.
The smaller the size, the more collisions occur as entries
are added to the table.
However, the hash function employed by the table primitives
ensures that no collisions occur in tables of size
256^\c
.I n
if all keys have a length less than or equal to
.I n .
.Pr table-store! table key obj
.PP
This primitive stores the Scheme object
.I obj
under the given
.I key
in the given
.I table .
The key argument must be a string or a symbol.
.Pr table-lookup table key
.PP
This primitive checks whether an object is stored in the given
.I table
under the specified
.I key ,
and if so, returns the object.
If no object is stored under
.I key ,
.I table-lookup
returns #f.
.Pr table-remove! table key
.PP
Removes the entry selected by
.I key
from the specified table.
.NH
Miscellaneous Primitives
.PP
The first two primitives described in this section are not essential,
as the same function could be achieved with pipe streams,
although with greater overhead.
The remaining primitives perform a number of troff-specific operations
and are only useful in a few specialized contexts.
.Pr shell-command command
.PP
Runs the specified
.I command
(which must be a string) as a shell command by passing it to a call to
.I system (3).
The return value is that of
.I system()
(an integer).
.Pr remove-file filename
.PP
Removes the specified file;
.I filename
must be a string or a symbol.
.Pr troff-compatible?
.PP
This predicate returns #t if troff compatibility mode has been
enabled (i.\|e. if the option
.B \-C
has been given), otherwise #f.
.Pr set-escape! char
.PP
Sets the troff escape character (initially `\e') to the specified
character argument.
This primitive is used to implement the ``.ec'' request.
.Pa .filter-eqn-line filter-eqn-line string
.PP
This primitive scans the string argument (which is supposed to
be passed to the
.I eqn
preprocessor afterwards) for occurrences of the ``delim'' directive.
If a ``delim'' directive is found, the current inline equation
delimiters maintained by the parser are changed or disabled as specified by
the directive.
The primitive returns #f if
.I string
is empty or consists just of white space, or if it contains
a valid ``delim'' or ``define'' directive, otherwise #t.
The inline equation delimiters are disabled initially.
.PP
The primitive is supposed to be used by implementations of
the request ``.EQ'' and inline equation event handlers to intercept the
.I eqn
input.
In this case, the
.I eqn
preprocessor need only be invoked if
.I filter-eqn-line
returned #t at least once.
.Pr skip-group
.PP
This primitive reads input lines from the current input stream
and scans them for the escape sequences `\e{' and `\e}' until
the nesting level of conditional input is balanced (i.\|e. until
a matching closing brace for an initial opening brace has been found).
The primitive is only useful for the implementation of the
troff requests for conditional input.
.NH
File Insertions
.PP
The primitive
.I file-insertions
is a general-purpose utility for inserting strings into files
at specified locations in a fast and robust way.
One application is to resolve forward references of any kind among
a group of files when all files have been processed.
In this case, the insertions would be executed by an
.I exit
event handler.
.Pa .file-insertions file-insertions insertions
.PP
.I insertions
is a list specifying the parameters for the file insertions.
Each element of the list is itself a list consisting
of a file name (a string),
a file offset (an integer between zero and the size of the file),
and a string to be inserted in the given file at the given offset.
.I file-insertions
sorts the list to ensure that each file is only processed once
and that the offsets for each file are in increasing order.
Then each file is copied to a temporary file
.Es
\f2filename\fP.new
.Ee
(where
.I filename
is the original file name), and the specified insertions are
carried out as the file is copied.
When processing of a file is finished, the temporary file is
renamed to its original name.
If there exist links to a file, a warning is displayed and the
insertion is skipped.
.NH
Utilities for Back-Ends
.PP
Writers of new back-ends (either for new output formats or for new
troff macro packages) can benefit from a number of Scheme procedures
and macros that are exported by the file ``scm/troff.scm'' which is
loaded from the library directory on startup.
The first two,
.I eval-if-mode
and
.I set-option!
are exceptions in that they are typically used by the user's
initialization file ``~/.unroff'' to customize
.I unroff ,
rather than by programmers of
.I unroff .
.Pr set-option! name value
.PP
This procedure assigns
.I value
to the option
.I name .
The value must be appropriate for the option's type.
.Pr eval-if-mode mode . forms
.PP
This macro is typically used to evaluate a sequence of expressions,
.I forms ,
depending on the output format and macro package specified in
the command line.
.I mode
is a list of two symbols, an output format and a macro package
name; the wildcard `*' can be used for both elements.
The
.I forms
are evaluated if the first symbol matches the value of the option
.B \-f
and the second symbol matches the value of the option
.B \-m ;
in this case the result of the last sub-expression is returned.
Otherwise the forms are ignored and #f is returned.
Example:
.Es
(eval-if-mode (* html)
  (set-option! 'mail-address "net@cs.tu-berlin.de"))
.Ee
.Ps
.Pr quit message . args
.Pr warn message . args
.Pe
.PP
These procedures print
.I message
and the optional
.I args
on the port returned by
.I error-port
using the primitive
.I format .
The message is prefixed by the program name, current input file
name and line number, and, in case of
.I warn ,
the word ``warning''.
A newline is appended.
.I quit
causes the program to exit with an exit code of 1, and
.I warn
returns the empty string (and can therefore be used as the last
form in event procedures).
.Pa .options option name
.PP
Returns the value of the specified option.
.Pr define-option name type initial
.PP
Defines a new option with the specified name, type, and initial
value.
.I name
and
.I type
are strings or symbols.
There exist a number of predefined, basic option types as
described in the
.Hr -url \*(Md/unroff.1.html "manual page"
.Hr "manual page"
.I unroff (1).
The initial value need not match the option's type; for example,
the following expression is valid:
.Es
(define-option 'author 'string #f)
.Ee
.Pr define-option-type name pre-check pre-msg converter post-check post-msg
.PP
This procedure defines a new option type named 
.I name
which can then be used in calls to
.I define-option .
If an option of this type is specified in the command line,
the procedure
.I pre-check
is applied to the option's value (a string).
In this case, if
.I pre-check
returns #f,
.I quit
is called with an error message including the string
.I pre-msg ,
which should describe the expected option value format
(e.\|g. ``a character'').
If the check succeeds, the procedure
.I converter
is called with the option's current value and with the string as given
in the command line.
The job of the converter procedure is to convert the option value
from a string representation to a Scheme object matching the option's
actual Scheme type.
.PP
Finally, the predicate
.I post-check
is applied either to the result of
.I converter
or, if the option was set through a call to
.I set-option! ,
to this procedure's argument.
If the predicate returns #f, a error is signaled with an error
message including
.I post-msg
as described in the previous paragraph.
For example, the predefined option type ``boolean'' is defined as
follows:
.Es
(define-option-type 'boolean
  (lambda (x) (member x '("0" "1")))    "0 or 1"
  (lambda (old new) (string=? new "1"))
  boolean? "a boolean")
.Ee
.Ps
.Pr with-input-from-stream target . forms
.Pr with-output-to-stream target . forms
.Pr with-output-appended-to-stream target . forms
.Pe
.PP
These macros open an input stream (first macro) or output stream to the
specified target and assign it to the current input stream (first
macro) or current output stream.
Then the specified
.I forms
are evaluated, the stream is reassigned its previous value, and
the result of the last sub-expression in
.I forms
is returned.
The macros recur on the primitives
.I open-input-stream ,
.I open-output-stream ,
and
.I append-output-stream ,
respectively.
.Pr skip-lines stop
.PP
Reads input lines using
.I read-line-expand
until either end-of-stream is reached (in this case a warning
is displayed) or a line matching the string argument
.I stop
is encountered.
