At the beginning of the unravel package source code we can find some cryptic catcode settings:

\begingroup\let\c\catcode\fam32\c\fam10\advance\fam5\c\fam14\c45 12 %
\c54 12\c55 12\c56 12\c57 12\c58 11\c95 11\c104 11\c106 11\c107 11 %
\c113 11\c115 11\c119 11\c120 11\c121 11\c122 11\endlinechar-1 %
\expandafter\ifx\csname unravel\endcsname\relax
\c84 11\c88 11\c35 6\c123 1\c125 2\c62 12\c61 12\c43 12 %

The package documenatation explains what happens here:

Catcode settings. In a group, set \c to be a synonym of \catcode for short, set the catcode of space to be 10 (using \fam to avoid needing a space or an equal sign to separate the two integer arguments of \catcode) and that of % to be 14 (using \fam again to avoid needing the digit 7 to have catcode other: we need the digit 5 anyway in two steps). Then make -, 6, 7, 8, 9 other (we must assume that 0 through 5 are already other), and make :, _, h, j, k, q, s, w, x, y, z letters (other lowercase letters already need to be letters in the rest of the code). Make sure there is no \endlinechar. We are finally ready to safely test whether the package has already been loaded and bail out in case it has.

What is still not clear to me is the exact purpose of that. The package seems to do that to check if the package has already been loaded. Therefore some other and some letter characters are reset to their normal catcodes in a tricky way. As far as I understand, it must still rely on a lot of other characters having their normal catcodes.

Perhaps I'm missing something here, but if that's the case, doesn't that make the whole catcode changing pretty much useless? The chances that half of the digits and letters still have the correct catcodes while the other half needs to be reset seem very low to me. Can someone explain why this is necessary or done?


\fam is an internal integer register, so \fam32 sets it to 32; then \advance\fam5 sets it to 37. Where's \fam used? In math mode it is initialized to -1, but it can be set to one of the math families (0 to 15) in order to change the alphabet of symbols with type 7 (letters, usually).

If we unravel the code, we get

\catcode 32=10 % ensure space has catcode 10
\catcode 37=14 % ensure % is comment (but without using the digit 7
% the digits from 0 to 5 have been used, assume they have catcode 12
% set other catcodes to their standard value
\catcode 45=12 % hyphen, necessary for -1
\catcode 54=12 % 6
\catcode 55=12 % 7
\catcode 56=12 % 8
\catcode 57=12 % 9
\catcode 58=11 % colon 
\catcode 95=11 % underscore
\catcode 104=11 % h
\catcode 106=11 % j
\catcode 107=11 % k
\catcode 113=11 % q
\catcode 115=11 % s
\catcode 119=11 % w
\catcode 120=11 % x
\catcode 121=11 % y
\catcode 122=11 % z
\endlinechar-1 %
\expandafter\ifx\csname unravel\endcsname\relax
  % \unravel is not defined
  % assume unravel.sty has already been loaded, bail out
  \endinput\expandafter\endgroup\fi % \fi needs to be on the same line as \endinput
\catcode 84=11 % T
\catcode 88=11 % X
\catcode 35=6 % #
\catcode 123=1 % {
\catcode 125=2 % }
\catcode 62=12 % >, necessary for \ifnum
\catcode 61=12 % <, necessary for \ifnum
\catcode 43=12 % +

There's other code following:

\expandafter\ifx\csname numexpr\endcsname\relax
\errmessage{unravel requires \numexpr from eTeX}
\expandafter\ifx\csname protected\endcsname\relax
\errmessage{unravel requires \protected from eTeX}
\expandafter\ifx\csname currentgrouplevel\endcsname\relax\else
\ifnum\currentgrouplevel>1 \errmessage{unravel loaded in a group}

The first \ifx is meant to bail out if e-TeX is not available. Note that the characters that have been set to their standard catcodes are used in this part of the code. Next a check that the group level is 1, otherwise unravel would have been loaded inside a group and won't work.

As far as I can see, the package makes the minimal possible assumptions about category codes. It needs that \ has category code 0, abcdefgilmnoprtuv have category code 11 (for \begingroup, \let, \c, \catcode, \fam and \advance) and the digits 012345 have category code 12. If not, there's not much that can be done. Using \bgroup instead of \begingroup would not save anything; besides, \bgroup is defined, whereas \begingroup is a primitive.

Of course, if some package loaded earlier than unravel has redefined \begingroup, \let, \catcode or \fam we're doomed.

Other programming languages refuse to redefined important keywords or function names, TeX doesn't and is happy to obey a programmer who wants to shoot their own foot.

One can imagine having to make less assumptions if the LaTeX kernel had some feature for setting category codes, say a macro \aaaaaaaaaaaaaaaaaaaaaaaa, needing just \ having category code 0 and a having category code 11. However, it's also possible to imagine some devious user who sees that macro in the code and redefines it.

  • Do you have any idea why this is done? The link in the comments above suggests that other package writers do the same. To me this seems more like code-golfing than of any actual pratical use.
    – siracusa
    Nov 19 '19 at 18:15
  • @siracusa Paranoia? ;-)
    – egreg
    Nov 19 '19 at 20:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.