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.

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}{%
  \global\advance\nra by1
  \nrb=0
  \foreach \ya\yb in {s1/s2,t1/t2,u1/u2,v1/v2}{%
    \global\advance\nrb by1
    \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} 
share|improve this question
    
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 –  Andrew Stacey 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
show 3 more comments

1 Answer

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}%
      }%
    }{%
      \advance\@tempcnta\@ne
      \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{%
  \advance\nra by1 \nrb=0
  % \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{%
    \advance\nrb by1
    \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:
\begin{tikzpicture}[shading=ball]
\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{%
    \shade[ball color=\cola!50!\colb] (\x,\y) circle (0.4cm);
  }%
}
\end{tikzpicture}

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

\tikz[shading=ball]
\newforeach \x/\cola \in 0/red,1/green,2/blue,3/yellow\do{%
  \newforeach* \y/\colb \in \listb\do{%
    \shade[ball color=\cola!50!\colb] (\x,\y) circle (0.4cm);
  }%
};

\begin{tikzpicture}[shading=ball]
% 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}%
    \shade[ball color=\cola!50!\colb] (\x,\y) circle (0.4cm);
  }%
}
% \show\cmdx % defined
% \show\cmdy % undefined
\end{tikzpicture}

% Examining \foreach's variable 'remember':
\begin{tikzpicture}[shading=ball]
% \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}{%
    \shade[ball color=\cola!50!\colb] (\x,\y) circle (0.4cm);
  }%
}
%\show\remx
%\show\savedremx
\end{tikzpicture}
%\show\remx
\end{document}

enter image description here

enter image description here

share|improve this answer
    
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. –  Andrew Stacey 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
show 1 more 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.