7

Please are there 'nestable' variants of Knuth's favourite looping macro \loop without the brace-scope introduced in the answer at nested loops. I am developing a package called loops, where I am collecting many loops (including a new \foreach and some weird looping macros).

\@tempcnta0 % LaTeX
\loop
  \advance\@tempcnta by1
  \ifnum\@tempcnta<3\relax
  \@tempcntb0
  \loop
    \advance\@tempcntb by1
    \ifnum\@tempcntb<4\relax
    \@namedef{w@\romannumeral\@tempcnta @\romannumeral\@tempcntb}{x}%
  \repeat
\repeat

3 Answers 3

8

My initial example, which I leave below did not allow the same loop macro to be nested but did allow declaration of (structurally identical) macros which could be nested one inside the other.

Here is a new version based on the same idea, but actually the coding is simpler and it does allow nesting of loops without having to pre-declare different loop names. The syntax of \nloop is as for plain TeX \loop except that \nloop and \repeat must each be followed by an identifying token, so they may be matched in nested cases.

The definition is hardly any more complicated than that of \loop and simpler than the version below

\def\nloop#1{%
  \def\nl@@p##1##2\repeat#1{%
  \def##1{##2\relax\expandafter##1\fi}%
   ##1\let##1\relax}%
\expandafter\nl@@p\csname nl@@p-\string#1\endcsname
}

and a full test document using this definition on the test case in the question. In this example I use the tokens \a and \b to identify the two loops, but the \a and \b are arbitrary, and don't need to be defined commands (you could use 1 and 2 for example).

\documentclass{article}

\begin{document}

\makeatletter

\def\nloop#1{%
  \def\nl@@p##1##2\repeat#1{%
  \def##1{##2\relax\expandafter##1\fi}%
   ##1\let##1\relax}%
\expandafter\nl@@p\csname nl@@p-\string#1\endcsname
}

\@tempcnta0 % LaTeX
\nloop\a
  \advance\@tempcnta by1
  \ifnum\@tempcnta<3\relax
  \@tempcntb0
  \nloop\b
    \advance\@tempcntb by1
    \ifnum\@tempcntb<4\relax
    \typeout{defining \expandafter\string\csname
w@\romannumeral\@tempcnta @\romannumeral\@tempcntb\endcsname}
    \@namedef{w@\romannumeral\@tempcnta @\romannumeral\@tempcntb}{x}%
  \repeat\b
\repeat\a


\end{document}

\newloop declaration version:

This doesn't let \loop be nested but it does let you define other macros that have essentially identical definitions, so you can nest \loopb inside a \loop and can make a \loopc if need be:


(/usr/share/texmf-dist/tex/latex/base/size10.clo)) (./lp.aux)
defining \w@i@i
defining \w@i@ii
defining \w@i@iii
defining \w@ii@i
defining \w@ii@ii
defining \w@ii@iii
(./lp.aux) )

\documentclass{article}

\begin{document}

\makeatletter

%\def\loop#1\repeat{\def\iterate{#1\relax\expandafter\iterate\fi}%
%  \iterate \let\iterate\relax}

\def\nloopx#1#2#3#4#5{repeat}
\def\nloopy#1#2#3#4#5{iterate}
\def\nloopz#1#2#3{%
\let#2\fi
\def#1##1#2{\def#3{##1\relax\expandafter#3\fi}#3\let#3\relax}}

\def\newloop#1{%
\expandafter\def\expandafter\@tempa\expandafter{%
\expandafter#1%
\csname\expandafter\nloopx\string#1\expandafter\endcsname
\csname\expandafter\nloopy\string#1\endcsname}%
\expandafter\nloopz\@tempa}

\newloop\loopb



\@tempcnta0 % LaTeX
\loop
  \advance\@tempcnta by1
  \ifnum\@tempcnta<3\relax
  \@tempcntb0
  \loopb
    \advance\@tempcntb by1
    \ifnum\@tempcntb<4\relax
    \typeout{defining \expandafter\string\csname
w@\romannumeral\@tempcnta @\romannumeral\@tempcntb\endcsname}
    \@namedef{w@\romannumeral\@tempcnta @\romannumeral\@tempcntb}{x}%
  \repeatb
\repeat


\end{document}
1
  • The second solution, which now appears on top, is also DC-like. The minor difference is that it redefines inner loops every time the outer loop is fired.
    – Ahmed Musa
    Jun 4, 2012 at 0:04
8

This is taken from Kees van der Laan (TUGboat 14, n. 3, 1993, p. 310–318). I write it in Plain TeX

\newcount\cnta
\newcount\cntb

\def\rows{%
  \advance\cnta by 1
  % Here the code we want to be executed at every outer cycle
  \cntb0 \cols
  %%%
  \ifnum\cnta=2 \swor\fi % Two outer cycles
  \rows}
\def\swor#1\rows{\fi}
\def\cols{%
  \advance\cntb by 1
  % Here the code we want to be executed at every inner cycle
  \immediate\write16{Defining \string\w@\romannumeral\cnta @\romannumeral\cntb}%
  \expandafter\def\csname w@\romannumeral\cnta @\romannumeral\cntb\endcsname{x}%
  %%%
  \ifnum\cntb=3 \sloc\fi % Three inner cycles
  \cols}
\def\sloc#1\cols{\fi}

\rows

\bye

The output is

Defining \w@i@i
Defining \w@i@ii
Defining \w@i@iii
Defining \w@ii@i
Defining \w@ii@ii
Defining \w@ii@iii

and, since there's no group involved, the definitions are effectively performed.

Of course \loop can't be nested with its usual definition, because it's a macro with its argument delimited by \repeat:

\def\loop#1\repeat{\def\body{#1}\iterate}
\def\iterate{\body \let\next\iterate \else\let\next\relax\fi \next}
\let\repeat=\fi % this makes \loop...\if...\repeat skippable

In LaTeX it's slightly different, but the limitation is the same:

\def\loop#1\repeat{\def\iterate{#1\relax\expandafter\iterate\fi}%
  \iterate \let\iterate\relax}

This one is taken instead from Paweł Jackowski (TUGboat 29, n. 2, 2008, p. 320–323) and requires a redefinition of \loop:

\long\def\loop#1\repeat{%
  \iterate\gobbleone{#1}}
\long\def\iterate\gobbleone#1{%
  #1\expandafter\iterate\fi
  \gobbleone{#1}}
\long\def\gobbleone#1{}

\newcount\cnta
\newcount\cntb

\loop{
  \advance\cnta by1
  \ifnum\cnta<3\relax
  \cntb0
  \loop
    \advance\cntb by1
    \ifnum\cntb<4\relax
    \immediate\write16{Defining \string\w@\romannumeral\cnta @\romannumeral\cntb}%
    \expandafter\def\csname w@\romannumeral\cnta @\romannumeral\cntb\endcsname{x}%
  \repeat
}\repeat

\immediate\write16{\expandafter\meaning\csname w@ii@iii\endcsname}

\bye

Here's the output:

Defining \w@i@i
Defining \w@i@ii
Defining \w@i@iii
Defining \w@ii@i
Defining \w@ii@ii
Defining \w@ii@iii
macro:->x
1
  • BTW, thanks for leading us to Kees van der Laan. I tasted some syntactic honey there. Now, Paweł Jackowski's scheme too requires brace-scoping and so it isn't really a black box. The user has to know and remember where to place the scope, to initially hide the inner loop from the scanner.
    – Ahmed Musa
    May 31, 2012 at 23:52
5

Before I labour over another solution (if that remains necessary or interesting), here is David Carlisle's syntactic cake coated with chocolate and dates.

\documentclass{article}
\makeatletter
\def\declarenewloop#1{%
  \begingroup
  \def\newloopa##1{repeat}%
  \def\newloopb##1{iterate@}%
  \def\newloopc##1##2##3{\endgroup
    \ifx##1\@undefined\else
      \edef\reserved@a{\expandafter\@gobble\string##1}%
      \@notdefinable
    \fi
    \let##2\fi
    \def##1####1##2{%
      \def##3{####1\relax\expandafter##3\fi}%
      ##3\let##3\relax
    }%
  }%
  \def\x##1##2{\expandafter\noexpand\csname\expandafter##1\string##2\endcsname}%
  \edef\x{\noexpand#1\x\newloopa#1\x\newloopb#1}%
  \expandafter\newloopc\x
}

% Tests:
\declarenewloop\loopb

\newcount\row\newcount\column
\row0 \column0
\def\stack{}
\loop
  \advance\row by1
  \ifnum\row<3\relax
  \column0
  \loopb
    \advance\column by1
    \ifnum\column<4\relax
    \let\rom\romannumeral
    \typeout{Including numbers \rom\row, \rom\column}%
    \edef\stack{%
      \unexpanded\expandafter{\stack}\noexpand\\{\rom\row}{\rom\column}%
    }%
  \repeatloopb
\repeat
\makeatother

\begin{document}
x
\end{document} 

This gives

Including numbers i, i
Including numbers i, ii
Including numbers i, iii
Including numbers ii, i
Including numbers ii, ii
Including numbers ii, iii

EDIT

Here is the solution I thought I could build. Notice, in the example, that both \loop and \repeat are nested without any change of their names. We have

\loop
 ...
  \loop
   ***
  \repeat
\repeat

The code follows:

\documentclass{article}
\usepackage{catoptions}
\makeatletter
\newcount\amloop@cnt
\newtoks\amloop@toks
\new@def*\this@ghost{\@gobble\this@ghost}
\new@def\amloop@ifcmdeq#1{\expandafter\ifcseqTF\cpt@car#1\this@ghost\car@nil}
\new@def\amloop@orig#1\repeat{\amloop@origb\@gobble{#1}}
\let\repeat\fi
\new@def\amloop@origb\@gobble#1{%
  #1\relax\expandafter\amloop@origb\fi\@gobble{#1}%
}
\amloop@toks{\amloop@orig}
\new@def\amloop@iiofii#1#2{#1#2}
\new@def\amloop@ifflochtype#1{%
  \csname @\if0\pdfstrcmp{\cptoxdetok{\amloop@iiofii#1{}{}}}%
    {\detokenize{#1{}}}first\else second\fi oftwo\endcsname
}
\robust@def*\am@defnewloop{%
  \begingroup
  \def\def@loop##1##2##3{\endgroup
    \def\name@cmd{##1}\def\repeat@cmd{##2}%
    \let##2\fi
    \def##1####1##2{%
      \def##3{####1\relax\expandafter##3\fi}%
      ##3\let##3\relax
    }%
  }%
  \def\x##1{\noexpandcsn{##1\romannumeral\amloop@cnt}}%
  \edef\x{\x{amloop@}\x{amrepeat@}\x{amiterate@}}%
  \expandafter\def@loop\x
}
\robust@def*\amloop{%
  \edef\amloop@restoreeof{\everyeof{\the\everyeof}}%
  \everyeof{EOF}\amloop@cnt\z@
  \amloop@pushloop
}
\robust@def\amloop@pushloop{\futurelet\next\amloop@pushloopb}
\robust@def\amloop@pushloopb#1{%
  \amloop@ifflochtype{#1}{%
    \amloop@ifcmdeq{#1}\amloop{%
      \advance\amloop@cnt\@ne
      \am@defnewloop
      \amloop@toks\expandafter{\the\expandafter\amloop@toks\name@cmd}%
      \amloop@pushloop
    }{%
      \ifstrcmpTF{#1}\repeat{%
        \ifnumcmpTF\amloop@cnt=\z@{%
          \amloop@restoreeof
          \amloop@toks\expandafter{\expandafter\amloop@orig
            \expandafter}\the\amloop@toks\repeat
        }{%
          \amloop@toks\expandafter{\the\expandafter\amloop@toks\repeat@cmd}%
          \advance\amloop@cnt\m@ne
          \amloop@pushloop
        }%
      }{%
        \ifcseqTF\next\@sptoken{%
          \amloop@toks\expandafter{\the\expandafter\amloop@toks\space#1}%
        }{%
          \amloop@toks\expandafter{\the\amloop@toks#1}%
        }%
        \amloop@pushloop
      }%
    }%
  }{%
    \ifstrcmpTF{#1}{EOF}{%
      \amloop@restoreeof
      \cpt@err{'\string\repeat' not found: endless loop}\@ehd
    }{%
      \ifcseqTF\next\@sptoken{%
        \amloop@toks\expandafter{\the\expandafter\amloop@toks\space{#1}}%
      }{%
        \amloop@toks\expandafter{\the\amloop@toks{#1}}%
      }%
      \amloop@pushloop
    }%
  }%
}

% Tests
\newcount\row\newcount\column
\row0 \column0
\def\stack{}
\let\loop\amloop
\loop
  \advance\row by1\relax
  \ifnum\row<3\relax
  \column0\relax
  \let\rom\romannumeral
  \loop
    \advance\column by1\relax
    \ifnum\column<4\relax
    \typeout{Including numbers \rom\row, \rom\column}%
    \edef\stack{\expandcsonce{\stack}\noexpand\\{\rom\row}{\rom\column}}%
  \repeat
  \typeout{Including row \rom\row}%
  \edef\stack{\expandcsonce{\stack}\noexpand\do{\rom\row}}%
\repeat
%\show\stack
\makeatother

\begin{document}
x
\end{document}

This gives

Including numbers i, i
Including numbers i, ii
Including numbers i, iii
Including row i
Including numbers ii, i
Including numbers ii, ii
Including numbers ii, iii
Including row ii

with

\stack=macro:
->\\{i}{i}\\{i}{ii}\\{i}{iii}\do {i}\\{ii}{i}\\{ii}{ii}\\{ii}{iii}\do {ii}

More test:

\cptnewcounts{rowcnt,colcnt,maxrow,maxcol}
\maxrow4 \maxcol3
\def\entries{}
\let\loop\amloop
\def\generateentries{%
  \loop
    \advance\rowcnt1\relax
    \colcnt0\relax
    \loop
      \advance\colcnt1\relax
      \edef\entries{\expandcsonce\entries\the\numexpr\rowcnt*\colcnt\relax}%
      \ifnum\colcnt<\numexpr\maxcol+1\relax\relax
      \edef\entries{\expandcsonce\entries\noexpand&}%
    \repeat
    \ifnum\rowcnt<\numexpr\maxrow+1\relax\relax
    \edef\entries{\expandcsonce\entries\noexpand\cr}%
  \repeat
  \edef\entries{\expandcsonce\entries\noexpand\crcr}%
}
\generateentries

\begin{document}
$$\vbox{\halign{&\ \hfil#\hfil\strut\cr\entries}}$$
\end{document} 

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .