Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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.

share|improve this question
add comment

1 Answer

up vote 9 down vote accepted

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.

share|improve this answer
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 '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
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.