lowlevel-conditionals.tex /size: 41 Kb    last modification: 2021-10-28 13:50
1% language=us runpath=texruns:manuals/lowlevel
2
3\environment lowlevel-style
4
5\startdocument
6  [title=conditionals,
7   color=middleblue]
8
9\pushoverloadmode
10
11\startsectionlevel[title=Preamble]
12
13\startsectionlevel[title=Introduction]
14
15You seldom need the low level conditionals because there are quite some so called
16support macros available in \CONTEXT . For instance, when you want to compare two
17values (or more accurate: sequences of tokens), you can do this:
18
19\starttyping[option=TEX]
20\doifelse {foo} {bar} {
21    the same
22} {
23    different
24}
25\stoptyping
26
27But if you look in the \CONTEXT\ code, you will see that often we use primitives
28that start with \type {\if} in low level macros. There are good reasons for this.
29First of all, it looks familiar when you also code in other languages. Another
30reason is performance but that is only true in cases where the snippet of code is
31expanded very often, because \TEX\ is already pretty fast. Using low level \TEX\
32can also be more verbose, which is not always nice in a document source. But, the
33most important reason (for me) is the layout of the code. I often let the look
34and feel of code determine the kind of coding. This also relates to the syntax
35highlighting that I am using, which is consistent for \TEX, \METAPOST, \LUA,
36etc.\ and evolved over decades. If code looks bad, it probably is bad. Of course
37this doesn't mean all my code looks good; you're warned. In general we can say
38that I often use \type {\if...} when coding core macros, and \type {\doifelse...}
39macros in (document) styles and modules.
40
41In the sections below I will discuss the low level conditions in \TEX. For the
42often more convenient \CONTEXT\ wrappers you can consult the source of the system
43and support modules, the wiki and|/|or manuals.
44
45Some of the primitives shown here are only available in \LUATEX, and some only in
46\LUAMETATEX . We could do without them for decades but they were added to these
47engines because of convenience and, more important, because then made for nicer
48code. Of course there's also the fun aspect. This manual is not an invitation to
49use these very low level primitives in your document source. The ones that
50probably make most sense are \type {\ifnum}, \type {\ifdim} and \type {\ifcase}.
51The others are often wrapped into support macros that are more convenient.
52
53In due time I might add more examples and explanations. Also, maybe some more
54tests will show up as part of the \LUAMETATEX\ project.
55
56\stopsectionlevel
57
58\startsectionlevel[title={Number and dimensions}]
59
60Numbers and dimensions are basic data types in \TEX. When you enter one, a number
61is just that but a dimension gets a unit. Compare:
62
63\starttyping[option=TEX]
641234
651234pt
66\stoptyping
67
68If you also use \METAPOST, you need to be aware of the fact that in that language
69there are not really dimensions. The \type {post} part of the name implies that
70eventually a number becomes a \POSTSCRIPT\ unit which represents a base point (\type
71{bp}) in \TEX. When in \METAPOST\ you entry \type {1234pt} you actually multiply
72\type {1234} by the variable \type {pt}. In \TEX\ on the other hand, a unit like
73\type {pt} is one of the keywords that gets parsed. Internally dimensions are
74also numbers and the unit (keyword) tells the scanner what multiplier to use.
75When that multiplier is one, we're talking of scaled points, with the unit \type
76{sp}.
77
78\startbuffer
79\the\dimexpr 12.34pt \relax
80\the\dimexpr 12.34sp \relax
81\the\dimexpr 12.99sp \relax
82\the\dimexpr 1234sp  \relax
83\the\numexpr 1234    \relax
84\stopbuffer
85
86\typebuffer[option=TEX]
87
88\startlines \getbuffer \stoplines
89
90When we serialize a dimension it always shows the dimension in points, unless we
91serialize it as number.
92
93\startbuffer
94\scratchdimen1234sp
95\number\scratchdimen
96\the\scratchdimen
97\stopbuffer
98
99\typebuffer[option=TEX]
100
101\startlines \getbuffer \stoplines
102
103When a number is scanned, the first thing that is taken care of is the sign. In many
104cases, when \TEX\ scans for something specific it will ignore spaces. It will
105happily accept multiple signs:
106
107\startbuffer
108\number +123
109\number +++123
110\number + + + 123
111\number +-+-+123
112\number --123
113\number ---123
114\stopbuffer
115
116\typebuffer[option=TEX]
117
118\startlines \getbuffer \stoplines
119
120Watch how the negation accumulates. The scanner can handle decimal, hexadecimal
121and octal numbers:
122
123\startbuffer
124\number -123
125\number -"123
126\number -'123
127\stopbuffer
128
129\typebuffer[option=TEX]
130
131\startlines \getbuffer \stoplines
132
133A dimension is scanned like a number but this time the scanner checks for upto
134three parts: an either or not signed number, a period and a fraction. Here no
135number means zero, so the next is valid:
136
137\startbuffer
138\the\dimexpr  . pt \relax
139\the\dimexpr 1. pt \relax
140\the\dimexpr  .1pt \relax
141\the\dimexpr 1.1pt \relax
142\stopbuffer
143
144\typebuffer[option=TEX]
145
146\startlines \getbuffer \stoplines
147
148Again we can use hexadecimal and octal numbers but when these are entered, there
149can be no fractional part.
150
151\startbuffer
152\the\dimexpr  16 pt \relax
153\the\dimexpr "10 pt \relax
154\the\dimexpr '20 pt \relax
155\stopbuffer
156
157\typebuffer[option=TEX]
158
159\startlines \getbuffer \stoplines
160
161The reason for discussing numbers and dimensions here is that there are cases where
162when \TEX\ expects a number it will also accept a dimension. It is good to know that
163for instance a macro defined with \type {\chardef} or \type {\mathchardef} also is
164treated as a number. Even normal characters can be numbers, when prefixed by a \type
165{`} (backtick).
166
167The maximum number in \TEX\ is 2147483647 so we can do this:
168
169\starttyping[option=TEX]
170\scratchcounter2147483647
171\stoptyping
172
173but not this
174
175\starttyping[option=TEX]
176\scratchcounter2147483648
177\stoptyping
178
179as it will trigger an error. A dimension can be positive and negative so there we
180can do at most:
181
182\starttyping[option=TEX]
183\scratchdimen  1073741823sp
184\stoptyping
185
186\startbuffer
187\scratchdimen1073741823sp
188\number\scratchdimen
189\the\scratchdimen
190\scratchdimen16383.99998pt
191\number\scratchdimen
192\the\scratchdimen
193\stopbuffer
194
195\typebuffer[option=TEX]
196
197\startlines
198\getbuffer
199\stoplines
200
201We can also do this:
202
203\startbuffer
204\scratchdimen16383.99999pt
205\number\scratchdimen
206\the\scratchdimen
207\stopbuffer
208
209\typebuffer[option=TEX]
210
211\startlines
212\getbuffer
213\stoplines
214
215but the next one will fail:
216
217\starttyping[option=TEX]
218\scratchdimen16383.9999999pt
219\stoptyping
220
221Just keep in mind that \TEX\ scans both parts as number so the error comes from
222checking if those numbers combine well.
223
224\startbuffer
225\ifdim 16383.99999  pt = 16383.99998  pt the same \else different \fi
226\ifdim 16383.999979 pt = 16383.999980 pt the same \else different \fi
227\ifdim 16383.999987 pt = 16383.999991 pt the same \else different \fi
228\stopbuffer
229
230\typebuffer[option=TEX]
231
232Watch the difference in dividing, the \type {/} rounds, while the \type {:}
233truncates.
234
235\startlines
236\getbuffer
237\stoplines
238
239You need to be aware of border cases, although in practice they never really
240are a problem:
241
242\startbuffer
243\ifdim \dimexpr16383.99997 pt/2\relax = \dimexpr 16383.99998 pt/2\relax
244    the same \else different
245\fi
246\ifdim \dimexpr16383.99997 pt:2\relax = \dimexpr 16383.99998 pt:2\relax
247    the same \else different
248\fi
249\stopbuffer
250
251\typebuffer[option=TEX]
252
253\startlines
254\getbuffer
255\stoplines
256
257\startbuffer
258\ifdim \dimexpr1.99997 pt/2\relax = \dimexpr 1.99998 pt/2\relax
259    the same \else different
260\fi
261\ifdim \dimexpr1.99997 pt:2\relax = \dimexpr 1.99998 pt:2\relax
262    the same \else different
263\fi
264\stopbuffer
265
266\typebuffer[option=TEX]
267
268\startlines
269\getbuffer
270\stoplines
271
272\startbuffer
273\ifdim \dimexpr1.999999 pt/2\relax = \dimexpr 1.9999995 pt/2\relax
274    the same \else different
275\fi
276\ifdim \dimexpr1.999999 pt:2\relax = \dimexpr 1.9999995 pt:2\relax
277    the same \else different
278\fi
279\stopbuffer
280
281\typebuffer[option=TEX]
282
283\startlines
284\getbuffer
285\stoplines
286
287This last case demonstrates that at some point the digits get dropped (still
288assuming that the fraction is within the maximum permitted) so these numbers then
289are the same. Anyway, this is not different in other programming languages and
290just something you need to be aware of.
291
292\stopsectionlevel
293
294\stopsectionlevel
295
296\startsectionlevel[title={\TEX\ primitives}]
297
298\startsectionlevel[title={\tex{if}}]
299
300I seldom use this one. Internally \TEX\ stores (and thinks) in terms of tokens.
301If you see for instance \type {\def} or \type {\dimen} or \type {\hbox} these all
302become tokens. But characters like \type {A} or {@} also become tokens. In this
303test primitive all non|-|characters are considered to be the same. In the next
304examples this is demonstrated.
305
306\startbuffer
307[\if AB yes\else nop\fi]
308[\if AA yes\else nop\fi]
309[\if CDyes\else nop\fi]
310[\if CCyes\else nop\fi]
311[\if\dimen\font yes\else nop\fi]
312[\if\dimen\font yes\else nop\fi]
313\stopbuffer
314
315\typebuffer[option=TEX]
316
317Watch how spaces after the two characters are kept: \inlinebuffer . This primitive looks
318at the next two tokens but when doing so it expands. Just look at the following:
319
320\startbuffer
321\def\AA{AA}%
322\def\AB{AB}%
323[\if\AA yes\else nop\fi]
324[\if\AB yes\else nop\fi]
325\stopbuffer
326
327\typebuffer[option=TEX]
328
329We get: \inlinebuffer .
330
331% protected macros
332
333\stopsectionlevel
334
335\startsectionlevel[title={\tex{ifcat}}]
336
337In \TEX\ characters (in the input) get interpreted according to their so called
338catcodes. The most common are letters (alphabetic) and and other (symbols) but
339for instance the backslash has the property that it starts a command, the dollar
340signs trigger math mode, while the curly braced deal with grouping. If for
341instance either or not the ampersand is special (for instance as column separator
342in tables) depends on the macro package.
343
344\startbuffer
345[\ifcat AB yes\else nop\fi]
346[\ifcat AA yes\else nop\fi]
347[\ifcat CDyes\else nop\fi]
348[\ifcat CCyes\else nop\fi]
349[\ifcat C1yes\else nop\fi]
350[\ifcat\dimen\font yes\else nop\fi]
351[\ifcat\dimen\font yes\else nop\fi]
352\stopbuffer
353
354\typebuffer[option=TEX]
355
356This time we also compare a letter with a number: \inlinebuffer . In that case
357the category codes differ (letter vs other) but in this test comparing the
358letters result in a match. This is a test that is used only once in \CONTEXT\ and
359even that occasion is dubious and will go away.
360
361You can use \type {\noexpand} to prevent expansion:
362
363\startbuffer
364\def\A{A}%
365\let\B B%
366\def\C{D}%
367\let\D D%
368[\ifcat\noexpand\A Ayes\else nop\fi]
369[\ifcat\noexpand\B Byes\else nop\fi]
370[\ifcat\noexpand\C Cyes\else nop\fi]
371[\ifcat\noexpand\C Dyes\else nop\fi]
372[\ifcat\noexpand\D Dyes\else nop\fi]
373\stopbuffer
374
375\typebuffer[option=TEX]
376
377We get: \inlinebuffer, so who still thinks that \TEX\ is easy to understand for a
378novice user?
379
380\stopsectionlevel
381
382\startsectionlevel[title={\tex{ifnum}}]
383
384This condition compares its argument with another one, separated by an \type {<},
385\type {=} or \type {>} character.
386
387\starttyping[option=TEX]
388\ifnum\scratchcounter<0
389    less than
390\else\ifnum\scratchcounter>0
391    more than
392\else
393    equal to
394\fi zero
395\stoptyping
396
397This is one of these situations where a dimension can be used instead. In that
398case the dimension is in scaled points.
399
400\starttyping[option=TEX]
401\ifnum\scratchdimen<0
402    less than
403\else\ifnum\scratchdimen>0
404    more than
405\else
406    equal to
407\fi zero
408\stoptyping
409
410Of course this equal treatment of a dimension and number is only true when the
411dimension is a register or box property.
412
413\stopsectionlevel
414
415\startsectionlevel[title={\tex{ifdim}}]
416
417This condition compares one dimension with another one, separated by an \type {<},
418\type {=} or \type {>} sign.
419
420\starttyping[option=TEX]
421\ifdim\scratchdimen<0pt
422    less than
423\else\ifdim\scratchdimen>0pt
424    more than
425\else
426    equal to
427\fi zero
428\stoptyping
429
430While when comparing numbers a dimension is a valid quantity but here you cannot
431mix them: something with a unit is expected.
432
433\stopsectionlevel
434
435\startsectionlevel[title={\tex{ifodd}}]
436
437This one can come in handy, although in \CONTEXT\ it is only used in checking for
438an odd of even page number.
439
440\startbuffer
441\scratchdimen  3sp
442\scratchcounter4
443
444\ifodd\scratchdimen   very \else not so \fi odd
445\ifodd\scratchcounter very \else not so \fi odd
446\stopbuffer
447
448\typebuffer[option=TEX]
449
450As with the previously discussed \type {\ifnum} you can use a dimension variable
451too, which is then interpreted as representing scaled points. Here we get:
452
453\startlines
454\getbuffer
455\stoplines
456
457\stopsectionlevel
458
459\startsectionlevel[title={\tex{ifvmode}}]
460
461This is a rather trivial check. It takes no arguments and just is true when we're
462in vertical mode. Here is an example:
463
464\startbuffer
465\hbox{\ifvmode\else\par\fi\ifvmode v\else h\fi mode}
466\stopbuffer
467
468\typebuffer[option=TEX]
469
470We're always in horizontal mode and issuing a \type {\par} inside a horizontal
471box doesn't change that, so we get: \ruledhbox{\inlinebuffer}.
472
473\stopsectionlevel
474
475\startsectionlevel[title={\tex{ifhmode}}]
476
477As with \type {\ifvmode} this one has no argument and just tells if we're in
478vertical mode.
479
480\startbuffer
481\vbox {
482    \noindent \ifhmode h\else v\fi mode
483    \par
484    \ifhmode h\else \noindent v\fi mode
485}
486\stopbuffer
487
488\typebuffer[option=TEX]
489
490You can use it for instance to trigger injection of code, or prevent that some
491content (or command) is done more than once:
492
493\startlinecorrection
494\ruledhbox{\inlinebuffer}
495\stoplinecorrection
496
497\stopsectionlevel
498
499\startsectionlevel[title={\tex{ifmmode}}]
500
501Math is something very \TEX\ so naturally you can check if you're in math mode.
502here is an example of using this test:
503
504\starttyping[option=TEX]
505\def\enforcemath#1{\ifmmode#1\else$ #1 $\fi}
506\stoptyping
507
508Of course in reality macros that do such things are more advanced than this one.
509
510\stopsectionlevel
511
512\startsectionlevel[title={\tex{ifinner}}]
513
514\startbuffer
515\def\ShowMode
516  {\ifhmode      \ifinner inner \fi hmode
517   \else\ifvmode \ifinner inner \fi vmode
518   \else\ifmmode \ifinner inner \fi mmode
519   \else         \ifinner inner \fi unset
520   \fi\fi\fi}
521\stopbuffer
522
523\typebuffer[option=TEX] \getbuffer
524
525\startbuffer
526\ShowMode \ShowMode
527
528\vbox{\ShowMode}
529
530\hbox{\ShowMode}
531
532$\ShowMode$
533
534$$\ShowMode$$
535\stopbuffer
536
537\typebuffer[option=TEX]
538
539The first line has two tests, where the first one changes the mode to horizontal
540simply because a text has been typeset. Watch how display math is not inner.
541
542\startpacked
543\startlines
544\getbuffer
545\stoplines
546\stoppacked
547
548By the way, moving the \type {\ifinner} test outside the branches (to the top of
549the macro) won't work because once the word \type {inner} is typeset we're no
550longer in vertical mode, if we were at all.
551
552\stopsectionlevel
553
554\startsectionlevel[title={\tex{ifvoid}}]
555
556A box is one of the basic concepts in \TEX. In order to understand this primitive
557we present four cases:
558
559\startbuffer
560\setbox0\hbox{}         \ifvoid0 void \else content \fi
561\setbox0\hbox{123}      \ifvoid0 void \else content \fi
562\setbox0\hbox{} \box0   \ifvoid0 void \else content \fi
563\setbox0\hbox to 10pt{} \ifvoid0 void \else content \fi
564\stopbuffer
565
566\typebuffer[option=TEX]
567
568In the first case, we have a box which is empty but it's not void. It helps to
569know that internally an hbox is actually an object with a pointer to a linked
570list of nodes. So, the first two can be seen as:
571
572\starttyping
573hlist -> [nothing]
574hlist -> 1 -> 2 -> 3 -> [nothing]
575\stoptyping
576
577but in any case there is a hlist. The third case puts something in a hlist but
578then flushes it. Now we have not even the hlist any more; the box register has
579become void. The last case is a variant on the first. It is an empty box with a
580given width. The outcome of the four lines (with a box flushed in between) is:
581
582\startlines
583\getbuffer
584\stoplines
585
586So, when you want to test if a box is really empty, you need to test also its
587dimensions, which can be up to three tests, depending on your needs.
588
589\startbuffer
590\setbox0\emptybox                  \ifvoid0 void\else content\fi
591\setbox0\emptybox        \wd0=10pt \ifvoid0 void\else content\fi
592\setbox0\hbox to 10pt {}           \ifvoid0 void\else content\fi
593\setbox0\hbox         {} \wd0=10pt \ifvoid0 void\else content\fi
594\stopbuffer
595
596\typebuffer[option=TEX]
597
598Setting a dimension of a void voix (empty) box doesn't make it less void:
599
600\startlines
601\getbuffer
602\stoplines
603
604\stopsectionlevel
605
606\startsectionlevel[title={\tex{ifhbox}}]
607
608This test takes a box number and gives true when it is an hbox.
609
610\stopsectionlevel
611
612\startsectionlevel[title={\tex{ifvbox}}]
613
614This test takes a box number and gives true when it is an vbox. Both a \type
615{\vbox} and \type {\vtop} are vboxes, the difference is in the height and depth
616and the baseline. In a \type {\vbox} the last line determines the baseline
617
618\startlinecorrection
619\ruledvbox{vbox or vtop\par vtop or vbox}
620\stoplinecorrection
621
622And in  a \type {\vtop} the first line takes control:
623
624\startlinecorrection
625\ruledvtop{vbox or vtop\par vtop or vbox}
626\stoplinecorrection
627
628but, once wrapped, both internally are just vlists.
629
630\stopsectionlevel
631
632\startsectionlevel[title={\tex{ifx}}]
633
634This test is actually used a lot in \CONTEXT: it compares two token(list)s:
635
636\startbuffer
637                   \ifx a b  Y\else N\fi
638                   \ifx ab   Y\else N\fi
639\def\A {a}\def\B{b}\ifx \A\B Y\else N\fi
640\def\A{aa}\def\B{a}\ifx \A\B Y\else N\fi
641\def\A {a}\def\B{a}\ifx \A\B Y\else N\fi
642\stopbuffer
643
644\typebuffer[option=TEX]
645
646Here the result is: \quotation{\inlinebuffer}. It does not expand the content, if
647you want that you need to use an \type {\edef} to create two (temporary) macros
648that get compared, like in:
649
650\starttyping[option=TEX]
651\edef\TempA{...}\edef\TempB{...}\ifx\TempA\TempB ...\else ...\fi
652\stoptyping
653
654\stopsectionlevel
655
656\startsectionlevel[title={\tex{ifeof}}]
657
658This test checks if a the pointer in a given input channel has reached its end.
659It is also true when the file is not present. The argument is a number which
660relates to the \type {\openin} primitive that is used to open files for reading.
661
662\stopsectionlevel
663
664\startsectionlevel[title={\tex{iftrue}}]
665
666It does what it says: always true.
667
668\stopsectionlevel
669
670\startsectionlevel[title={\tex{iffalse}}]
671
672It does what it says: always false.
673
674\stopsectionlevel
675
676\startsectionlevel[title={\tex{ifcase}}]
677
678The general layout of an \type {\ifcase} tests is as follows:
679
680\starttyping[option=TEX]
681\ifcase<number>
682    when zero
683\or
684    when one
685\or
686    when two
687\or
688    ...
689\else
690    when something else
691\fi
692\stoptyping
693
694As in other places a number is a sequence of signs followed by one of more digits
695
696\stopsectionlevel
697
698\stopsectionlevel
699
700\startsectionlevel[title={\ETEX\ primitives}]
701
702\startsectionlevel[title={\tex{ifdefined}}]
703
704This primitive was introduced for checking the existence of a macro (or primitive)
705and with good reason. Say that you want to know if \type {\MyMacro} is defined? One
706way to do that is:
707
708\startbuffer
709\ifx\MyMacro\undefined
710    {\bf undefined indeed}
711\fi
712\stopbuffer
713
714\typebuffer[option=TEX]
715
716This results in: \inlinebuffer , but is this macro really undefined? When \TEX\
717scans your source and sees a the escape character (the forward slash) it will
718grab the next characters and construct a control sequence from it. Then it finds
719out that there is nothing with that name and it will create a hash entry for a
720macro with that name but with no meaning. Because \type {\undefined} is also not
721defined, these two macros have the same meaning and therefore the \type {\ifx} is
722true. Imagine that you do this many times, with different macro names, then your
723hash can fill up. Also, when a user defined \type {\undefined} you're suddenly
724get a different outcome.
725
726In order to catch the last problem there is the option to test directly:
727
728\startbuffer
729\ifdefined\MyOtherMacro \else
730    {\bf also undefined}
731\fi
732\stopbuffer
733
734\typebuffer[option=TEX]
735
736This (or course) results in: \inlinebuffer, but the macro is still sort of
737defined (with no meaning). The next section shows how to get around this.
738
739\stopsectionlevel
740
741\startsectionlevel[title={\tex{ifcsname}}]
742
743A macro is often defined using a ready made name, as in:
744
745\starttyping[option=TEX]
746\def\OhYes{yes}
747\stoptyping
748
749The name is made from characters with catcode letter which means that you cannot
750use for instance digits or underscores unless you also give these characters that
751catcode, which is not that handy in a document. You can however use \type
752{\csname} to define a control sequence with any character in the name, like:
753
754\starttyping[option=TEX]
755\expandafter\def\csname Oh Yes : 1\endcsname{yes}
756\stoptyping
757
758Later on you can get this one with \type {\csname}:
759
760\starttyping[option=TEX]
761\csname Oh Yes : 1\endcsname
762\stoptyping
763
764However, if you say:
765
766\starttyping[option=TEX]
767\csname Oh Yes : 2\endcsname
768\stoptyping
769
770you won't get some result, nor a message about an undefined control sequence, but
771the name triggers a define anyway, this time not with no meaning (undefined) but
772as equivalent to \type {\relax}, which is why
773
774\starttyping[option=TEX]
775\expandafter\ifx\csname Oh Yes : 2\endcsname\relax
776    {\bf relaxed indeed}
777\fi
778\stoptyping
779
780is the way to test its existence. As with the test in the previous section,
781this can deplete the hash when you do lots of such tests. The way out of this
782is:
783
784\starttyping[option=TEX]
785\ifcsname Oh Yes : 2\endcsname \else
786    {\bf unknown indeed}
787\fi
788\stoptyping
789
790This time there is no hash entry created and therefore there is not even an
791undefined control sequence.
792
793In \LUATEX\ there is an option to return false in case of a messy expansion
794during this test, and in \LUAMETATEX\ that is default. This means that tests can
795be made quite robust as it is pretty safe to assume that names that make sense
796are constructed from regular characters and not boxes, font switches, etc.
797
798\stopsectionlevel
799
800\startsectionlevel[title={\tex{iffontchar}}]
801
802This test was also part of the \ETEX\ extensions and it can be used to see if
803a font has a character.
804
805\startbuffer
806\iffontchar\font`A
807    {\em This font has an A!}
808\fi
809\stopbuffer
810
811\typebuffer[option=TEX]
812
813And, as expected, the outcome is: \quotation {\inlinebuffer}. The test takes two
814arguments, the first being a font identifier and the second a character number,
815so the next checks are all valid:
816
817\starttyping[option=TEX]
818\iffontchar\font     `A yes\else nop\fi\par
819\iffontchar\nullfont `A yes\else nop\fi\par
820\iffontchar\textfont0`A yes\else nop\fi\par
821\stoptyping
822
823In the perspective of \LUAMETATEX\ I considered also supporting \type {\fontid}
824but it got a bit messy due to the fact that this primitive expands in a different
825way so this extension was rejected.
826
827\stopsectionlevel
828
829\startsectionlevel[title={\tex{unless}}]
830
831You can negate the results of a test by using the \type {\unless} prefix, so for
832instance you can replace:
833
834\starttyping[option=TEX]
835\ifdim\scratchdimen=10pt
836    \dosomething
837\else\ifdim\scratchdimen<10pt
838    \dosomething
839\fi\fi
840\stoptyping
841
842by:
843
844\starttyping[option=TEX]
845\unless\ifdim\scratchdimen>10pt
846    \dosomething
847\fi
848\stoptyping
849
850\stopsectionlevel
851
852\stopsectionlevel
853
854\startsectionlevel[title={\LUATEX\ primitives}]
855
856\startsectionlevel[title={\tex{ifincsname}}]
857
858As it had no real practical usage uit might get dropped in \LUAMETATEX, so it
859will not be discussed here.
860
861\stopsectionlevel
862
863\startsectionlevel[title={\tex{ifprimitive}}]
864
865As it had no real practical usage due to limitations, this one is not available
866in \LUAMETATEX\ so it will not be discussed here.
867
868\stopsectionlevel
869
870\startsectionlevel[title={\tex{ifabsnum}}]
871
872This test is inherited from \PDFTEX\ and behaves like \type {\ifnum} but first
873turns a negative number into a positive one.
874
875\stopsectionlevel
876
877\startsectionlevel[title={\tex{ifabsdim}}]
878
879This test is inherited from \PDFTEX\ and behaves like \type {\ifdim} but first
880turns a negative dimension into a positive one.
881
882\stopsectionlevel
883
884\startsectionlevel[title={\tex{ifcondition}}]
885
886This is not really a test but in order to unstand that you need to know how
887\TEX\ internally deals with tests.
888
889\starttyping[option=TEX]
890\ifdimen\scratchdimen>10pt
891    \ifdim\scratchdimen<20pt
892        result a
893    \else
894        result b
895    \fi
896\else
897    result c
898\fi
899\stoptyping
900
901When we end up in the branch of \quotation {result a} we need to skip two \type
902{\else} branches after we're done. The \type {\if..} commands increment a level
903while the \type {\fi} decrements a level. The \type {\else} needs to be skipped
904here. In other cases the true branch needs to be skipped till we end up a the
905right \type {\else}. When doing this skipping, \TEX\ is not interested in what it
906encounters beyond these tokens and this skipping (therefore) goes real fast but
907it does see nested conditions and doesn't interpret grouping related tokens.
908
909A side effect of this is that the next is not working as expected:
910
911\starttyping[option=TEX]
912\def\ifmorethan{\ifdim\scratchdimen>}
913\def\iflessthan{\ifdim\scratchdimen<}
914
915\ifmorethan10pt
916    \iflessthan20pt
917        result a
918    \else
919        result b
920    \fi
921\else
922    result c
923\fi
924\stoptyping
925
926The \type{\iflessthan} macro is not seen as an \type {\if...} so the nesting gets
927messed up. The solution is to fool the scanner in thinking that it is. Say we have:
928
929\startbuffer
930\scratchdimen=25pt
931
932\def\ifmorethan{\ifdim\scratchdimen>}
933\def\iflessthan{\ifdim\scratchdimen<}
934\stopbuffer
935
936\typebuffer[option=TEX] \getbuffer
937
938and:
939
940\startbuffer
941\ifcondition\ifmorethan10pt
942    \ifcondition\iflessthan20pt
943        result a
944    \else
945        result b
946    \fi
947\else
948    result c
949\fi
950\stopbuffer
951
952\typebuffer[option=TEX]
953
954When we expand this snippet we get: \quotation {\inlinebuffer} and no error
955concerning a failure in locating the right \type {\fi's}. So, when scanning the
956\type {\ifcondition} is seen as a valid \type {\if...} but when the condition is
957really expanded it gets ignored and the \type {\ifmorethan} has better come up
958with a match or not.
959
960In this perspective it is also worth mentioning that nesting problems can be
961avoided this way:
962
963\starttyping[option=TEX]
964\def\WhenTrue {something \iftrue  ...}
965\def\WhenFalse{something \iffalse ...}
966
967\ifnum\scratchcounter>123
968    \let\next\WhenTrue
969\else
970    \let\next\WhenFalse
971\fi
972\next
973\stoptyping
974
975This trick is mentioned in The \TeX book and can also be found in the plain \TEX\
976format. A variant is this:
977
978\starttyping[option=TEX]
979\ifnum\scratchcounter>123
980    \expandafter\WhenTrue
981\else
982    \expandafter\WhenFalse
983\fi
984\stoptyping
985
986but using \type {\expandafter} can be quite intimidating especially when there
987are multiple in a row. It can also be confusing. Take this: an \type
988{\ifcondition} expects the code that follows to produce a test. So:
989
990\starttyping[option=TEX]
991\def\ifwhatever#1%
992  {\ifdim#1>10pt
993      \expandafter\iftrue
994   \else
995      \expandafter\iffalse
996   \fi}
997
998\ifcondition\ifwhatever{10pt}
999    result a
1000\else
1001    result b
1002\fi
1003\stoptyping
1004
1005This will not work! The reason is in the already mentioned fact that when we end
1006up in the greater than \type {10pt} case, the scanner will happily push the \type
1007{\iftrue} after the \type {\fi}, which is okay, but when skipping over the \type
1008{\else} it sees a nested condition without matching \type {\fi}, which makes ity
1009fail. I will spare you a solution with lots of nasty tricks, so here is the clean
1010solution using \type {\ifcondition}:
1011
1012\starttyping[option=TEX]
1013\def\truecondition {\iftrue}
1014\def\falsecondition{\iffalse}
1015
1016\def\ifwhatever#1%
1017  {\ifdim#1>10pt
1018      \expandafter\truecondition
1019   \else
1020      \expandafter\falsecondition
1021   \fi}
1022
1023\ifcondition\ifwhatever{10pt}
1024    result a
1025\else
1026    result b
1027\fi
1028\stoptyping
1029
1030It will be no surprise that the two macros at the top are predefined in \CONTEXT.
1031It might be more of a surprise that at the time of this writing the usage in
1032\CONTEXT\ of this \type {\ifcondition} primitive is rather minimal. But that
1033might change.
1034
1035As a further teaser I'll show another simple one,
1036
1037\startbuffer
1038\def\HowOdd#1{\unless\ifnum\numexpr ((#1):2)*2\relax=\numexpr#1\relax}
1039
1040\ifcondition\HowOdd{1}very \else not so \fi odd
1041\ifcondition\HowOdd{2}very \else not so \fi odd
1042\ifcondition\HowOdd{3}very \else not so \fi odd
1043\stopbuffer
1044
1045\typebuffer[option=TEX]
1046
1047This renders:
1048
1049\startlines
1050\getbuffer
1051\stoplines
1052
1053The code demonstrates several tricks. First of all we use \type {\numexpr} which
1054permits more complex arguments, like:
1055
1056\starttyping[option=TEX]
1057\ifcondition\HowOdd{4+1}very \else not so \fi odd
1058\ifcondition\HowOdd{2\scratchcounter+9}very \else not so \fi odd
1059\stoptyping
1060
1061Another trick is that we use an integer division (the \type {:}) which is an
1062operator supported by \LUAMETATEX .
1063
1064\stopsectionlevel
1065
1066\stopsectionlevel
1067
1068\startsectionlevel[title={\LUAMETATEX\ primitives}]
1069
1070\startsectionlevel[title={\tex{ifcmpnum}}]
1071
1072This one is part of s set of three tests that all are a variant of a \type
1073{\ifcase} test. A simple example of the first test is this:
1074
1075\starttyping[option=TEX]
1076\ifcmpnum 123 345 less \or equal \else more \fi
1077\stoptyping
1078
1079The test scans for two numbers, which of course can be registers or expressions,
1080and sets the case value to 0, 1 or 2, which means that you then use the normal
1081\type {\or} and \type {\else} primitives for follow up on the test.
1082
1083\stopsectionlevel
1084
1085\startsectionlevel[title={\tex{ifchknum}}]
1086
1087This test scans a number and when it's okay sets the case value to 1, and otherwise
1088to 2. So you can do the next:
1089
1090\starttyping[option=TEX]
1091\ifchknum 123\or good \else bad \fi
1092\ifchknum bad\or good \else bad \fi
1093\stoptyping
1094
1095An error message is suppressed and the first \type {\or} can be seen as a sort of
1096recovery token, although in fact we just use the fast scanner mode that comes
1097with the \type {\ifcase}: because the result is 1 or 2, we never see invalid
1098tokens.
1099
1100\stopsectionlevel
1101
1102\startsectionlevel[title={\tex{ifnumval}}]
1103
1104A sort of combination of the previous two is \type {\ifnumval} which checks a
1105number but also if it's less, equal or more than zero:
1106
1107\starttyping[option=TEX]
1108\ifnumval 123\or less \or equal \or more \else error \fi
1109\ifnumval bad\or less \or equal \or more \else error \fi
1110\stoptyping
1111
1112You can decide to ignore the bad number or do something that makes more sense.
1113Often the to be checked value will be the content of a macro or an argument like
1114\type {#1}.
1115
1116\stopsectionlevel
1117
1118\startsectionlevel[title={\tex{ifcmpdim}}]
1119
1120This test is like \type {\ifcmpnum} but for dimensions.
1121
1122\stopsectionlevel
1123
1124\startsectionlevel[title={\tex{ifchkdim}}]
1125
1126This test is like \type {\ifchknum} but for dimensions. The last checked value is
1127available as \type {\lastchknum}.
1128
1129\stopsectionlevel
1130
1131\startsectionlevel[title={\tex{ifdimval}}]
1132
1133This test is like \type {\ifnumval} but for dimensions. The last checked value is
1134available as \type {\lastchkdim}
1135
1136\stopsectionlevel
1137
1138\startsectionlevel[title={\tex{iftok}}]
1139
1140Although this test is still experimental it can be used. What happens is that
1141two to be compared \quote {things} get scanned for. For each we first gobble
1142spaces and \type {\relax} tokens. Then we can have several cases:
1143
1144\startitemize[n,packed]
1145    \startitem
1146        When we see a left brace, a list of tokens is scanned upto the
1147        matching right brace.
1148    \stopitem
1149    \startitem
1150        When a reference to a token register is seen, that register is taken as
1151        value.
1152    \stopitem
1153    \startitem
1154        When a reference to an internal token register is seen, that register is
1155        taken as value.
1156    \stopitem
1157    \startitem
1158        When a macro is seen, its definition becomes the to be compared value.
1159    \stopitem
1160    \startitem
1161        When a number is seen, the value of the corresponding register is taken
1162    \stopitem
1163\stopitemize
1164
1165An example of the first case is:
1166
1167\starttyping[option=TEX]
1168\iftok {abc} {def}%
1169  ...
1170\else
1171  ...
1172\fi
1173\stoptyping
1174
1175The second case goes like this:
1176
1177\starttyping[option=TEX]
1178\iftok\scratchtoksone\scratchtokstwo
1179  ...
1180\else
1181  ...
1182\fi
1183\stoptyping
1184
1185Case one and four mixed:
1186
1187\starttyping[option=TEX]
1188\iftok{123}\TempX
1189  ...
1190\else
1191  ...
1192\fi
1193\stoptyping
1194
1195The last case is more a catch: it will issue an error when no number is given.
1196Eventually that might become a bit more clever (depending on our needs.)
1197
1198\stopsectionlevel
1199
1200\startsectionlevel[title={\tex{ifcstok}}]
1201
1202There is a subtle difference between this one and \type {iftok}: spaces
1203and \type {\relax} tokens are skipped but nothing gets expanded. So, when
1204we arrive at the to be compared \quote {things} we look at what is there,
1205as|-|is.
1206
1207\stopsectionlevel
1208
1209\startsectionlevel[title={\tex{iffrozen}}]
1210
1211{\em This is an experimental test.} Commands can be defined with the \type
1212{\frozen} prefix and this test can be used to check if that has been the case.
1213
1214\stopsectionlevel
1215
1216\startsectionlevel[title={\tex{ifprotected}}]
1217
1218Commands can be defined with the \type {\protected} prefix (or in \CONTEXT, for
1219historic reasons, with \type {\unexpanded}) and this test can be used to check if
1220that has been the case.
1221
1222\stopsectionlevel
1223
1224\startsectionlevel[title={\tex{ifusercmd}}]
1225
1226{\em This is an experimental test.} It can be used to see if the command is
1227defined at the user level or is a build in one. This one might evolve.
1228
1229\stopsectionlevel
1230
1231\startsectionlevel[title={\tex{ifarguments}}]
1232
1233This conditional can be used to check how many arguments were matched. It only
1234makes sense when used with macros defined with the \type {\tolerant} prefix
1235and|/|or when the sentinel \type {\ignorearguments} after the arguments is used.
1236More details can be found in the lowlevel macros manual.
1237
1238\stopsectionlevel
1239
1240\startsectionlevel[title={\tex{orelse}}]
1241
1242This it not really a test primitive but it does act that way. Say that we have this:
1243
1244\starttyping[option=TEX]
1245\ifdim\scratchdimen>10pt
1246    case 1
1247\else\ifdim\scratchdimen<20pt
1248    case 2
1249\else\ifcount\scratchcounter>10
1250    case 3
1251\else\ifcount\scratchcounter<20
1252    case 4
1253\fi\fi\fi\fi
1254\stoptyping
1255
1256A bit nicer looks this:
1257
1258\starttyping[option=TEX]
1259\ifdim\scratchdimen>10pt
1260    case 1
1261\orelse\ifdim\scratchdimen<20pt
1262    case 2
1263\orelse\ifcount\scratchcounter>10
1264    case 3
1265\orelse\ifcount\scratchcounter<20
1266    case 4
1267\fi
1268\stoptyping
1269
1270% We stay at the same level and the only test that cannot be used this way is \type
1271% {\ifcondition} but that is no real problem. Sometimes a more flat test tree had
1272
1273We stay at the same level. Sometimes a more flat test tree had advantages but if
1274you think that it gives better performance then you will be disappointed. The
1275fact that we stay at the same level is compensated by a bit more parsing, so
1276unless you have millions such cases (or expansions) it might make a bit of a
1277difference. As mentioned, I'm a bit sensitive for how code looks so that was the
1278main motivation for introducing it. There is a companion \type {\orunless}
1279continuation primitive.
1280
1281A rather neat trick is the definition of \type {\quitcondition}:
1282
1283\starttyping[option=TEX]
1284\def\quitcondition{\orelse\iffalse}
1285\stoptyping
1286
1287This permits:
1288
1289\starttyping[option=TEX]
1290\ifdim\scratchdimen>10pt
1291    case 1a
1292    \quitcondition
1293    case 4b
1294\fi
1295\stoptyping
1296
1297where, of course, the quitting normally is the result of some intermediate extra
1298test. But let me play safe here: beware of side effects.
1299
1300\stopsectionlevel
1301
1302\stopsectionlevel
1303
1304\startsectionlevel[title={For the brave}]
1305
1306\startsectionlevel[title={Full expansion}]
1307
1308If you don't understand the following code, don't worry. There is seldom much
1309reason to go this complex but obscure \TEX\ code attracts some users so \unknown
1310
1311When you have a macro that has for instance assignments, and when you expand that
1312macro inside an \type {\edef}, these assignments are not actually expanded but
1313tokenized. In \LUAMETATEX\ there is a way to apply these assignments without side
1314effects and that feature can be used to write a fully expandable user test. For
1315instance:
1316
1317\startbuffer
1318\def\truecondition {\iftrue}
1319\def\falsecondition{\iffalse}
1320
1321\def\fontwithidhaschar#1#2%
1322  {\beginlocalcontrol
1323   \scratchcounter\numexpr\fontid\font\relax
1324   \setfontid\numexpr#1\relax
1325   \endlocalcontrol
1326   \iffontchar\font\numexpr#2\relax
1327      \beginlocalcontrol
1328      \setfontid\scratchcounter
1329      \endlocalcontrol
1330      \expandafter\truecondition
1331   \else
1332      \expandafter\falsecondition
1333   \fi}
1334\stopbuffer
1335
1336\typebuffer[option=TEX] \getbuffer
1337
1338The \type {\iffontchar} test doesn't handle numeric font id, simply because
1339at the time it was added to \ETEX, there was no access to these id's. Now we
1340can do:
1341
1342\startbuffer
1343\edef\foo{\fontwithidhaschar{1} {75}yes\else nop\fi} \meaning\foo
1344\edef\foo{\fontwithidhaschar{1}{999}yes\else nop\fi} \meaning\foo
1345
1346[\ifcondition\fontwithidhaschar{1} {75}yes\else nop\fi]
1347[\ifcondition\fontwithidhaschar{1}{999}yes\else nop\fi]
1348\stopbuffer
1349
1350\typebuffer[option=TEX]
1351
1352These result in:
1353
1354\startlines
1355\getbuffer
1356\stoplines
1357
1358If you remove the \type {\immediateassignment} in the definition above then the
1359typeset results are still the same but the meanings of \type {\foo} look
1360different: they contain the assignments and the test for the character is
1361actually done when constructing the content of the \type {\edef}, but for the
1362current font. So, basically that test is now useless.
1363
1364\stopsectionlevel
1365
1366\startsectionlevel[title={User defined if's}]
1367
1368There is a \type {\newif} macro that defines three other macros:
1369
1370\starttyping[option=TEX]
1371\newif\ifOnMyOwnTerms
1372\stoptyping
1373
1374After this, not only \type {\ifOnMyOwnTerms} is defined, but also:
1375
1376\starttyping[option=TEX]
1377\OnMyOwnTermstrue
1378\OnMyOwnTermsfalse
1379\stoptyping
1380
1381These two actually are macros that redefine \type {\ifOnMyOwnTerms} to be either
1382equivalent to \type {\iftrue} and \type {\iffalse}. The (often derived from plain
1383\TEX) definition of \type {\newif} is a bit if a challenge as it has to deal with
1384removing the \type {if} in order to create the two extra macros and also make
1385sure that it doesn't get mixed up in a catcode jungle.
1386
1387In \CONTEXT\ we have a variant:
1388
1389\starttyping[option=TEX]
1390\newconditional\MyConditional
1391\stoptyping
1392
1393that can be used with:
1394
1395\starttyping[option=TEX]
1396\settrue\MyConditional
1397\setfalse\MyConditional
1398\stoptyping
1399
1400and tested like:
1401
1402\starttyping[option=TEX]
1403\ifconditional\MyConditional
1404    ...
1405\else
1406    ...
1407\fi
1408\stoptyping
1409
1410This one is cheaper on the hash and doesn't need the two extra macros per test.
1411The price is the use of \type {\ifconditional}, which is {\em not} to confused
1412with \type {\ifcondition} (it has bitten me already a few times).
1413
1414\stopsectionlevel
1415
1416\stopsectionlevel
1417
1418\startsectionlevel[title=Relaxing]
1419
1420When \TEX\ scans for a number or dimension it has to check tokens one by one. On
1421the case of a number, the scanning stops when there is no digit, in the case of a
1422dimension the unit determine the end of scanning. In the case of a number, when a
1423token is not a digit that token gets pushed back. When digits are scanned a
1424trailing space or \type {\relax} is pushed back. Instead of a number of dimension
1425made from digits, periods and units, the scanner also accepts registers, both the
1426direct accessors like \type {\count} and \type {\dimen} and those represented by
1427one token. Take these definitions:
1428
1429\startbuffer
1430\newdimen\MyDimenA \MyDimenA=1pt  \dimen0=\MyDimenA
1431\newdimen\MyDimenB \MyDimenB=2pt  \dimen2=\MyDimenB
1432\stopbuffer
1433
1434\typebuffer \getbuffer
1435
1436I will use these to illustrate the side effects of scanning. Watch the spaces
1437in the result.
1438
1439% \startbuffer[a]
1440% \testfeatureonce{1000000}{
1441%     \whatever{1pt}{2pt}%
1442%     \whatever{1pt}{1pt}%
1443%     \whatever{\dimen0}{\dimen2}%
1444%     \whatever{\dimen0}{\dimen0}%
1445%     \whatever\MyDimenA\MyDimenB
1446%     \whatever\MyDimenA\MyDimenB
1447% } \elapsedtime
1448% \stopbuffer
1449
1450\startbuffer[b]
1451\starttabulate[|T|T|]
1452\NC \type {\whatever{1pt}{2pt}}         \NC \edef\temp{\whatever        {1pt}{2pt}}[\meaning\temp] \NC \NR
1453\NC \type {\whatever{1pt}{1pt}}         \NC \edef\temp{\whatever        {1pt}{1pt}}[\meaning\temp] \NC \NR
1454\NC \type {\whatever{\dimen0}{\dimen2}} \NC \edef\temp{\whatever{\dimen0}{\dimen2}}[\meaning\temp] \NC \NR
1455\NC \type {\whatever{\dimen0}{\dimen0}} \NC \edef\temp{\whatever{\dimen0}{\dimen0}}[\meaning\temp] \NC \NR
1456\NC \type {\whatever\MyDimenA\MyDimenB} \NC \edef\temp{\whatever\MyDimenA\MyDimenB}[\meaning\temp] \NC \NR
1457\NC \type {\whatever\MyDimenA\MyDimenB} \NC \edef\temp{\whatever\MyDimenA\MyDimenB}[\meaning\temp] \NC \NR
1458\stoptabulate
1459\stopbuffer
1460
1461First I show what effect we want to avoid. When second argument contains a number
1462(digits) the zero will become part of it so we actually check \type {\dimen00}
1463here.
1464
1465\startbuffer[c]
1466\def\whatever#1#2%
1467  {\ifdim#1=#20\else1\fi}
1468\stopbuffer
1469
1470\typebuffer[c] \getbuffer[c,b]
1471
1472The solution is to add a space but watch how that one can end up in the result:
1473
1474\startbuffer[c]
1475\def\whatever#1#2%
1476  {\ifdim#1=#2 0\else1\fi}
1477\stopbuffer
1478
1479\typebuffer[c] \getbuffer[c,b]
1480
1481A variant is using \type {\relax} and this time we get this token retained in
1482the output.
1483
1484\startbuffer[c]
1485\def\whatever#1#2%
1486  {\ifdim#1=#2\relax0\else1\fi}
1487\stopbuffer
1488
1489\typebuffer[c] \getbuffer[c,b]
1490
1491A solution that doesn't have side effects of forcing the end of a number (using a
1492space or \type {\relax} is one where we use expressions. The added overhead of
1493scanning expressions is taken for granted because the effect is what we like:
1494
1495\startbuffer[c]
1496\def\whatever#1#2%
1497  {\ifdim\dimexpr#1\relax=\dimexpr#2\relax0\else1\fi}
1498\stopbuffer
1499
1500\typebuffer[c] \getbuffer[c,b]
1501
1502Just for completeness we show a more obscure trick: this one hides assignments to
1503temporary variables. Although performance is okay, it is the least efficient
1504one so far.
1505
1506\ifdefined\beginlocalcontrol
1507
1508\startbuffer[c]
1509\def\whatever#1#2%
1510  {\beginlocalcontrol
1511   \MyDimenA#1\relax
1512   \MyDimenB#2\relax
1513   \endlocalcontrol
1514   \ifdim\MyDimenA=\MyDimenB0\else1\fi}
1515\stopbuffer
1516
1517\typebuffer[c] \getbuffer[c,b]
1518
1519\fi
1520
1521It is kind of a game to come up with alternatives but for sure those involve
1522dirty tricks and more tokens (and runtime). The next can be considered a dirty
1523trick too: we use a special variant of \type {\relax}. When a number is scanned
1524it acts as relax, but otherwise it just is ignored and disappears.
1525
1526\ifdefined\norelax\else\let\norelax\relax\fi
1527
1528\startbuffer[c]
1529\def\whatever#1#2%
1530  {\ifdim#1=#2\norelax0\else1\fi}
1531\stopbuffer
1532
1533\typebuffer[c] \getbuffer[c,b]
1534
1535\stopsectionlevel
1536
1537\popoverloadmode
1538
1539\stopdocument
1540
1541% \def\foo{foo=bar}
1542% \def\oof{foo!bar}
1543% \scratchtoks{=}
1544
1545% \ifhasxtoks\scratchtoks{foo!bar} YES\else NOP\fi\par
1546% \ifhasxtoks\scratchtoks{foo=bar} YES\else NOP\fi\par
1547
1548% \showluatokens\foo
1549
1550% \ifhastoks\scratchtoks\oof YES\else NOP\fi\par
1551% \ifhastoks\scratchtoks\foo YES\else NOP\fi\par
1552