11

Reading http://profs.sci.univr.it/~gregorio/introtex.pdf, I have come across the definition of \newif on page 75:

\def\newif#1{\@ifdefinable{#1}{\@newif#1}}
\def\@newif#1{\count@\escapechar \escapechar\m@ne
\expandafter\expandafter\expandafter
\def\@if#1{true}{\let#1=\iftrue}%
\expandafter\expandafter\expandafter
\def\@if#1{false}{\let#1=\iffalse}%
\@if#1{false}\escapechar\count@} % the condition starts out false
\def\@if#1#2{\csname\expandafter\if@\string#1#2\endcsname}
{\uccode‘1=‘i \uccode‘2=‘f \uppercase{\gdef\if@12{}}} % ‘if’ required

I'm trying to understand how it works by following its expansion. So imagine I type \newif\iffoo.

  1. The first expansion should be

    \@ifdefinable{\iffoo}{\@newif\iffoo}
    

    therefore checking if there is an already defined \iffoo and then, if there is none, passing \iffoo to \@newif. And that's where my problems begin.

  2. \@newif should expand into:

    \count@\escapechar \escapechar\m@ne
    \expandafter\expandafter\expandafter
    \def\@if\iffoo{true}{\let\iffoo=\iftrue}%
    \expandafter\expandafter\expandafter
    \def\@if\iffoo{false}{\let\iffoo=\iffalse}%
    \@if\iffoo{false}\escapechar\count@
    

    Now \count@ is a counter (\countdef\count@=255) and so is \m@ne (\countdef\m@ne=22 \m@ne=-1), and \escapechar contains the code point of \, being defined as \escapechar=\\ (I can't put the backtick after the = because the site's markup causes trouble). So \count@\escapechar, I guess, gives \count@ that code, and \escapechar\m@ne makes \escapechar -1. That's for later, in \if@. Then we have the sequence of \expandafters. Now the first one makes TeX skip the second one, and see the third one, which makes it skip \def and expand \@if, which we will get to later; then TeX (I guess) remembers it had skipped through an \expandafter, go back to it and execute it; this means \def<expansion of \@if\iffoo{true}>{\let\iffoo=\iftrue} is delayed, and we enter a new sequence of \expandafters; the first one is skipped and stored away due to the left-over \expandafter which delayed the just-mentioned \def; so the second one is executed, the third one consequently stored away, and so we get a \def executed which should redefine \if@ but that can't be! So the definition of the expansion of \@if\iffoo{true} as \let\iffoo=\iftrue takes place before the second \expandafter of the first sequence is executed, but why? Well in that assumption we get to expanding the second \@if and defining its expansion as \let\iffoo=\iffalse, then we have one/two \expandafters; if there are two, they should elide, making me wonder why bother putting three both times if one both times is sufficient, whereas if there is one we get \count@ and then \escapechar, so we have a useless instruction which is the repetition of one at the beginning, \count@\escapechar. So there must be two, right? And eliding, right? So why bother putting three?

  3. And now the \@if. We have, as said above:

    \@if\iffoo{true}
    

    so the expansion should be:

    \csname\expandafter\if@\string\iffootrue\endcsname
    

    The \expandafter leaves \if@ as it is, and expands the \string, which makes \iffootrue become iffootrue, since \escapechar is -1<0; so we get to:

    \csname\if@iffootrue\endcsname
    

    right?

  4. Finally, the line {\uccode‘1=‘i \uccode‘2=‘f \uppercase{\gdef\if@12{}}} makes i the uppercase of 1 and f the uppercase of 2, probably to make sure \if@ in the \uppercase doesn't get taken for \if@if, since we're in \makeatletter, but split off because 1 and 2 cannot be put in a control sequence and are then turned into i and f by \uppercase. So here's the \if@: it gets expanded to {}, leaving just:

    \csname footrue \endcsname
    

    (or foofalse). So we understand the actual reason of the \expandafter in the \csname…\endcsname: to delay the expansion of the \if@, which is global since it's \gdef, so is defined in a group to leave the \uccodes local but is set to global to be used back up in those \csname…\endcsnames.

  5. Finally, of course, we want \escapechar back to its original value, which was stored in \count@, and is restored at the end of everything.

After all these reflections, I seem to have been left with only one question mark: why three \expandafters per \if@ control sequence definition? So the question finally is:

Is there anything wrong in what I have said, what is it, and why are there those three \expandafters up there?

1 Answer 1

9

Let's look at

\expandafter\expandafter\expandafter
\def\@if\iffoo{true}{\let\iffoo=\iftrue}%

recalling the definition of \@if:

\def\@if#1#2{\csname\expandafter\if@\string#1#2\endcsname}

The first \expandafter expands the third one, which in turn causes the expansion of \@if, which has two arguments; in this case they are

#1<-\iffoo
#2<-true

where I use the customary way TeX uses for showing what argument are assigned. So we're left with

\expandafter\def\csname\expandafter\if@\string\iffoo true\endcsname

Now the remaining \expandafter triggers the expansion of \csname that, I recall, does exhaustive expansion until finding the matching \endcsname. So, in order to know what control sequence name will be produced, we have to follow the expansion of all tokens after \csname.

The first token is \expandafter, that causes the expansion of \string; since \escapechar is -1, we're left with

\if@ iffootrue\endcsname

Because of its definition, \if@ removes i and f and leaves footrue; so we have

\def\footrue{\let\iffoo=\iftrue}

It's now the same procedure for defining \foofalse.

The definition of \if@ is indirect, because it must be followed by i and f of category code 12, for all character tokens produced by \string have category code 12 (except spaces).

You're wrong in thinking that TeX remembers having expanded \expandafter: this token just disappears after triggering the expansion of the second token after it. Also it's incorrect to say that two of them elide each other.


Note 1: in a couple of places there's an apparent space, where there isn't really one; it's just for distinguishing one token from another. Notably there is one in \string\iffoo true and one in \if@ iffootrue.

Note 2: this is not the definition of \newif in the LaTeX kernel, where a simplified version is used, which doesn't check if the control sequence after \newif has a name starting with if (as discussed in the book when \newif is described).


Just for fun, here's how one could implement \newif in expl3. Of course this is only an exercise to show that one can get “clearer” programming.

\documentclass{article}
\usepackage{xparse,l3regex}

\ExplSyntaxOn
\NewDocumentCommand{\xnewif}{ m }
 {
  \xnewif_newif:N #1
 }

\tl_new:N \l__xnewif_name_tl

\msg_new:nnnn { xnewif } { bad~input }
 { #2 }
 { The~argument~\token_to_str:N #1 is~#2;~fix~it }

\cs_new_protected:Npn \xnewif_newif:N #1
 {
  \cs_if_exist:NTF #1
   {% the control sequence is already defined, stop
    \msg_error:nnnn { xnewif } { bad~input } { #1 } { already~defined }
   }
   {% the control sequence is undefined, go on
    \__xnewif_newif_do:N #1
   }
 }

\cs_new_protected:Npn \__xnewif_newif_do:N #1
 {
  \tl_set:Nx \l__xnewif_name_tl { \cs_to_str:N #1 }
  \regex_replace_once:nnNTF { \A if } { } \l__xnewif_name_tl
   {% the name of the control sequence starts with `if', go on
    \cs_gset_eq:NN #1 \if_false: % start globally false
    \cs_new:cpn { \l__xnewif_name_tl true } % define \if...true
     {
      \cs_set_eq:NN #1 \if_true:
     }
    \cs_new:cpn { \l__xnewif_name_tl false } % define \if...false
     {
      \cs_set_eq:NN #1 \if_false:
     }
   }
   {% the name of the control sequence doesn't start with `if', stop
    \msg_error:nnnn { xnewif } { bad~input } { #1 } { not~starting~with~`if' }
   }
 }

\ExplSyntaxOff

% Test
\xnewif\iffoo
\show\iffoo
\footrue
\show\iffoo
\foofalse
\show\iffoo

\xnewif\isss
\xnewif\iffoo

Here's the output on the terminal

> \iffoo=\iffalse.
l.50 \show\iffoo

? 
> \iffoo=\iftrue.
l.52 \show\iffoo

? 
> \iffoo=\iffalse.
l.54 \show\iffoo

? 

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! xnewif error: "bad input"
! 
! not starting with `if'
! 
! See the xnewif documentation for further information.
! 
! For immediate help type H <return>.
!...............................................  

l.56 \xnewif\isss

? 

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
! xnewif error: "bad input"
! 
! already defined
! 
! See the xnewif documentation for further information.
! 
! For immediate help type H <return>.
!...............................................  

l.57 \xnewif\iffoo
1
  • I like how the word “clearer” is in quotation marks ;)
    – morbusg
    Commented Apr 17, 2014 at 5:06

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .