The previous sections described our document model and the techniques used to construct high-level representations given documents that conform to this model. The TEX macro facility [Knu84, Knu86] allows the definition of new markup commands, making (LA)TEX extensible. Macros permit the author to abstract away layout details when writing the document. To give an example, the command \kronecker is not present in (LA)TEX. An author can extend (LA)TEX by defining
\newcommand{\kronecker}{\raisebox{1pt}{\:\otimes\:}}
and then write
$ A \kronecker B$
The new definition for \kronecker has extended the markup language. LATEX [Lam86] itself is a good example of how TEX macros can be used to implement a language for encoding document structure.
The presence of user-defined macros in documents presents an interesting challenge for a system like AS TE R. Our goal is to handle books and technical documents written in (LA)TEX, so recognizing the extended logical structure introduced by the definition of new macros is therefore essential. In general, macro expansion can perform any arbitrary computation permitted by the TEX language. Hence, it is impossible to directly translate the macro expansion into an audio rendering. The TEX primitives are visual layout operators, and translating a TEX macro directly into an audio rendering rule would imply a one-to-one mapping between the visual and audio rendering.
As explained in Section 1.1, visual renderings are attuned to a two-dimensional display, and audio renderings need to be attuned to the features of an auditory display. Further, expanding a TEX macro loses structural information; when all macros in a document have been expanded, only the visual layout remains.
The first step in solving this problem is to represent user-defined macros in our high-level document model. Producing audio renderings of such instances will then be equivalent to rendering any other object present in the model.
Macro definitions introduce new object types. Thus, defining \kronecker is equivalent to adding object kronecker to the set of objects present in the document model. A macro definition in (LA)TEX has two parts; the first part declares the macro and its number of arguments; the second part specifies how instances of this macro call are to be displayed. Translating this to the object-oriented model, the first part of the macro introduces a new object type; the second part is a rendering rule for instances of this object.
We first describe how the recognizer is extended to handle instances of the new object introduced by a macro definition. We specify the following information about the new macro and the associated object type:
This information is supplied by calling Lisp macro define-text-object. We illustrate this in the case of \kronecker in Figure 2.2 on page 49. The Lisp macro itself will be described in Section 2.4.1.
Note that our recognizer has more information about the new macro than TEX. This is consistent with the fact that our internal representation is richer than the TEX representation, as described in Section 2.2.
To summarize, we model a macro as:
A call to the macro in the document creates an object of the type introduced by that macro.
To continue with the example of kronecker, given
$A \kronecker B$
LISPIFYconverts it to
(inline-math "A" (cs "kronecker" ) "B" )
LISPIFYmarks the (LA)TEX macro instance as a a control sequence (cs). The recursive descent recognizer performs the following steps on encountering calls to macro \kronecker:
Function kronecker-expand constructs an instance of object kronecker. At this point, this instance of object kronecker has its children set to null. The input list is thus converted to a list containing three math objects shown below.
In the above list, only the types of the objects are shown. This list is now processed by function inf-to-pre to produce the quasi-prefix form. Since class kronecker is a subclass of binary-operator with the same precedence as multiplication, the result is a tree with kronecker as the root and with two children, one each corresponding to A and B.
In the above, A and B may be arbitrarily complex pieces of (LA)TEX markup; the recursive nature of the recognition algorithm will set the children of object kronecker to the operands in their processed form. For example, we can now build an internal representation for the following equation:
which would be written in (LA)TEX as
\[ (A \kronecker B){T} = A{T} \kronecker B{T} \]
Lispmacro define-text-object is quite involved. In this overview, we use the call shown in Figure 2.2 on page 49 to illustrate the various steps. A call to macro define-text-object performs the following steps:
(defclass kronecker (binary-operator)
((contents :initform nil :initarg :contents
:accessor contents))
(:documentation "class kronecker
corresponding to document macro kronecker"))
If the corresponding macro takes n arguments, i.e., number-args = n in Figure 2.2 on page 49, then the new object type is defined to have a slot arguments. This slot will hold a list containing the result of processing the n arguments of the (LA)TEX macro call.
(defun kronecker-expand (rest arguments)
"automatically generated processing function"
(assert (= (length arguments) 0)
nil "wrong number of arguments")
(let⋆ ((self (make-instance 'kronecker))
(processor (if (math-p self)
#'process-argument-as-math
#'process-argument-as-text)))
(unless (= number-args 0)
(setf (arguments self)
(loop for arg in arguments collect
(funcall processor arg)))) self))
where variable number-args is bound to the value of the number-args keyword argument in the lexical scope in which the function definition is evaluated. Functions process-argument-as-math and process-argument-as-text apply the recursive descent parser to their argument; the former parses mathematical content, the latter processes plain text.
(defmethod argument ((n integer) (kronecker kronecker))
"automatically generated argument accessor"
(assert (<= n (length (arguments kronecker ))) nil
"not that many arguments.")
(elt (arguments kronecker) (- n 1)))
We take advantage of the generic dispatch provided by CLOS and define an instance of the above method with its arguments reversed this avoids having to remember the order of arguments to function argument when writing rendering rules.
(define-precedence "kronecker" :same-as 'multiplication)
(define-tex-macro "kronecker" 0 'kronecker-expand)
This specifies that the macro being defined takes 0 arguments and calls to it should be processed using function kronecker-expand.
The call to the Lisp macro define-text-object shown in this example produces 123 lines of Lisp code.
Our system of rendering rules will be described in detail in Chapter 4. Such rules are written in AFL, our language for audio formatting, described in Chapter 3. Here, we show a small example of such a rendering rule for a user-defined macro. In the following, we use CLOS generic function read-aloud, described in Chapter 4. For the present, let us assume that function read-aloud executes the necessary actions to render its argument. After executing the appropriate call to define-text-object for the (LA)TEX macro \inference, which is defined as
\newcommand\inference[2]{\frac{#1}{#2}}
to render instances of calls to \inference, we can define
(defmethod read-aloud((inference inference))
"Sample read-aloud method for object inference.
Demonstrates how macro arguments are accessed when rendering. "
(read-aloud (argument 1 inference))
(read-aloud "implies")
(read-aloud (argument 2 inference)))
If we wished to produce a rendering that inverts the order in which the arguments to macro \inference are rendered, we would define:
(defmethod read-aloud((inference inference))
"Renders inference with arguments reversed."
(read-aloud "We know that ")
(read-aloud (argument 2 inference))
(read-aloud "because")
(read-aloud (argument 1 inference)))
A flexible method for switching among different rendering rules to obtain different audio views of the same object is described in Section 4.1.
As outlined in Section 2.1, the document model in LATEX can be extended by defining new environments. Typically, a new environment is defined to achieve specific kinds of layout, e.g., for typesetting conjectures and lemmas. We treat such environment definitions as adding new objects to the document model. The approach used is very similar to handling user-defined macros, though somewhat simpler. Object new-environment is used as a base class for all user-defined environments. The principal difference between objects introduced by user-defined macros and user-defined environments is that new-environment objects can be labeled and later cross referenced. The Lisp macro define-new-environmentdoes the necessary bookkeeping involved in tracking such cross-referenceable objects. These properties are provided by the class definition for object new-environment.