# pgf's \foreach macro

Please is there a way of turning the group scoping of \foreach off? The following problem illustrates my difficulty. Without global assignment the aim is not achieved, but global assignments might take things too far.

Andrew Stacey: This is a genuine question.

\documentclass{article}
\usepackage{pgffor}
\begin{document}

\newcount\nra\newcount\nrb
\def\print{%
\par{\tt\tabskip=20pt
\halign to\hsize{##&##&##\cr
\number\nra-\number\nrb:& \xa/\xb & \ya/\yb\cr
}}%
}

\begingroup
\nrb=10
Value of {\tt\string\nrb}' within the current group is \number\nrb.
\par\medskip
\xdef\savednrb{\the\nrb}
\foreach \xa\xb in {a1/a2,b1/b2,c1/c2,d1/d2}{%
\nrb=0
\foreach \ya\yb in {s1/s2,t1/t2,u1/u2,v1/v2}{%
\ifnum\nrb>1 \breakforeach\fi
\print
}%
}
\endgroup

\par\medskip
Value of {\tt\string\nrb}' outside the latest group is \number\nrb, not \number\savednrb.

\end{document}

-
I'm a little disappointed that this is a genuine question. I was really hoping that you'd have an answer to this that you were going to reveal to us. You see, it's something that I've pondered as well ... indeed, even asked about: tex.stackexchange.com/q/15204/86 – Loop Space May 5 '12 at 19:31
It's difficult to answer the question. Clearly it is possible to design a for-each macro that does not use grouping, for example the standard LaTeX \@tfor. Presumably other uses of the pgf for macro expect the groups so just using a different name (or an existing alternative such as \@tfor) id preferable to redefining the macro with the same name. – David Carlisle May 5 '12 at 21:27
Thanks to all. The \foreach macro is much more versatile than any looping macro in the LaTeX kernel, except for the fact that it locally scopes assignments. It does that for good reason. Two obvious motives might be to minimize the number of globally defined macros and to avoid redefining existing macros. – Ahmed Musa May 5 '12 at 23:27
Have you tried \globaldefs=-1? \foreach \xa\xb in {a1/a2,b1/b2,c1/c2,d1/d2}{% \globaldefs=-1 \advance\nra by1 \nrb=0 \foreach \ya\yb in {s1/s2,t1/t2,u1/u2,v1/v2}{% \advance\nrb by1 \ifnum\nrb>1 \breakforeach\fi \print }% } – Marco Daniel May 6 '12 at 18:35
Yes, not a solution. TeX by Topic: If \globaldefs is positive every assignment is implicitly prefixed with \global, and if \globaldefs is negative, \global is ignored. Ordinarily this parameter is zero. – Ahmed Musa May 6 '12 at 18:40

Here is a solution I am building into catopitons package. There is no hardwired local scoping of computations in this scheme. Arbitrary parsers, even active parsers, are accepted. There is still some way to the perfect finish line. The provision for user variables/keys such as 'evaluate' and 'count' are yet to be provided. Since there are no hardwired local groups, we don't need the 'remember' variable of \foreach here. The variable 'remember' of \foreach should actually read 'remember last'. In the present scheme we need to complete arguments with tokens of the form n,...,m.

\documentclass{article}
\usepackage{tikz}
\usepackage{catoptions}
\makeatletter
% \newforeach*[<options>]<holder-cmds>\in<list>\do{<callback>}
%
% The optional <options> include
%  1. Main list parser <parser>; its default value is comma (,).
%  2. Sub list parser <subparser>; its default value is slash (/).
%      <subparser> or <sub parser> separates the sublists, eg, slash
%      in the following list:
%      \x/\y/\z -> a/b/c, d/e/f, g/h/i
%  3. Other options as in pgf's \foreach (to be included).
%
% Both the main parser and sub parser can be active characters,
% unlike in pgf's \foreach.
%
% The optional star (*) suffix, when specified, implies that <listcmd>
% (a macro containing the list) is given in place of <list>.
%
% Any type of argument and delimiter is allowed in \newforeach.

\robust@def*\newforeach{\cpt@teststopt\cpt@foreach@a{}}
\robust@def*\cpt@foreach@a[#1]#2\in#3\do{%
\ifnextcharTF\bgroup{%
\cpt@foreach@c{#1}{#2}{#3}%
}{%
\cpt@foreach@b{#1}{#2}{#3}%
}%
}
\robust@def*\cpt@foreach@b#1#2#3#4;{\cpt@foreach@c{#1}{#2}{#3}{#4}}
\robust@def*\cpt@foreach@c#1#2#3#4{%
% Localize internal macros but not user callbacks:
\begingroup
\cpt@stchoose{cpt@st}{#3}\foreach@list\cpt@foreach@c
\def\foreach@parser{,}%
\def\foreach@subparser{/}%
\def\foreach@normalize##1##2{%
\ifblankTF{##2}{%
\def\normalized@list{}%
}{%
\expandafter\csv@@normalize\expandafter[##1]{##2}%
}%
}%
% Process the keys/variables:
\def\foreach@split##1=##2=##3\@nil{%
\csn@edef{foreach@\cptzapspaces{##1}}{\cpttrimspace{##2}}%
}%
\kv@@normalize{#1}%
\expandafter\cptfor\normalized@list\dofor{%
\foreach@split##1==\@nil
}%
% Normalize the given list for active subparser and spurious spaces:
\cptexpandsecond\foreach@normalize
{{\foreach@subparser}{\expandcsonce\foreach@list}}%
\let\foreach@list\normalized@list
% We need a scanner here, but user callbacks will not be rescanned:
\def\foreach@rescan##1{%
\begingroup
\endlinechar\m@one\newlinechar\m@one
\everyeof{\foreach@nil}%
\def\reserved@a####1\foreach@nil{%
\endgroup\edef##1{\unexpanded{####1}}%
}%
\expandafter\reserved@a\scantokens\expandafter{##1}%
}%
\def\ifforeach@subparser##1{%
\expandafter\ifstrcmpTF\expandafter{\foreach@subparser}{##1}%
}%
% \foreach@holderlistb contains holders defined in terms of the
% parameters of \csv@do; \foreach@holderlista is simply a comma list
% of the holders:
\@tempcnta\z@pt\def\foreach@holderlista{}%
\def\foreach@holderlistb{}\def\foreach@paramlist{}%
% Normalize holder-macro list for active subparsers and spurious
% spaces:
\foreach@normalize\foreach@subparser{#2}%
\expandafter\cpttfor\normalized@list\dofor{%
\ifforeach@subparser{##1}{%
\ifnum\@tempcnta=\z@pt
\cpt@err{First item of \noexpand\newforeach can't
\MessageBreak be a delimiter}\@ehc
\fi
% Insert holder delimiter:
\edef\foreach@paramlist{%
\expandcsonce\foreach@paramlist\unexpanded{##1}%
}%
}{%
\edef\foreach@holderlista{%
\ifcsemptyTF\foreach@holderlista
{}{\expandcsonce\foreach@holderlista,}%
\unexpanded{##1}%
}%
\edef\foreach@holderlistb{%
\expandcsonce\foreach@holderlistb
\edef\unexpanded{##1}{%
\noexpand\cpttrimspace{\cpt@hashchar\number\@tempcnta}%
}%
}%
\edef\foreach@paramlist{%
\expandcsonce\foreach@paramlist\cpt@hashchar\number\@tempcnta
}%
}%
}%
% Save holder macros #2 (eg, \x, \y, \z) so that \newforeach can later
% restore their prior definitions:
\cptexpandargonce{\pushfunctions\newforeach}%
\foreach@holderlista\cpt@csvdepth
\foreach@rescan\foreach@holderlistb
\foreach@rescan\foreach@paramlist
\let\foreach@nil\relax
\def\elt{foreach@do@\romannumeral\cpt@csvdepth}%
\cptexpanded{\endgroup
\def\noexpandcsn\elt\expandcsonce\foreach@paramlist\foreach@nil{%
\expandcsonce\foreach@holderlistb\unexpanded{#4}%
}%
\def\noexpand\csv@do####1{%
\noexpandcsn\elt####1\foreach@nil
}%
\csv@@parse[\foreach@parser]{\expandcsonce\foreach@list}%
}%
% Restore holder macros to their pre-loop values:
\popfunctions\newforeach\cpt@csvdepth
}
\newletcs\breaknewforeach\loopbreak
\makeatother

\begin{document}
% Examples:
\newcount\nra\newcount\nrb
% The parser is semicolon (;) in the outer loop; the subparser is slash (/).
\newforeach[parser=;,sub parser=/] \xa/\ya/\za \in
a1/b1/c1; a2/b2/c2; a3/b3/c3 \do{%
% \xa, \ya, \za, etc, will be despaced (ie, spurious leading and
% trailing spaces around their contents will be removed internally).
% The parser is comma (,) in the inner loop; the subparser is bar (|).
% The token 'parser={,}' below can be omitted, since comma is the
% default parser:
\newforeach[parser={,},sub parser=|] \xb|\yb|\zb \in
s1|t1|u1, s2|t2|u2, s3|t3|u3 \do{%
\def\elt{cmda@\romannumeral\nra @\romannumeral\nrb}%
\expandafter\edef\csname\elt\endcsname####1{%
\xa\ya\za*####1*\xb\yb\zb
}%
\ifnum\nrb>1 \breaknewforeach\fi
{\tt\expandafter\meaning\csname\elt\endcsname}\endgraf
}%
}
\par\medskip
% TikZ example:
\newforeach \x / \cola \in 0/red,1/green,2/blue,3/yellow\do{%
\newforeach \y / \colb \in {0/red,1/green,2/blue,3/yellow}\do{%
}%
}
\end{tikzpicture}

% Or
\begingroup
\catcode\,=13 % active commas
\gdef\listb{0/red, 1/green, 2/blue, 3/yellow}
\endgroup

\newforeach \x/\cola \in 0/red,1/green,2/blue,3/yellow\do{%
\newforeach* \y/\colb \in \listb\do{%
}%
};

% Exercise: why can't we use \tikz[shading=ball] here?
\newforeach[parser=;] \x/\cola \in 0/red; 1/green; 2/blue; 3/yellow\do{%
\edef\cmdx{\x}%
\foreach[remember=\y as \remy (initially 0)] \y/\colb in
{0/red, 1/green, 2/blue, 3/yellow}{%
\edef\cmdy{\y}%
}%
}
% \show\cmdx % defined
% \show\cmdy % undefined
\end{tikzpicture}

% Examining \foreach's variable 'remember':
% \remx is remembered as \x only after the loop, hence only the last
% value (3) is returned in \remx. \savedremx returns repeated entries of initial
% assignment:
\foreach[remember=\x as \remx (initially 0)] \x / \cola in
{0/red,1/green,2/blue,3/yellow}{%
\xdef\savedremx{\ifdefFT\savedremx{}{\ifcsemptyTF\savedremx{}{\savedremx,}}\remx}%
\foreach \y / \colb in {0/red,1/green,2/blue,3/yellow}{%
}%
}
%\show\remx
%\show\savedremx
\end{tikzpicture}
%\show\remx
\end{document}


-
pgfplots includes a \pgfplotsforeachungrouped macro that is essentially a \foreach without groups. Might be interesting to take a look at how that was implemented. – Jake May 6 '12 at 23:40
@Jake: Have you tried \foreach \x/\y in {0/red; 1/green; 2/blue; 3/yellow} with \pgfplotsforeachungrouped? Note the list parser ;: \foreach doesn't accept arbitrary list separators. And what of active parsers? Remember babel. Also, I need to mix list parsers, eg, comma (,) and semicolon (;). – Ahmed Musa May 7 '12 at 9:52
Sorry, I didn't mean that this does what you're implementing. I mainly meant that if the question is merely "is there a way of turning the group scoping of \foreach` off?", that might be worth a look. – Jake May 7 '12 at 10:46
So it wasn't a genuine question! You do have an answer of your own. I'm very pleased. – Loop Space May 7 '12 at 12:58
@AndrewStacey: No, it was seriously a question. I worked hard to get here. Look at the edit log. – Ahmed Musa May 7 '12 at 16:40