7

I have a macro that contains a loop using \@for and it works until I try to write its output to a file. The following document shows the problem.

\documentclass{article}
\makeatletter
\newwrite\my@out
\AtBeginDocument{\immediate\openout\my@out my.tmp}
\AtEndDocument{\immediate\closeout\my@out}

\def\plist#1{%
  \@for\tmpkey:=#1\do{blah }
}
\def\mymacro{%
  \plist{a,b,c}% this works, appears in document.
  \immediate\write\my@out{hello}% make sure our file is okay
  \immediate\write\my@out\plist{a,b,c}% does not work!
}
\makeatother
\begin{document}
  \mymacro
\end{document}

When I comment out the last line of \mymacro, the code works and the words `blah blah blah' appear in the pdf. So the logic is not too far off.

However, I must be running into an expansion timing problem when I write to the external file. I get the error message:

! Missing number, treated as zero.
<to be read again>
                 \def
l.21 \mymacro

I can use another package if I have to. I'm using the LaTeX2e standard \@for in an effort to minimize the number of dependencies the package will have.

I've tried a lot of permutations using \expandafters and \edef, but my understanding just doesn't go far enough.

1 Answer 1

9

Your problem is that \@for is not 'expandable' (it contains an assignment, which is always a killer). The \write operation is an 'expansion context', like \edef, and so you need a fully-expandable loop. Here, I've taken the one from LaTeX3 and recoded it for LaTeX2e:

\documentclass{article}
\makeatletter
\newwrite\my@out
\AtBeginDocument{\immediate\openout\my@out my.tmp}
\AtEndDocument{\immediate\closeout\my@out}
\long\def\clist@map#1%
  {%
    \expandafter\ifx\expandafter\relax\detokenize{#1}\relax
      \expandafter\@gobble
    \else
      \expandafter\clist@map@aux
    \fi
      {#1}%
  }
\long\def\clist@map@aux#1#2%
  {\clist@map@loop#2#1,\q@tail,\q@stop}
\long\def\clist@map@loop#1#2,%
  {%
    \ifnum\pdfstrcmp{\unexpanded{#2}}{\noexpand\q@tail}=\z@
      \expandafter\clist@map@loop@stop
    \fi
    #1{#2}%
    \clist@map@loop#1%
  }
\long\def\clist@map@loop@stop#1\q@stop{}
\def\plist#1{\clist@map{#1}\plistaux}
\def\plistaux#1{blah }
\def\mymacro{%
  \plist{a,b,c}% this works, appears in document.
  \immediate\write\my@out{hello}% make sure our file is okay
  \immediate\write\my@out{\plist{a,b,c}}% does not work!
}
\makeatletter
\makeatother
\begin{document}
  \mymacro
\end{document}

This uses \pdfstrcmp, which is only available for pdfTeX. To use the code with XeLaTeX and LuaLaTeX, load the pdftexcmds package and use \pdf@strcmp in place of \pdfstrcmp.

The general idea to the code is to set a loop with a know end marker which will not occur in the list: the marker here is \q@tail. The \pdfstrcmp primitive lets us find that using a 'text' search in an expandable manner. (It is possible to do the same without the primitive, but it's tedious.)

A version using expl3 just to provide the loop would read

\documentclass{article}
\usepackage{expl3}
\makeatletter
\newwrite\my@out
\AtBeginDocument{\immediate\openout\my@out my.tmp}
\AtEndDocument{\immediate\closeout\my@out}
\ExplSyntaxOn
\def\plist#1{\clist_map_function:nN{#1}\plistaux}
\ExplSyntaxOff
\def\plistaux#1{blah }
\def\mymacro{%
  \plist{a,b,c}% this works, appears in document.
  \immediate\write\my@out{hello}% make sure our file is okay
  \immediate\write\my@out{\plist{a,b,c}}% does not work!
}
\makeatletter
\makeatother
\begin{document}
  \mymacro
\end{document}

(A full expl3 solution would use the LaTeX3 file mechanisms too.)

In both cases, notice that I've defined \plistaux, which pre-defines the action of \plist. This is needed to keep things expandable.

2
  • 2
    Of course, an alternative would be to do the write operation inside the loop using a dedicated version of the \plist macro. However, that does not illustrate the reason for the issue quite so well.
    – Joseph Wright
    Nov 15, 2011 at 21:13
  • Thanks. I didn't think about \@for being unexpandable. I think I am in dangerous waters here and need to rethink my original problem. Anything but simple situations involving \expandafter makes my head spin. thanks again!
    – Tim A
    Nov 16, 2011 at 15:44

You must log in to answer this question.

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