lowlevel-loops.tex /size: 10 Kb    last modification: 2024-01-16 10:21
1% language=us runpath=texruns:manuals/lowlevel
2
3\environment lowlevel-style
4
5\startdocument
6  [title=loops,
7   color=middleyellow]
8
9\startsectionlevel[title=Introduction]
10
11I have hesitated long before I finally decided to implement native loops in
12\LUAMETATEX. Among the reasons against such a feature is that one can define
13macros that do loops (preferably using tail recursion). When you don't need an
14expandable loop, counters can be used, otherwise there are dirty and obscure
15tricks that can be of help. This is often the area where tex programmers can show
16off but the fact remains that we're using side effects of the expansion machinery
17and specific primitives like \type {\romannumeral} magic. In \LUAMETATEX\ it is
18actually possible to use the local control mechanism to hide loop counter advance
19and checking but that comes with at a performance hit. And, no matter what tricks
20one used, tracing becomes pretty much cluttered.
21
22In the next sections we describe the new native loop primitives in \LUAMETATEX\ as
23well as the more traditional \CONTEXT\ loop helpers.
24
25\stopsectionlevel
26
27\startsectionlevel[title=Primitives]
28
29Because \METAPOST, which is also a macro language, has native loops, it makes
30sense to also have native loops in \TEX\ and in \LUAMETATEX\ it was not that hard
31to add it. One variant uses the local control mechanism which is reflected in its
32name and two others collect expanded bodies. In the local loop content gets
33injected as we go, so this one doesn't work well in for instance an \type
34{\edef}. The macro takes the usual three loop numbers as well as a token list:
35
36\starttyping[option=TEX]
37\localcontrolledloop 1 100000 1 {%
38    % body
39}
40\stoptyping
41
42Here is an example of usage:
43
44\startbuffer
45\localcontrolledloop 1 5 1 {%
46    [\number\currentloopiterator]
47    \localcontrolledloop 1 10 1 {%
48        (\number\currentloopiterator)
49    }%
50    [\number\currentloopiterator]
51    \par
52}
53\stopbuffer
54
55\typebuffer[option=TEX]
56
57The \type {\currentloopiterator} is a numeric token so you need to explicitly
58serialize it with \type {\number} or \type {\the} if you want it to be typeset:
59
60\startpacked \getbuffer \stoppacked
61
62Here is another example. This time we also show the current nesting:
63
64\startbuffer
65\localcontrolledloop 1 100 1 {%
66    \ifnum\currentloopiterator>6\relax
67        \quitloop
68    \else
69        [\number\currentloopnesting:\number\currentloopiterator]
70        \localcontrolledloop 1 8 1 {%
71            (\number\currentloopnesting:\number\currentloopiterator)
72        }\par
73    \fi
74}
75\stopbuffer
76
77\typebuffer[option=TEX]
78
79Watch the \type {\quitloop}: it will end the loop at the {\em next} iteration so
80any content after it will show up. Normally this one will be issued in a
81condition and we want to end that properly.
82
83\startpacked \getbuffer \stoppacked
84
85The three loop variants all perform differently:
86
87\startbuffer
88l:\testfeatureonce {1000} {\localcontrolledloop 1 2000 1 {\relax}} %
89  \elapsedtime
90e:\testfeatureonce {1000} {\expandedloop        1 2000 1 {\relax}} %
91  \elapsedtime
92u:\testfeatureonce {1000} {\unexpandedloop      1 2000 1 {\relax}} %
93  \elapsedtime
94\stopbuffer
95
96\typebuffer[option=TEX]
97
98An unexpanded loop is (of course) the fastest because it only collects and then
99feeds back the lot. In an expanded loop each cycle does an expansion of the body
100and collects the result which is then injected afterwards, and the controlled
101loop just expands the body each iteration.
102
103\startlines\tttf \getbuffer \stoplines
104
105The different behavior is best illustrated with the following example:
106
107\startbuffer[definition]
108\edef\TestA{\localcontrolledloop 1 5 1 {A}} % out of order
109\edef\TestB{\expandedloop        1 5 1 {B}}
110\edef\TestC{\unexpandedloop      1 5 1 {C\relax}}
111\stopbuffer
112
113\typebuffer[definition][option=TEX]
114
115We can show the effective definition:
116
117\startbuffer[example]
118\meaningasis\TestA
119\meaningasis\TestB
120\meaningasis\TestC
121
122A: \TestA
123B: \TestB
124C: \TestC
125\stopbuffer
126
127\typebuffer[example][option=TEX]
128
129Watch how the first test pushes the content in the main input stream:
130
131\startlines\tttf \getbuffer[definition,example]\stoplines
132
133Here are some examples that show what gets expanded and what not:
134
135\startbuffer
136\edef\whatever
137  {\expandedloop 1 10 1
138     {(\number\currentloopiterator)
139      \scratchcounter=\number\currentloopiterator\relax}}
140
141\meaningasis\whatever
142\stopbuffer
143
144\typebuffer[option=TEX]
145
146\startpacked \veryraggedright \tt\tfx \getbuffer \stoppacked
147
148A local control encapsulation hides the assignment:
149
150\startbuffer
151\edef\whatever
152  {\expandedloop 1 10 1
153     {(\number\currentloopiterator)
154      \beginlocalcontrol
155      \scratchcounter=\number\currentloopiterator\relax
156      \endlocalcontrol}}
157
158\meaningasis\whatever
159\stopbuffer
160
161\typebuffer[option=TEX]
162
163\blank \start \veryraggedright \tt\tfx \getbuffer \stop \blank
164
165Here we see the assignment being retained but with changing values:
166
167\startbuffer
168\edef\whatever
169  {\unexpandedloop 1 10 1
170     {\scratchcounter=1\relax}}
171
172\meaningasis\whatever
173\stopbuffer
174
175\typebuffer[option=TEX]
176
177\blank \start \veryraggedright \tt\tfx \getbuffer \stop \blank
178
179We get no expansion at all:
180
181\startbuffer
182\edef\whatever
183  {\unexpandedloop 1 10 1
184     {\scratchcounter=\the\currentloopiterator\relax}}
185
186\meaningasis\whatever
187\stopbuffer
188
189\typebuffer[option=TEX]
190
191\blank \start \veryraggedright \tt\tfx \getbuffer \stop \blank
192
193And here we have a mix:
194
195\startbuffer
196\edef\whatever
197  {\expandedloop 1 10 1
198     {\scratchcounter=\the\currentloopiterator\relax}}
199
200\meaningasis\whatever
201\stopbuffer
202
203\typebuffer[option=TEX]
204
205\blank \start \veryraggedright \tt\tfx \getbuffer \stop \blank
206
207There is one feature worth noting. When you feed three numbers in a row, like here,
208there is a danger of them being seen as one:
209
210\starttyping
211\expandedloop
212  \number\dimexpr1pt
213  \number\dimexpr2pt
214  \number\dimexpr1pt
215  {}
216\stoptyping
217
218This gives an error because a too large number is seen. Therefore, these loops
219permit leading equal signs, as in assignments (we could support keywords but
220it doesn't make much sense):
221
222\starttyping
223\expandedloop =\number\dimexpr1pt =\number\dimexpr2pt =\number\dimexpr1pt{}
224\stoptyping
225
226\stopsectionlevel
227
228\startsectionlevel[title=Wrappers]
229
230We always had loop helpers in \CONTEXT\ and the question is: \quotation {What we
231will gain when we replace the definitions with ones using the above?}. The answer
232is: \quotation {We have little performance but not as much as one expects!}. This
233has to do with the fact that we support \type {#1} as iterator and \type {#2} as
234(verbose) nesting values and that comes with some overhead. It is also the reason
235why these loop macros are protected (unexpandable). However, using the primitives
236might look somewhat more natural in low level \TEX\ code.
237
238Also, replacing their definitions can have side effects because the primitives are
239(and will be) still experimental so it's typically a patch that I will run on my
240machine for a while.
241
242Here is an example of two loops. The inner state variables have one hash, the outer
243one extra:
244
245\startbuffer
246\dorecurse{2}{
247    \dostepwiserecurse{1}{10}{2}{
248        (#1:#2) [##1:##2]
249    }\par
250}
251\stopbuffer
252
253\typebuffer[option=TEX]
254
255We get this:
256
257\startpacked \getbuffer \stoppacked
258
259We can also use two state macro but here we would have to store the outer ones:
260
261\startbuffer
262\dorecurse {2} {
263    /\recursedepth:\recurselevel/
264    \dostepwiserecurse {1} {10} {2} {
265        <\recursedepth:\recurselevel>
266    }\par
267}
268\stopbuffer
269
270\typebuffer[option=TEX]
271
272That gives us:
273
274\startpacked \getbuffer \stoppacked
275
276An endless loop works as follows:
277
278\startbuffer
279\doloop {
280    ...
281    \ifsomeconditionismet
282        ...
283        \exitloop
284    \else
285        ...
286    \fi
287  % \exitloopnow
288    ...
289}
290\stopbuffer
291
292\typebuffer[option=TEX]
293
294Because of the way we quit there will not be a new implementation in terms of
295the loop primitives. You need to make sure that you don't leave in the middle
296of an ongoing condition. The second exit is immediate.
297
298We also have a (simple) expanded variant:
299
300\startbuffer
301\edef\TestX{\doexpandedrecurse{10}{!}} \meaningasis\TestX
302\stopbuffer
303
304\typebuffer[option=TEX]
305
306This helper can be implemented in terms of the loop primitives which makes them a
307bit faster, but these are not critical:
308
309\startpacked \getbuffer \stoppacked
310
311A variant that supports \type {#1} is the following:
312
313\startbuffer
314\edef\TestX{\doexpandedrecursed{10}{#1}} \meaningasis\TestX
315\stopbuffer
316
317\typebuffer[option=TEX]
318
319So:
320
321\startpacked \getbuffer \stoppacked
322
323% private: \dofastloopcs{#1}\cs with % \fastloopindex and \fastloopfinal
324
325\stopsectionlevel
326
327\startsectionlevel[title=About quitting]
328
329You can quit a local and expanded loop at the next iteration using \typ
330{\quitloop}. With \typ {\quitloopnow} you immediately leave the loop but you
331need to beware of side effects, like not ending a condition properly. Keep in
332mind that a macro language like \TEX\ is not that friendly towards loops so the
333implementation is a bit hairy.
334
335\stopsectionlevel
336
337\startsectionlevel[title=Simple repeaters]
338
339For simple iterations we have \typ {\localcontrolledrepeat}, \typ
340{\expandedrepeat}, \typ {\unexpandedrepeat}. These take one integer instead of
341three: the final iterator value.
342
343\stopsectionlevel
344
345\startsectionlevel[title=Endless loops]
346
347There are three endless loop primitives: \typ {\localcontrolledendless}, \typ
348{\expandedendless}, \typ {\unexpandedendless}. These will keep running till you
349quit them. The loop counter can overflow the maximum integer value and will then
350start again at 1.
351
352\stopsectionlevel
353
354
355\startsectionlevel[title=Loop variables]
356
357The following example shows how we can access the current, parent and grand parent
358loop iterator values using a parameter like syntax:
359
360\startbuffer
361\localcontrolledloop 1 4 1 {%
362    \localcontrolledloop 1 3 1 {%
363        \localcontrolledloop 1 2 1 {%
364  \edef\foo{[#G,#P,#I]}\foo
365  \def \oof{<#G,#P,#I>}\oof
366            (#G,#P,#I)\space
367        }
368        \par
369    }
370}
371\stopbuffer
372
373\typebuffer  \startpacked\getbuffer\stoppacked
374
375Another way to access a(ny) parent is:
376
377\starttyping
378\localcontrolledloop 1 4 1 {%
379    \localcontrolledloop 1 3 1 {%
380        \localcontrolledloop 1 2 1 {%
381            (\the\previousloopiterator2,%
382             \the\previousloopiterator1,%
383             \the\currentloopiterator)
384        }
385        \par
386    }
387}
388\stoptyping
389
390These methods make that one doesn't have to store the outer loop variables for
391usage inside the inner loop. Watch out with the \type {\edef}:
392
393\startbuffer
394\edef\foo{[#G,#P,#I]}
395\def \oof{<#G,#P,#I>}
396
397\localcontrolledloop 1 4 1 {%
398    \localcontrolledloop 1 3 1 {%
399        \localcontrolledloop 1 2 1 {%
400        %
401        % I iterator    \currentloopiterator
402        % P parent      \previousloopiterator1
403        % G grandparent \previousloopiterator2
404        %
405            \edef\ofo{[#G,#P,#I]}%
406            \foo\oof\ofo(#G,#P,#I)\space
407        %
408        }
409        \par
410    }
411}
412\stopbuffer
413
414\typebuffer \startpacked\getbuffer\stoppacked
415
416\stopsectionlevel
417
418\stopdocument
419