4

I am not able to reset a counter to zero using a macro in Plain TeX.

This code uses \ifcase and the counter animalnum to output one of a list of animal names, incrementing the counter each time it is called so that next call will produce the subsequent name in the list. If it has been called more than a maximum number, it outputs an error message.

The command \resetanimalnum is supposed to set the counter back to zero and start the list over, but for some reason it doesn't work as expected.

Why does the counter behave this way and I how can I fix it?

\newcount\animalnum

\def\nextanimal{%                                                                                                                                                                              
  \ifcase\animalnum%                                                                                                                                                                           
    \animalI\or
    \animalII\or
    \animalIII%                                                                                                                                                                                
  \fi%                                                                                                                                                                                         
  \ifnum\animalnum > 2
    \exclaim%                                                                                                                                                                                  
  \fi%                                                                                                                                                                                         
  \advance\animalnum by 1%                                                                                                                                                                     
}

\def\resetanimalnum{\animalnum=0}

\def\animalI   {Lions}
\def\animalII  {Tigers}
\def\animalIII {Bears}
\def\exclaim   {Oh my!}

\nextanimal\ \nextanimal\ \nextanimal\ \nextanimal

\resetanimalnum\nextanimal\ \nextanimal

\bye

enter image description here

musarithmia
  • 12,463
  • I think, \resetanimalnum is the culprit. If you put \nextanimal into the next line, it works –  Jan 14 '16 at 19:13
  • 1
    try \def\resetanimalnum{\animalnum=0\relax} – touhami Jan 14 '16 at 19:13
  • 1
    you can replace \fi% \ifnum\animalnum > 2 with \else – touhami Jan 14 '16 at 19:14
  • @touhami: I just wanted to suggest the same with \relax, but I don't know why this must be this way –  Jan 14 '16 at 19:15
  • @touhami @ChristianHupfer Yes, that works (and yes, \else is better)! But why does TeX not reset the counter without the \relax (or a space)? – musarithmia Jan 14 '16 at 19:17
  • 4
    if tex is trying to determine the value of a counter, it keeps going until it definitively finds something non-numeric. so a space or \relax will stop the parsing of \animalnum=0. – barbara beeton Jan 14 '16 at 19:26
  • @barbarabeeton: Oh, I didn't see your comment while I was answering –  Jan 14 '16 at 19:32
  • 1
    There are a number of references to this on the site. Here's one which includes a reference elsewhere as well: Use of \relax after \ifnum\fi construction – Werner Jan 14 '16 at 19:38
  • “Missing required space” syndrome! – egreg Jan 14 '16 at 20:32
  • @werner the question doesn't ask about relax and the accepted answer doesn't use \relax so i wouldn't have used the gold badge hammer to close it as a dup of a question about relax. – David Carlisle Jan 14 '16 at 21:17
  • @barbarabeeton ^^ – David Carlisle Jan 14 '16 at 21:19
  • @DavidCarlisle: A question doesn't have to ask about something to be a duplicate. Typically people don't know what the problem is, hence their question. It's commonly the answer that identifies a duplicate (see my thought process in How to search for duplicates and handle them?). Also, the accepted answer is just that, it's the answer that helped the OP most, but there could be other answers that answer the question to this one. That's why you may have accepted answers being outscored by other answers. You can vote to re-open, of course. – Werner Jan 14 '16 at 21:22
  • @Werner yes but the referenced question can only have answers about \relax but this one \relax is usually not the best solution so it just isn't a duplicate at all, really. I voted to re-open (actually it opened straight away, didn't know it would do that:-) – David Carlisle Jan 14 '16 at 21:25
  • @DavidCarlisle -- okay, you've given a cogent counter-argument in your answer, and i've removed my comment. (i still think the referenced answer contains an excellent explanation.) – barbara beeton Jan 14 '16 at 22:00

2 Answers2

5

the main culprits are the incorrect use of % after the 1 which prevents the 1 being terminated, and the lack of a space after 0. In some cases you can terminate a number with \relax but that leaves a \relax in the token stream which is not always desirable, a space is absorbed as part of the number.

\newcount\animalnum

\def\nextanimal{%
  \ifcase\animalnum
    \animalI\or
    \animalII\or
    \animalIII
  \fi
  \ifnum\animalnum > 2
    \exclaim
  \fi
  \advance\animalnum by 1
}

\def\resetanimalnum{\animalnum=0 }

\def\animalI   {Lions}
\def\animalII  {Tigers}
\def\animalIII {Bears}
\def\exclaim   {Oh my!}

\nextanimal\ \nextanimal\ \nextanimal\ \nextanimal

\resetanimalnum\nextanimal\ \nextanimal

\bye
David Carlisle
  • 757,742
  • Thank you -- this explains it completely. Is it also true that I should more properly use \else instead of the \ifnum construction? – musarithmia Jan 14 '16 at 20:56
  • @AndrewCashner I saw that comment on the other answer but they are not equivalent so it depends what you want if you go ifcase or, or, or ...else fiexclaim then you either get an animal or exclaim. if you go ifcase of or or fi \ifnum exclaim fi then you get an animal and then exclaim if the test number is bigger than 2 so you may get an animal and an exclaim – David Carlisle Jan 14 '16 at 21:13
2

It's stated (somehow) in the TeX by Topic book (page 66)

TeX can expand too far in case of counter evaluation and will try find something non-numerical, i.e. it might absorb and expand the next statement \nextanimal which is a fault here.

If \relax is inserted, this can be stopped. (The \relax is not mentioned on that page, but the expansion can be stopped with \relax then.

A one-time solution would be to place a newline between \resetanimalnum and the next call to \nextanimal, but this might get tedious.

\newcount\animalnum

\def\nextanimal{%
    \ifcase\animalnum%                                                                                                                                      
    \animalI\or
    \animalII\or
    \animalIII%                                                                                                                                             
    \fi%
    \ifnum\animalnum > 2
    \exclaim%
    \fi%
    \advance\animalnum by 1%
}

\def\resetanimalnum{\animalnum=0\relax}%

\def\animalI   {Lions}
\def\animalII  {Tigers}
\def\animalIII {Bears}
\def\exclaim   {Oh my!}

\nextanimal\ \nextanimal\ \nextanimal\ \nextanimal

\resetanimalnum\nextanimal\ \nextanimal

\bye

enter image description here