expansion of loop macro

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

-

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.

-
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 '11 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 '11 at 15:44