24

Various constructions can cause TeX (aka the lion) to go into an infinite loop. The simplest example is \def~{~}~, which defines ~ to expand to itself, then expands it. Now, if we exclude macro expansion, getting a loop is more difficult. With pure TeX, the two shortest methods I found (without macros) are

\everypar{\the\everypar}.
\toksdef~0~{\the~}\the~.

(including the trailing dots); note that the second is not macro expansion, but the expansion of \the\toks0 in hiding. With eTeX, I got down to

\if\unexpanded\fi

which has the additional "quality" of being expandable. Here, \unexpanded could be replaced by \detokenize or \scantokens.

  1. Why does that trigger an infinite loop?
  2. Is there a similarly short TeX constructions which does not use macro expansion (preferably only primitives)?
4
  • perhaps the title should be rather: how to end up with an endless loop or something ... Feb 13, 2012 at 20:49
  • 1
    @FrankMittelbach do you like this title better? I think it would be nice to have an explanation in your answer of what \if\unexpanded\fi does. Feb 13, 2012 at 21:11
  • 3
    "Wenn ein Löwe sprechen könnte, wit könnten ihn nicht verstehen" -- Ludwig Wittgenstein, Philosophische Untersuchungen II, p.190 Feb 13, 2012 at 21:24
  • 1
    Bruno yes like it better, not sure a guy having a runaway lion would recognize ;-) though. Yes sorry, I realized a little too late that I should have answered the first part too. and egreg beat me to it. But then you said you did know it, so why not say it? Feb 13, 2012 at 21:38

3 Answers 3

17

I think to have understood why the infinite loop is triggered. The e-TeX primitive \unexpanded is mostly like \lowercase: it should be followed by <general text>, which in turn is <filler>{<balanced text><right brace>. The important bit is <filler>, which is an arbitrary sequence of \relax commands and spaces (TeXbook, p. 276).

So \unexpanded looks for a { expanding commands on the go and ignoring \relax and space tokens. But it is expandable!

When e-TeX finds \if\unexpanded\fi, the primitive \if triggers the expansion of \unexpanded; but the \fi immediately following causes the insertion of a (frozen) \relax, because of the unfinished conditional, that \unexpanded swallows, leaving an unfinished conditional, which causes the insertion of \relax

Oops, infinite loop. For \detokenize and \scantokens the situation is the same. In original TeX no expandable primitive accepts a <general text> as argument, as far as I know.

The situation is indeed completely different with \if\lowercase\fi, where \lowercase is not expandable, so TeX inserts \relax and the comparison can be performed (and is true).

18

Not extremly short, but a real bug I ran into once when working on xor

\output{\deadcycles=1
        \message{.}%            just to show that something is happening
        \setbox0\box255}
\null
\bye

Of course the output routine didn't set \deadcycles to 1. In reality it was two ORs being called in a row the first setting it to zero.

Just realized that I only answered the second part of the question. By now the first part has already been answered by @egreg.

1
  • I knew your loops would be \output-related :) And sorry, you are right, I should've mentionned I knew the answer. Feb 14, 2012 at 2:12
4

A shorter loop just using classic tex and no macro expansion is

\let\par\relax.\vskip
2
  • A variant of that is \everypar{\par}. Mar 21, 2020 at 21:47
  • @BrunoLeFloch yes but that's more explicitly recursive Mar 21, 2020 at 23:06

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.