11

My \john command is defined like so:

\def\john{\DontExpandMe}

I would now like to repeatedly change its definition, to keep adding some extra stuff on the front.

\foreach\i in {ape,bat,cow,dog} {
  \xdef\john{\i,\unexpanded{\john}}
  \show\john
}

My intention is that the \show\john command at each iteration should result in:

\john=macro: -> \DontExpandMe.
\john=macro: -> ape,\DontExpandMe.
\john=macro: -> bat,ape,\DontExpandMe.
\john=macro: -> cow,bat,ape,\DontExpandMe.
\john=macro: -> dog,cow,bat,ape,\DontExpandMe.

That is to say, I would like \john to be "partially expanded" in some sense. But I can't make that work. I have tried the following

  • If I use an \xdef, then the whole command is expanded, including the \DontExpandMe part.
  • If I just use a \gdef, then the \i is not expanded.
  • If I use \xdef with \unexpanded{...} around \john (as I have in my current code) then I get \john=macro: -> ape,\john. and \john=macro: -> bat,\john. and so on.

Here is my code.

\documentclass{article}
\usepackage{pgffor}
\begin{document}
  \def\john{\DontExpandMe}
  \show\john
  \foreach\i in {ape,bat,cow,dog} {
    \xdef\john{\i,\unexpanded{\john}}
    \show\john
  }
\end{document}
12

The line in question is:

\xdef\john{\i,\unexpanded{\john}}

\i should expanded (full/once?) and \john should be expanded once

Partial expansion with \xdef and \unexpanded:

Before \unexpanded reads the open curly brace, it is in expanding mode for gobbling spaces. Therefore it can be used to sneak in a \expandafter:

\xdef\john{\i,\unexpanded\expandafter{\john}}

(Without this trick, two \expandafter would be needed:

\xdef\john{\i,\expandafter\unexpanded\expandafter{\john}}

The same can also be achieved without e-TeX by using a token register:

\toks0=\expandafter{\john}% similar trick as above to minimize the number of \expandafter
\xdef\john{\i,\the\toks0}

The contents of the token register is not further expanded inside \edef.

One expansion step for \i and \john:

  • Same as above, but for \i, too:

    \xdef\john{\unexpanded\expandafter{\i},\unexpanded\expandafter{\john}}
    
  • \expandafter orgy with \gdef:

    \expandafter\expandafter\expandafter\gdef
    \expandafter\expandafter\expandafter\john
    \expandafter\expandafter\expandafter{%
      \expandafter\i\expandafter,\john
    }
    

    First \john is expanded once, then \i.

  • Thank you Heiko. (+1 for the \expandafter orgy!) – John Wickerson May 30 '13 at 9:45
8

There are many ways to do this; the easiest, but riskier, is to use \xdef:

\def\john{\DontExpandMe}
\show\john
\foreach\i in {ape,bat,cow,dog} {%
  \xdef\john{\i,\unexpanded\expandafter{\john}}%
  \show\john
}

Why riskier? Try with \textbf{ape} in the list to see the effect: commands that don't survive \edef will make the code die horribly. A better choice would be expanding also \i only once:

  \xdef\john{\unexpanded\expandafter{\i},\unexpanded\expandafter{\john}}%

Using token registers might be an option:

\def\john{\DontExpandMe}
\show\john
\foreach\i in {ape,bat,cow,dog} {%
  \toks0=\expandafter{\i}%
  \toks2=\expandafter{\john}%
  \xdef\john{\the\toks0,\the\toks2}%
  \show\john
}

Tokens delivered by \the<token registers> are not subject to further expansion in an \edef or \xdef.

A macro in LaTeX3 language:

\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand{\prependreverselist}{mm}
 {% #1 is the macro to extend; #2 is the list
  \clist_map_inline:nn { #2 }
   {
    \tl_put_left:Nn #1 { ##1 , }
   }
 }
\ExplSyntaxOff

\newcommand{\john}{\DontExpandMe}

\prependreverselist{\john}{ape,bat,cow,dog}

\show\john
  • Cool, thanks :-). Why do you use \toks0 and \toks2 rather than, say, \toks0 and \toks1? – John Wickerson May 30 '13 at 9:39
  • I'd use a group for the toks (see my answer, which I'll probably remove). – Joseph Wright May 30 '13 at 9:40
  • 1
    @JohnWickerson By convention, even-numbered scratch registers should be used locally and odd-numbered ones globally. So \toks1 is not appropriate here. – Joseph Wright May 30 '13 at 9:40
  • 1
    @JosephWright \foreach does each cycle in a group, so adding one is not necessary. – egreg May 30 '13 at 9:44
6

At the TeX level, this can be done at least couple ways depending on whether we assume e-TeX is available. The classical way, with no e-TeX, is to use a token register for the definition:

\long\def\addtoclist#1#2{%
  \begingroup
    \toks@\expandafter{#1}%
    \toks2{#2}%
    \edef#1{\the\toks2,\the\toks@}%
  \expandafter\endgroup
  \expandafter\def\expandafter#1\expandafter{#1}%
}

where I've been cautious and assumed that the new material (#2) should not expand at all. The way that the above works is that TeX only expands token registers ('toks') once inside an \edef, so I can be sure no further expansion will occur. I've used two temporary toks: \toks@ (which is \toks0 addressed by name), and \toks2 (addressed by number). The group means I won't affect any value already stored in these.

With e-TeX, we don't need the group or toks assignments

\protected\long\def\addtoclist#1#2{%
  \edef#1{\unexpanded{#2},\unexpanded\expandafter{#1}}%
}

where the \unexpanded primitive has behaviour similar to a toks: you can expand the material to be assigned using \expandafter just before the {.

(In both cases, you really need to check for an empty initial definition, but that is not the key point here.)

4

With the help of cgnieder's comment, I have fixed my code as follows:

\documentclass{article}
\usepackage{pgffor}
\usepackage{etoolbox}
\begin{document}
  \def\john{\DontExpandMe}
  \show\john
  \foreach\i in {ape,bat,cow,dog} {
    \xdef\john{\i,\expandonce{\john}}
    \show\john
  }
\end{document}

And with the help of cgnieder's other comment, I have shortened my code as follows:

\documentclass{article}
\usepackage{pgffor}
\begin{document}
  \def\john{\DontExpandMe}
  \show\john
  \foreach\i in {ape,bat,cow,dog} {
    \xdef\john{\i,\unexpanded\expandafter{\john}}
    \show\john
  }
\end{document}
  • 2
    Actually you don't need etoolbox for this. The definition of \expandonce is simple: \newcommand{\expandonce}[1]{\unexpanded\expandafter{#1}} – clemens May 30 '13 at 9:17

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.