[Tex/LaTex] Completely expandable loop macro that also works in tabular

expansionloopsmacros

Since I normally use tikz in my documents, I mostly use the \foreach \x in \list loop to repeat things. However, this famously fails in tabular environments and also everywhere where things must be expanded.

Thus I've written a \for macro which works in tabular, but I'd like to know how to do it better. It's not completely expandable and probably also quite inefficient.

This is okay, because most tables don't have thousands of cells, so performance is usually not an issue. It works in tabular environments to generate multiple columns in a single line or – with some additional care – to generate multiple rows:

Some tables generated using loops


As mentioned above, there are clearly some issues with this loop, but most of the time it "works for me". Still I'd like to improve it, so these are the points that bug me the most:

  1. Each iteration's loop body is completely expanded using \edef. This ensures that the loop counter is expanded to its value during that iteration. However this also requires the user to protect some things using \noexpand, like \pgfmathparse or \\ and \hline in the tables below. Also it would require double protection (triple \noexpand maybe?) for nested loops.
  2. Obviously it's not completely expandable because it uses \def and \edef, so it's not possible to use the loop inside, say, another \edef in order to define a macro that has a regular structure. Although of course it's perfectly possible to \def a macro that expands to a loop invocation.

Now, how could these be solved?

  • For problem 1 it would be great to have a way to expand only the
    loop counter anywhere it's used in the loop body, but leave
    everything else alone. This would eliminate all the \noexpands.
  • For problem 2 I suspect that it could be possible to sacrifice some functionality, like the flexible loop counters and conditions (e.g. \repeat{\i}{5}{some code using \i} instead of the C-like syntax).

However I don't really know how to approach these problems. Can anyone give me some pointers in the right direction? Is it at all possible to achieve both, selective expansion and complete expandability?

Finally, here's the code:

\documentclass{article}

\begin{filecontents*}{myforloop.sty}
\usepackage{pgf}
\usepackage{pgffor}
\usepackage{ifthen}

% Example:
% \for (\i = 0; \i < 10; \i = \i + 1) {
%    some code using \i
% }

\def\for(#1=#2;#3;#4=#5)#6{%
    \def\forloopresult{}%                        Macro to store the result
    \pgfmathtruncatemacro{#1}{#2}%               Set initial value of counter
    \def\forloopinner{%                          Recursive loop macro
        \pgfmathparse{int(#3)}%                  Calculate loop condition
        \ifthenelse{1=\pgfmathresult\relax}{%    if loop condition is true:
            \toks@=\expandafter{\forloopresult}%   Get tokens from result macro
            \edef\forloopresult{\the\toks@#6}%     Append expanded code to result
            \pgfmathtruncatemacro{#1}{#5}%         Set new counter value
            \forloopinner%                         Recurse
        }{%                                      else:
            \forloopresult%                        Expand to result
        }%
    }%
    \forloopinner%                               Start looping
}
\end{filecontents*}
\usepackage{myforloop}

\newcommand\N{5}% Number of loop iterations

\begin{document}
\begin{tabular}{|r|*{\N}{r|}}
    \hline
    $x$:   \for(\x=0; \x<\N; \x=\x+1) {&\x}\\\hline
    $e^x$: \for(\x=0; \x<\N; \x=\x+1) {%
        & \noexpand\pgfmathparse{exp(\x)}\noexpand\pgfmathresult%
    }\\\hline
\end{tabular}

\vspace{1em}

\begin{tabular}{|l|l|}
\hline
$x$ & $e^x$ \\\hline
\for(\x=0; \x<\N; \x=\x+1) {%
    \x & \noexpand\pgfmathparse{exp(\x)}\noexpand\pgfmathresult
    \noexpand\\\noexpand\hline
}
\end{tabular}
\end{document}

Best Answer

There are a number of possible approaches to doing this without the complexity: I'll cover a couple using expl3. First, if you don't mind keeping things non-expandable then you could do something like

\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\cs_new_protected:Npn \For #1#2#3#4
  {
    \int_step_inline:nnnn {#1} {#2} {#3 - 1} {#4}
  }
\cs_new_eq:NN \fpeval \fp_eval:n
\ExplSyntaxOff
\newcommand*\N{5}
\begin{document}

\begin{tabular}{|r|*{\N}{r|}}
    \hline
    $x$:   \For{0}{1}{\N}{&#1}\\
    \hline
    $e^x$: \For{0}{1}{\N}{& \fpeval{round(exp(#1),5)}}\\
    \hline
\end{tabular}
\end{document}

where the code to implement the cells can then be given at point-of-use. You could instead have a two-part setup where the functions are pre-coded: this is then expandable at point-of-use

\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn
\cs_new_protected:Npn \For #1#2#3#4
  {
    \int_step_function:nnnN {#1} {#2} {#3 - 1} #4
  }
\cs_new_eq:NN \fpeval \fp_eval:n
\ExplSyntaxOff
\newcommand*\N{5}
\newcommand\CellNumber[1]{&#1}
\newcommand\CellExp[1]{&\fpeval{round(exp(#1),5)}}
\begin{document}
\begin{tabular}{|r|*{\N}{r|}}
    \hline
    $x$:   \For{0}{1}{\N}\CellNumber\\
    \hline
    $e^x$: \For{0}{1}{\N}\CellExp\\
    \hline
\end{tabular}
\end{document}

In both cases, the \int_step_... functions work much like David's answer: the loop itself is done by expansion. You can't do an assignment to \x or similar and have expandability.

An alternative approach if you are using LuaTeX is to do everything in Lua: the primitive here is expandable so you can use a perhaps more familiar approach (the question suggests a C background).

\documentclass{article}
\newcommand\For[4]{%
  \directlua{%
    for x = #1, (#3 - 1), #2 do
      tex.print(#4)
    end
  }%
}
\newcommand*\N{5}
\makeatletter
\let\Percent\@percentchar
\makeatother
\begin{document}

\begin{tabular}{|r|*{\N}{r|}}
    \hline
    $x$:   \For{0}{1}{\N}{"&" .. x}\\
    \hline
    $e^x$: \For{0}{1}{\N}{"&" .. string.format("\Percent.3f", math.exp(x))}\\
    \hline
\end{tabular}
\end{document}

For the number formatting, we need % which is a bit awkward in TeX: I've given a 'user' name to the LaTeX kernel command \@percentchar (a % which isn't a comment char).

Related Question