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)?
  • perhaps the title should be rather: how to end up with an endless loop or something ... – Frank Mittelbach Feb 13 '12 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. – Bruno Le Floch Feb 13 '12 at 21:11
  • 3
    "Wenn ein Löwe sprechen könnte, wit könnten ihn nicht verstehen" -- Ludwig Wittgenstein, Philosophische Untersuchungen II, p.190 – Brent.Longborough Feb 13 '12 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? – Frank Mittelbach Feb 13 '12 at 21:38

3 Answers3

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.

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).

egreg
  • 1,121,712
5

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

\let\par\relax.\vskip

or

\let

\noindent

\vskip

David Carlisle
  • 757,742