Friday, December 31, 2010

Forcing full expansion

Unfortunately, the current version of pdfTeX is lacking a \expanded primitive that would fully expand its argument, similar to the full expansion that happens for the replacement text of an \edef. However, there is a trick that will keep expanding macros until it reaches an unexpandable token.

This trick depends on an obscure aspect of TeX's grammar. Namely, wherever TeX expects a number, there are 4 different ways to give it a literal (and several more ways besides): (1) as a decimal number 1234, (2) as an octal number '31, (3) as a hexadecimal number "AA55, or (4) as the character code of a character token `X. For any of those, the literal can be preceded by an optional string of minuses (each one negates the number that is to follow so --5 is the same as 5) and can be followed by an optional space token. For example,
contains both the optional minus and the optional space (because the newline was turned into a space token).

We can use case (4) along with the fact that \romannumeral expands to nothing when the following number is nonpositive to produce a full expansion:
\foo will expand until there is a single nonexpandable token. Why does this work? Well, -`X is a complete number, namely -88. Since it is negative, \romannumeral-`X expands to nothing, as we would expect. But that leaves \foo. Remember that optional space in the grammar? It turns out that TeX will now expand tokens until it finds something unexpandable in its search for that optional space. The downside to this is that if \foo expands to something that starts with a space, the space will disappear.

As an example of this hack, consider the \hexnumber macro. This macro does not produce any nonexpandable tokens until it is ready to produce all of them. This makes it a fine candidate for the \romannumeral hack.
If we examine TeX's output, the difference is immediately obvious.
> \foo=macro:
> \bar=macro:
->\ifnumcomp {37}<0{}{\hn@i {37}{}}.

Thanks to Joseph Wright for pointing this out.

1 comment:

  1. I have used this \romannumeral trick for other purposes too.