texit-lookahead.tex /size: 8855 b    last modification: 2020-07-01 14:35
1\environment texit-style
2
3\startcomponent texit-lookahead
4
5\startchapter[title={Lookahead}]
6
7When you look at the \TEX\ source of a macro package, your can often see
8constructs like this:
9
10\startTEX
11\def\foo#1%
12  {We do something with  "#1".}
13\stopTEX
14
15or maybe:
16
17\startTEX
18\def\foo#1{%
19    We do something with  "#1".%
20}
21\stopTEX
22
23Normally the percentage symbol is used to indicate a comment, but here
24are no comments. In these cases, it makes the definition effectively
25
26\startTEX
27\def\foo#1{do something with "#1"!}
28\stopTEX
29
30which is different from when we would not have that percent sign there:
31
32\startTEX
33\def\foo#1 {We do something with "#1"!}
34\stopTEX
35
36That variant is valid \TEX\ code but expects a space as delimiter of the
37argument to \type {\foo}. This means that you can say:
38
39\startTEX
40\foo{1} \foo 2 \foo {34} and \foo 56 .
41\stopTEX
42
43while this can trigger an error message (when no space is seen at some point) or
44at least give unexpected results.
45
46\startTEX
47\foo{1}\foo 2\foo {34}and\foo 56.
48\stopTEX
49
50A different use of the percent is seen in cases like this:
51
52\startTEX
53\def\foo#1%
54  {We do something %
55   with "#1".}
56\stopTEX
57
58This time we want to preserve the space after \type {something} because an
59end|-|of|-|line would either or not collapse it with \type {with} depending on
60how the endofline character is set up. Normally
61
62\startTEX
63\def\foo#1%
64  {We do something
65   with "#1".}
66\stopTEX
67
68will also add a space after something but when \TEX\ is set up to ignore lines
69you get a collapse. So the explicit space is a robust way out. Both cases of
70using or omitting the comment symbol are easy to spot as they trigger an error
71or result in weird typeset results.
72
73\startbuffer[defs]
74\def\fooA#1%
75  {\ifnum#1>100
76     yes\else nop%
77   \fi}
78
79\def\fooB#1{\ifnum#1>100 yes\else nop \fi}
80
81\def\fooC#1%
82  {\ifnum#1>100%
83     yes\else nop%
84   \fi}
85\stopbuffer
86
87\typebuffer[defs][option=TEX] \getbuffer[defs]
88
89We test this with:
90
91\startbuffer[demo]
92\fooA{100} \fooB{100} \fooC{100}
93\fooA{101} \fooB{101} \fooC{101}
94\stopbuffer
95
96\typebuffer[demo][option=TEX]
97
98And the result is probably what you expect:
99
100\startlines
101\getbuffer[demo]
102\stoplines
103
104\startbuffer[defs]
105\def\fooA#1%
106  {\ifnum#1>100
107     1\else 0%
108   \fi}
109
110\def\fooB#1{\ifnum#1>100 1\else 0\fi}
111
112\def\fooC#1%
113  {\ifnum#1>100%
114     1\else 0%
115   \fi}
116\stopbuffer
117
118However, when we have the following macro body:
119
120\typebuffer[defs][option=TEX] \getbuffer[defs]
121
122We get this output. Do you see the issue?
123
124\startlines
125\getbuffer[demo]
126\stoplines
127
128A preferred way to catch this is the following as a \type {\relax} ends scanning
129for a number:
130
131\startbuffer[defs]
132\def\foo#1%
133  {\ifnum#1>100\relax
134     1\else 0%
135   \fi}
136\stopbuffer
137
138\typebuffer[defs][option=TEX] \getbuffer[defs]
139
140However, watch what happens here:
141
142\startbuffer[demo]
143\edef\result{\foo{123}}
144\stopbuffer
145
146\typebuffer[demo][option=TEX] \getbuffer[demo]
147
148The \type {\result} macro has the following body:
149
150\expanded{\setbuffer[result]\meaning\result\endbuffer}
151
152\typebuffer[result][option=TEX]
153
154A neat trick out of this is the following:
155
156\startbuffer[defs]
157\def\foo#1%
158  {\ifnum#1>\numexpr100\relax
159     1\else 0%
160   \fi}
161\stopbuffer
162
163\typebuffer[defs][option=TEX] \getbuffer[defs]
164
165\getbuffer[demo]
166
167Now the body of \type {\result} looks like this:
168
169\expanded{\setbuffer[result]\meaning\result\endbuffer}
170
171\typebuffer[result][option=TEX]
172
173Of course this also works:
174
175\startTEX
176\def\foo#1%
177  {\ifnum#1>100 %
178     1\else 0%
179   \fi}
180\stopTEX
181
182as a space also delimits scanning the number. But that method can actually introduce
183that space in the output. Think of this definition:
184
185\startbuffer[defs]
186\def\foo#1#2%
187  {\ifnum#1>#2 %
188     1\else 0%
189   \fi}
190\stopbuffer
191
192\typebuffer[defs][option=TEX] \getbuffer[defs]
193
194What if \type {#2} has a trailing space? What if it is a verbose number? What if
195it is a counter variable?
196
197\startbuffer[demo]
198\scratchcounter=100
199    [\foo{101}{100}] [\foo{101}{100 }] [\foo{101}\scratchcounter]
200\scratchcounter=101
201    [\foo{100}{101}] [\foo{100}{101 }] [\foo{100}\scratchcounter]
202\stopbuffer
203
204\typebuffer[demo][option=TEX]
205
206\startlines
207\getbuffer[demo]
208\stoplines
209
210If you really want to introduce an unpredictable situation, use a coding style like
211this:
212
213\startTEX
214\def\foo#1#2#3#4{\if#1=#2#3\else#4\fi}
215\stopTEX
216
217This is not that imaginary as you often see users play safe and do things like this:
218
219\startTEX
220\ifnum\scratchcounterone=\scratchcountertwo%
221    ...
222\else
223    ...
224\fi
225\stopTEX
226
227Here the percent sign is useless as the number scanner already got the number,
228just try:
229
230\startTEX
231\scratchcounterone=1
232\scratchcountertwo=1
233
234\ifnum\scratchcounterone=\scratchcountertwo
235    yes
236\else
237    nop
238\fi
239\stopTEX
240
241A previous one liner formatted like this really is not better!
242
243\startTEX
244\def\foo#1#2#3#4%
245  {\ifnum#1=#2%
246      #3%
247   \else
248      #4%
249   \fi}
250\stopTEX
251
252When you define macros more often than not you don't want unexpected spaces (aka spurious spaces)
253which is why in \CONTEXT\ for instance setups ignores lines:
254
255\startbuffer[defs]
256\startsetups foo
257    here
258    we ignore
259    spaces at the end
260    of a line
261\stopsetups
262\stopbuffer
263
264\typebuffer[defs][option=TEX] \getbuffer[defs]
265
266so we get: \quotation {\directsetup{foo}} which means that the normally few times
267that we {\em do} want spaces we need to be explicit:
268
269\startbuffer[defs]
270\startsetups foo
271    here\space
272    we ignore\space
273    spaces at the end\space
274    of a line\space
275\stopsetups
276\stopbuffer
277
278\typebuffer[defs][option=TEX] \getbuffer[defs]
279
280Now we're okay: \quotation {\directsetup{foo}}. The same is true for:
281
282\startTEX
283\starttexdefinition foo
284    here\space
285    we ignore\space
286    spaces at the end\space
287    of a line\space
288\stoptexdefinition
289\stopTEX
290
291There are more cases where \TEX\ will look further. Take for example skip (glue)
292scanning. A glue specification can have \type {plus} and \type {minus} fields.
293
294\startbuffer[defs]
295\scratchdimenone=10pt
296\scratchskipone =10pt plus 10pt minus 10pt
297\scratchskiptwo =0pt
298\stopbuffer
299
300\typebuffer[defs][option=TEX]
301
302Now take the following test:
303
304\startbuffer[demo]
305{1 \scratchskiptwo  10pt             plus 10pt \relax\the\scratchskiptwo}
306{2 \scratchskiptwo  \scratchdimenone plus 10pt \relax\the\scratchskiptwo}
307{3 \scratchskiptwo 1\scratchdimenone plus 10pt \relax\the\scratchskiptwo}
308{4 \scratchskiptwo  \scratchskipone  plus 10pt \relax\the\scratchskiptwo}
309{5 \scratchskiptwo 1\scratchskipone  plus 10pt \relax\the\scratchskiptwo}
310\stopbuffer
311
312\typebuffer[demo][option=TEX]
313
314\startlines
315\inlinebuffer[defs]\getbuffer[demo]
316\stoplines
317
318If you wonder what the second \type {\relax} does, here is a variant:
319
320\startlines
321{1 \scratchskiptwo  10pt             plus 10pt \the\scratchskiptwo}
322{2 \scratchskiptwo  \scratchdimenone plus 10pt \the\scratchskiptwo}
323{3 \scratchskiptwo 1\scratchdimenone plus 10pt \the\scratchskiptwo}
324{4 \scratchskiptwo  \scratchskipone  plus 10pt \the\scratchskiptwo}
325{5 \scratchskiptwo 1\scratchskipone  plus 10pt \the\scratchskiptwo}
326\stoplines
327
328\typebuffer[demo][option=TEX]
329
330\startlines
331\inlinebuffer[defs]\getbuffer[demo]
332\stoplines
333
334In this second variant \TEX\ happily keep looking for a glue specification when
335it sees the \type {\the} so it serializes \type {\scratchskiptwo}. But as it sees
336\type {0pt} then, it stops scanning the glue spec. What we get typeset is the old
337value, not the new one! If you want to prevent this you need to \type {\relax}.
338
339Another case where \TEX\ keeps scanning is the following:
340
341\startbuffer[demo]
342\vrule width 40pt height 2pt depth 5pt \quad
343\vrule width 40pt height 20pt depth 5pt height 10pt \quad
344\vrule width 40pt height 10pt height 20pt \quad
345\vrule width 40pt height 20pt depth 5pt height 10pt width 80pt
346\stopbuffer
347
348\typebuffer[demo][option=TEX]
349
350This gives the rules:
351
352\startlinecorrection \darkgray
353\getbuffer[demo]
354\stoplinecorrection
355
356So you can overload dimensions. The space before the \type {quad} is gobbled as
357part of the look ahead for more keywords.
358
359Often rules (just like glue assignments) are wrapped in macro definitions where the
360macro writer used \type {\relax} to look ahead. That way you prevent an error message
361in cases like:
362
363\startTEX
364\def\foo{\vrule width 40pt height 2pt}
365
366The \foo depth of this thought is amazing.
367\stopTEX
368
369because \type {of} definitely is not a valid dimension. Even more subtle is:
370
371\startTEX
372\def\foo{\hskip 10pt plus 1fil}
373
374The \foo fine points of typesetting can actually become a nightmare.
375\stopTEX
376
377As \TEX\ will now see the \type {f} of \type {fine} as further specification and
378think that you want \type {1fill}.
379
380So, the most important lesson of this chapter is that you need to be aware of the way
381\TEX\ scans for quantities and specifications. In most cases the users can safely use
382a \type {\relax} to prevent a lookahead. And try to avoid adding percent signs all
383over the place.
384
385\stopchapter
386
387\stopcomponent
388