texconditional.c /size: 67 Kb    last modification: 2025-02-21 11:03
1/*
2    See license.txt in the root of this project.
3*/
4
5# include "luametatex.h"
6
7/*tex
8
9    In \LUAMETATEX\ The condition code has been upgraded. Bits and pieces have been optimized and
10    on top of the extra checks in \LUATEX|\ we have a few more here. In order to get nicer looking
11    nested conditions |\orelse| has been introduced. Some conditionals are not really needed but
12    they give less noise when tracing macros. It's also possible to let \LUA\ code behave like
13    a test.
14
15*/
16
17/*tex
18
19    We consider now the way \TEX\ handles various kinds of |\if| commands. Conditions can be inside
20    conditions, and this nesting has a stack that is independent of the |save_stack|.
21
22    Four global variables represent the top of the condition stack: |cond_ptr| points to
23    pushed-down entries, if any; |if_limit| specifies the largest code of a |fi_or_else| command
24    that is syntactically legal; |cur_if| is the name of the current type of conditional; and
25    |if_line| is the line number at which it began.
26
27    If no conditions are currently in progress, the condition stack has the special state
28    |cond_ptr = null|, |if_limit = normal|, |cur_if = 0|, |if_line = 0|. Otherwise |cond_ptr|
29    points to a two-word node; the |type|, |subtype|, and |link| fields of the first word contain
30    |if_limit|, |cur_if|, and |cond_ptr| at the next level, and the second word contains the
31    corresponding |if_line|.
32
33    In |cond_ptr| we keep track of the top of the condition stack while |if_limit| holds the upper
34    bound on |fi_or_else| codes. The type of conditional being worked on is stored in cur_if and
35    |if_line| keeps track of the line where that conditional began. When we skip conditional text,
36    |skip_line| keeps track of the line number where skipping began, for use in error messages.
37
38    All these variables are collected in:
39
40*/
41
42condition_state_info lmt_condition_state = {
43    .cond_ptr      = null,
44    .cur_if        = 0,
45    .if_limit      = 0,
46    .cur_unless    = 0,
47    .if_unless     = 0,
48    .if_step       = 0,
49    .unused        = 0,
50    .if_line       = 0,
51    .if_nesting    = 0,
52    .skip_line     = 0,
53    .chk_integer   = 0,
54    .chk_dimension = 0,
55};
56
57/*tex
58
59    Here is a procedure that ignores text until coming to an |\or|, |\else|, or |\fi| at level zero
60    of |\if| \unknown |\fi| nesting. After it has acted, |cur_chr| will indicate the token that was
61    found, but |cur_tok| will not be set (because this makes the procedure run faster).
62
63    With |l| we keep track of the level of |\if|\unknown|\fi| nesting and |scanner_status| let us
64    return to the entry status. The |pass_text| function only returns when we have a |fi_or_else|.
65
66*/
67
68static void tex_aux_pass_text(void)
69{
70    int level = 0;
71    int status = lmt_input_state.scanner_status;
72    lmt_input_state.scanner_status = scanner_is_skipping;
73    lmt_condition_state.skip_line = lmt_input_state.input_line;
74    while (1) {
75        tex_get_next();
76        if (cur_cmd == if_test_cmd) {
77            switch (cur_chr) {
78                case fi_code:
79                    if (level == 0) {
80                        lmt_input_state.scanner_status = status;
81                        return;
82                    } else {
83                        --level;
84                        break;
85                    }
86                case else_code:
87                case or_code:
88                    if (level == 0) {
89                        lmt_input_state.scanner_status = status;
90                        return;
91                    } else {
92                        break;
93                    }
94                case or_else_code:
95                case or_unless_code:
96                    tex_get_next_non_spacer();
97                    /*tex So we skip the token after |\orelse| or |\orunless| without testing it! */
98                    break;
99                default:
100                   ++level;
101                   break;
102            }
103        }
104    }
105}
106
107/*tex
108    We return when we have a |fi_or_else| or when we have a valid |or_else| followed by an
109    |if_test_cmd|.
110*/
111
112static int tex_aux_pass_text_x(int tracing_ifs, int tracing_commands)
113{
114    int level = 0;
115    int status = lmt_input_state.scanner_status;
116    lmt_input_state.scanner_status = scanner_is_skipping;
117    lmt_condition_state.skip_line = lmt_input_state.input_line;
118    while (1) {
119        tex_get_next();
120        if (cur_cmd == if_test_cmd) {
121            switch (cur_chr) {
122                case if_code:
123                   ++level;
124                   break;
125                case fi_code:
126                    if (level == 0) {
127                        lmt_input_state.scanner_status = status;
128                        return 0;
129                    } else {
130                        --level;
131                        break;
132                    }
133                case else_code:
134                case or_code:
135                    if (level == 0) {
136                        lmt_input_state.scanner_status = status;
137                        return 0;
138                    } else {
139                        break;
140                    }
141                case or_else_code:
142                case or_unless_code:
143                    if (level == 0) {
144                        int unless = cur_chr == or_unless_code;
145                        if (tracing_commands > 1) {
146                            tex_begin_diagnostic();
147                            tex_print_str(unless ? "{orunless}" : "{orelse}");
148                            tex_end_diagnostic();
149                        } else if (tracing_ifs) {
150                            tex_show_cmd_chr(cur_cmd, cur_chr);
151                        }
152                        tex_get_next_non_spacer();
153                        if (lmt_condition_state.if_limit == if_code) {
154                            if (cur_cmd == if_test_cmd && cur_chr >= first_real_if_test_code) {
155                                /* okay */
156                            } else {
157                                tex_handle_error(
158                                    normal_error_type,
159                                    unless ? "No condition after \\orunless" : "No condition after \\orelse",
160                                    "I'd expected a proper if test command."
161                                );
162                            }
163                            lmt_input_state.scanner_status = status;
164                            return unless;
165                        }
166                    } else {
167                        --level;
168                    }
169                    break;
170                default:
171                   ++level;
172                   break;
173            }
174        }
175    }
176}
177
178/*tex
179
180    When we begin to process a new |\if|, we set |if_limit = if_code|; then, if |\or| or |\else| or
181    |\fi| occurs before the current |\if| condition has been evaluated, |\relax| will be inserted.
182    For example, a sequence of commands like |\ifvoid 1 \else ... \fi| would otherwise require
183    something after the |1|.
184
185    When a conditional ends that was apparently started in a different input file, the |if_warning|
186    procedure is invoked in order to update the |if_stack|. If moreover |\tracingnesting| is
187    positive we want to give a warning message (with the same complications as above).
188
189*/
190
191static void tex_aux_if_warning(void)
192{
193    /*tex Do we need a warning? */
194    bool warning = false;
195    int index = lmt_input_state.in_stack_data.ptr;
196    lmt_input_state.base_ptr = lmt_input_state.input_stack_data.ptr;
197    /*tex Store current state. */
198    lmt_input_state.input_stack[lmt_input_state.base_ptr] = lmt_input_state.cur_input;
199    while (lmt_input_state.in_stack[index].if_ptr == lmt_condition_state.cond_ptr) {
200        /*tex Set variable |w| to. */
201        if (tracing_nesting_par > 0) {
202            while ((lmt_input_state.input_stack[lmt_input_state.base_ptr].state == token_list_state) || (lmt_input_state.input_stack[lmt_input_state.base_ptr].index > index)) {
203                --lmt_input_state.base_ptr;
204            }
205            if (lmt_input_state.input_stack[lmt_input_state.base_ptr].name > 17) {
206                warning = true;
207            }
208        }
209        lmt_input_state.in_stack[index].if_ptr = node_next(lmt_condition_state.cond_ptr);
210        --index;
211    }
212    if (warning) {
213        tex_begin_diagnostic();
214        tex_print_format("[conditional: end of %C%L of a different file]", if_test_cmd, lmt_condition_state.cur_if, lmt_condition_state.if_line);
215        tex_end_diagnostic();
216        if (tracing_nesting_par > 1) {
217            tex_show_context();
218        }
219        if (lmt_error_state.history == spotless) {
220            lmt_error_state.history = warning_issued;
221        }
222    }
223}
224
225/*tex 
226    We can consider a dedicated condition stack so that we can copy faster. Or we can just emulate
227    an if node in |lmt_condition_state|. 
228*/
229
230static void tex_aux_push_condition_stack(int code, int unless)
231{
232    halfword p = tex_get_node(if_node_size);
233    node_type(p) = if_node;
234    node_subtype(p) = 0; /* unused */
235    node_next(p) = lmt_condition_state.cond_ptr;
236    if_limit_type(p) = (quarterword) lmt_condition_state.if_limit;
237    if_limit_subtype(p) = (quarterword) lmt_condition_state.cur_if;
238    if_limit_step(p) = (singleword) lmt_condition_state.cur_unless;
239    if_limit_unless(p) = (singleword) lmt_condition_state.if_unless;
240    if_limit_stepunless(p) = (singleword) lmt_condition_state.if_unless;
241    if_limit_line(p) = lmt_condition_state.if_line;
242    lmt_condition_state.cond_ptr = p;
243    lmt_condition_state.cur_if = cur_chr;
244    lmt_condition_state.cur_unless = unless;
245    lmt_condition_state.if_step = code;
246    lmt_condition_state.if_limit = if_code;
247    lmt_condition_state.if_line = lmt_input_state.input_line;
248    ++lmt_condition_state.if_nesting;
249}
250
251static void tex_aux_pop_condition_stack(void)
252{
253    halfword p;
254    if (lmt_input_state.in_stack[lmt_input_state.in_stack_data.ptr].if_ptr == lmt_condition_state.cond_ptr) {
255        /*tex
256            Conditionals are possibly not properly nested with files. This test can become an
257            option.
258        */
259        tex_aux_if_warning();
260    }
261    p = lmt_condition_state.cond_ptr;
262    --lmt_condition_state.if_nesting;
263    lmt_condition_state.if_line = if_limit_line(p);
264    lmt_condition_state.cur_if = if_limit_subtype(p);
265    lmt_condition_state.cur_unless = if_limit_unless(p);
266    lmt_condition_state.if_step = if_limit_step(p);
267    lmt_condition_state.if_unless = if_limit_stepunless(p);
268    lmt_condition_state.if_limit = if_limit_type(p);
269    lmt_condition_state.cond_ptr = node_next(p);
270    tex_free_node(p, if_node_size);
271}
272
273/*
274    void tex_quit_fi(void) 
275    {
276        tex_aux_pop_condition_stack();
277    }
278*/
279
280/*tex
281    Here's a procedure that changes the |if_limit| code corresponding to a given value of
282    |cond_ptr|.
283*/
284
285static inline void tex_aux_change_if_limit(int l, halfword p)
286{
287    if (p == lmt_condition_state.cond_ptr) {
288        lmt_condition_state.if_limit = l;
289    } else {
290        halfword q = lmt_condition_state.cond_ptr;
291        while (q) {
292            if (node_next(q) == p) {
293                if_limit_type(q) = (quarterword) l;
294                return;
295            } else {
296                q = node_next(q);
297            }
298        }
299        tex_confusion("if");
300    }
301}
302
303/*tex
304
305    The conditional|\ifcsname| is equivalent to |\expandafter| |\expandafter| |\ifdefined|
306    |\csname|, except that no new control sequence will be entered into the hash table (once all
307    tokens preceding the mandatory |\endcsname| have been expanded). Because we have \UTF 8, we
308    find plenty of small helpers that are used in conversion.
309
310    A csname resolve can itself have nested csname resolving. We keep track of the nesting level
311    and also remember the last match.
312
313*/
314
315/* moved to texexpand */
316
317/*tex
318
319    An active character will be treated as category 13 following |\if \noexpand| or following
320    |\ifcat \noexpand|.
321
322*/
323
324static void tex_aux_get_x_token_or_active_char(void)
325{
326    tex_get_x_token();
327 // if (cur_cmd == relax_cmd && cur_chr == no_expand_flag && tex_is_active_cs(cs_text(cur_cs))) {
328    if (cur_cmd == relax_cmd && cur_chr == no_expand_relax_code && tex_is_active_cs(cs_text(cur_cs))) {
329        cur_cmd = active_char_cmd;
330        cur_chr = active_cs_value(cs_text(cur_tok - cs_token_flag));
331    }
332}
333
334/*tex
335
336    A condition is started when the |expand| procedure encounters an |if_test| command; in that
337    case |expand| reduces to |conditional|, which is a recursive procedure.
338
339*/
340
341static void tex_aux_missing_equal_error(int code)
342{
343    tex_handle_error(back_error_type, "Missing = inserted for %C", if_test_cmd, code,
344        "I was expecting to see '<', '=', or '>'. Didn't."
345    );
346}
347
348/*tex
349
350    This is an important function because a bit larger macro package does lots of testing. Compared
351    to regular \TEX\ there is of course the penalty of larger data structures but there's not much
352    we can do about that. Then there are more variants, which in turn can lead to a performance hit
353    as there is more to test and more code involved, which might influence cache hits and such.
354    However, I already optimized the \LUATEX\ code a bit and here there are some more tiny potential
355    speedups. But \unknown\ they are hard to measure and especially their impact on a normal run:
356    \TEX\ is already pretty fast and often these tests themselves are not biggest bottleneck, at
357    least not in \CONTEXT. My guess is that the speedups compensate the extra if tests so in the end
358    we're still okay. Expansion, pushing back tokens, accessing memory all over the place, excessive
359    use of \LUA\ \unknown\ all that has probably way more impact on a run. But I keep an eye on the
360    next one anyway.
361
362*/
363
364static void tex_aux_show_if_state(halfword code, halfword case_value)
365{
366    tex_begin_diagnostic();
367    switch (code) {
368        case if_chk_int_code       : tex_print_format("{chknum %i}",        case_value); break;
369        case if_chk_integer_code   : tex_print_format("{chknumber %i}",     case_value); break;
370        case if_val_int_code       : tex_print_format("{numval %i}",        case_value); break;
371        case if_cmp_int_code       : tex_print_format("{cmpnum %i}",        case_value); break;
372        case if_chk_dim_code       : tex_print_format("{chkdim %i}",        case_value); break;
373        case if_chk_dimension_code : tex_print_format("{chkdimension %i}",  case_value); break;
374        case if_val_dim_code       : tex_print_format("{dimval %i}",        case_value); break;
375        case if_cmp_dim_code       : tex_print_format("{cmpdim %i}",        case_value); break;
376        case if_case_code          : tex_print_format("{case %i}",          case_value); break;
377        case if_math_parameter_code: tex_print_format("{mathparameter %i}", case_value); break;
378        case if_math_style_code    : tex_print_format("{mathstyle %i}",     case_value); break;
379        case if_arguments_code     : tex_print_format("{arguments %i}",     case_value); break;
380        case if_parameter_code     : tex_print_format("{parameter %i}",     case_value); break;
381        case if_parameters_code    : tex_print_format("{parameters %i}",    case_value); break;
382        default                    : tex_print_format("{todo %i}",          case_value); break;
383    }
384    tex_end_diagnostic();
385}
386
387/*tex Why do we skip over relax? */
388
389static inline halfword tex_aux_grab_toks(int expand, int expandlist, int *head) // todo: tail 
390{
391    halfword p = null;
392    if (expand) {
393        do {
394            tex_get_x_token();
395        } while (cur_cmd == spacer_cmd || cur_cmd == relax_cmd);
396    } else {
397        do {
398            tex_get_token();
399        } while (cur_cmd == spacer_cmd || cur_cmd == relax_cmd);
400    }
401    switch (cur_cmd) {
402        case left_brace_cmd:
403            p = expandlist ? tex_scan_toks_expand(1, NULL, 0, 0) : tex_scan_toks_normal(1, NULL);
404            *head = p;
405            break;
406        case internal_toks_cmd:
407        case register_toks_cmd:
408            p = eq_value(cur_chr);
409            break;
410        case register_cmd:
411            /* is this okay? probably not as cur_val can be way to large */
412            if (cur_chr == token_val_level) {
413                halfword n = tex_scan_toks_register_number();
414                p = eq_value(register_toks_location(n));
415                break;
416            } else {
417                goto DEFAULT;
418            }
419        case cs_name_cmd:
420            if (cur_chr == last_named_cs_code) {
421                if (lmt_scanner_state.last_cs_name != null_cs) {
422                    p = eq_value(lmt_scanner_state.last_cs_name);
423                }
424                break;
425            } else { 
426                /* fall through */
427            }
428        case call_cmd:
429        case protected_call_cmd:
430        case semi_protected_call_cmd:
431        case constant_call_cmd:
432        case tolerant_call_cmd:
433        case tolerant_protected_call_cmd:
434        case tolerant_semi_protected_call_cmd:
435            p = eq_value(cur_cs);
436            break;
437        default:
438          DEFAULT:
439            {
440                halfword n;
441                tex_back_input(cur_tok);
442                n = tex_scan_toks_register_number();
443                p = eq_value(register_toks_location(n));
444                break;
445            }
446    }
447    /* skip over the ref count */
448    return p ? token_link(p) : null;
449}
450
451/*tex 
452    
453    We can at some point (when deemed critital in \CONTEXT) add these: 
454
455    \starttyping 
456    \counttok   token     tokenlist 
457    \counttoks  tokenlist tokenlist 
458    \countxtoks tokenlist tokenlist % expanded 
459    \countchar  chartoken tokenlist
460    \stoptyping 
461
462    but for now we keep them local (static).
463*/
464
465static halfword tex_aux_count_tok(int once)
466{
467    halfword qq = null;
468    halfword p, q;
469    halfword result = 0;
470    int save_scanner_status = lmt_input_state.scanner_status;
471    lmt_input_state.scanner_status = scanner_is_normal;
472    p = tex_get_token();
473    q = tex_aux_grab_toks(0, 0, &qq);
474    if (p == q) {
475        result = 1;
476    } else {
477        while (q) {
478            if (p == token_info(q)) {
479                result += 1;
480                if (once) { 
481                    break;
482                }
483            } else {
484                q = token_link(q);
485            }
486        }
487    }
488    if (qq) {
489        tex_flush_token_list(qq);
490    }
491    lmt_input_state.scanner_status = save_scanner_status;
492    return result;
493}
494
495static halfword tex_aux_count_toks(int once, int expand)
496{
497    halfword pp = null;
498    halfword p;
499    halfword result = 0;
500    int save_scanner_status = lmt_input_state.scanner_status;
501    lmt_input_state.scanner_status = scanner_is_normal;
502    p = tex_aux_grab_toks(expand, expand, &pp);
503    if (p) {
504        halfword qq = null;
505        halfword q = tex_aux_grab_toks(expand, expand, &qq);
506        if (p == q) {
507            result = 1;
508        } else {
509            int qh = q;
510            int ph = p;
511            while (p && q) {
512                halfword pt = token_info(p);
513                halfword qt = token_info(q);
514              AGAIN:
515                if (pt == qt) {
516                    p = token_link(p);
517                    q = token_link(q);
518                } else if (token_cmd(pt) == ignore_cmd && token_cmd(qt) >= ignore_cmd && token_cmd(qt) <= other_char_cmd) {
519                    p = token_link(p);
520                    if (token_chr(pt) == token_chr(qt)) {
521                        q = token_link(q);
522                    } else {
523                        pt = token_info(p);
524                        goto AGAIN;
525                    }
526                } else {
527                    p = ph;
528                    q = token_link(qh);
529                    qh = q;
530                }
531                if (! p) {
532                    result += 1;
533                    if (once) { 
534                        break;
535                    }
536                }
537            }
538        }
539        if (qq) {
540            tex_flush_token_list(qq);
541        }
542    }
543    if (pp) {
544        tex_flush_token_list(pp);
545    }
546    lmt_input_state.scanner_status = save_scanner_status;
547    return result;
548}
549
550static halfword tex_aux_count_char(int once)
551{
552    halfword tok;
553    halfword qq = null;
554    halfword q;
555    halfword result = 0;
556    int save_scanner_status = lmt_input_state.scanner_status;
557    lmt_input_state.scanner_status = scanner_is_normal;
558    tok = tex_get_token();
559    q = tex_aux_grab_toks(0, 0, &qq);
560    if (q) {
561        int nesting = 0;
562        while (q) {
563            if (! nesting && token_info(q) == tok) {
564                result += 1;
565                if (once) { 
566                    break;
567                }
568            } else if (token_cmd(token_info(q)) == left_brace_cmd) {
569                nesting += 1;
570            } else if (token_cmd(token_info(q)) == right_brace_cmd) {
571                nesting -= 1;
572            }
573            q = token_link(q);
574        }
575    }
576    if (qq) {
577        tex_flush_token_list(qq);
578    }
579    lmt_input_state.scanner_status = save_scanner_status;
580    return result;
581}
582
583// static inline halfword tex_aux_scan_comparison(int code)
584// {
585//     halfword r;
586//     do {
587//         tex_get_x_token();
588//     } while (cur_cmd == spacer_cmd);
589//     r = cur_tok - other_token;
590//     if ((r < '<') || (r > '>')) {
591//         tex_aux_missing_equal_error(code);
592//         return '=';
593//     } else {
594//         return r;
595//     }
596// }
597
598// static inline halfword tex_aux_scan_comparison(int code)
599// {
600//     do {
601//         tex_get_x_token();
602//     } while (cur_cmd == spacer_cmd);
603//     if (cur_cmd != other_char_cmd || (cur_chr < '<') || (cur_chr > '>')) {
604//         tex_aux_missing_equal_error(code);
605//         return '=';
606//     } else {
607//         return cur_chr;
608//     }
609// }
610
611typedef enum comparisons { 
612    /* case 0 .. 7 */
613    comparison_equal       = 0,
614    comparison_less        = 1,
615    comparison_greater     = 2,
616    comparison_not_equal   = 3,
617    comparison_not_less    = 4,
618    comparison_not_greater = 5,
619    comparison_element     = 6,
620    comparison_not_element = 7,
621} comparisons; 
622
623typedef enum values { 
624    /* case 1 .. 4 */
625    value_less    = 1,
626    value_equal   = 2,
627    value_greater = 3,
628    value_error   = 4,
629} values;
630
631typedef enum checks { 
632    /* case 1 .. 2 */
633    check_okay    = 1,
634    check_error   = 2,
635} checks;
636
637typedef enum parameterstates { 
638    parameter_zero  = 0,
639    parameter_set   = 1,
640    parameter_unset = 2,
641} parameterstates;
642
643static inline halfword tex_aux_scan_comparison(int code)
644{
645    bool negate = false;
646    while (1) {
647        tex_get_x_token();
648        switch (cur_cmd) { 
649            case letter_cmd: 
650            case other_char_cmd: 
651                switch (cur_chr) { 
652                    /* traditional */
653                    case '='   : return negate ? comparison_not_equal   : comparison_equal;
654                    case '<'   : return negate ? comparison_not_less    : comparison_less;
655                    case '>'   : return negate ? comparison_not_greater : comparison_greater;
656                    /* also */
657                    case '&'   : return negate ? comparison_not_element : comparison_element;
658                    /* bonus */
659                    case '!'   : negate = ! negate ; continue;
660                    /* neat */
661                    case 0x2208: return negate ? comparison_not_element : comparison_element; 
662                    case 0x2209: return negate ? comparison_element     : comparison_not_element;
663                    case 0x2260: return negate ? comparison_equal       : comparison_not_equal;
664                    case 0x2264: return negate ? comparison_greater     : comparison_not_greater;
665                    case 0x2265: return negate ? comparison_less        : comparison_not_less; 
666                    case 0x2270: return negate ? comparison_greater     : comparison_not_greater;
667                    case 0x2271: return negate ? comparison_less        : comparison_not_less; 
668                }
669            case spacer_cmd: 
670                continue;
671            default:
672                tex_aux_missing_equal_error(code);
673                return 0;
674        }
675    } 
676}
677
678static inline void tex_aux_check_strict(int *result)
679{
680    tex_get_x_token();
681    switch (cur_cmd) { 
682        case relax_cmd:
683        case spacer_cmd: 
684        case if_test_cmd:
685            break;
686        default: 
687            *result = check_error;
688            break;
689    }
690    tex_back_input(cur_tok);
691}
692
693void tex_conditional_if(halfword code, int unless)
694{
695    /*tex The result or case value. */
696    int result = 0;
697    /*tex The |cond_ptr| corresponding to this conditional: */
698    halfword save_cond_ptr;
699    /*tex Tracing options */
700    int tracing_ifs = tracing_ifs_par > 0;
701    int tracing_commands = tracing_commands_par;
702    int tracing_both = tracing_ifs && (tracing_commands <= 1);
703    if (tracing_both) {
704        tex_show_cmd_chr(cur_cmd, cur_chr);
705    }
706    tex_aux_push_condition_stack(code, unless);
707    save_cond_ptr = lmt_condition_state.cond_ptr;
708    /*tex Either process |\ifcase| or set |b| to the value of a boolean condition. */
709  HERE:
710    /*tex We can get back here so we need to make sure result is always set! */
711    lmt_condition_state.if_step = code;
712    lmt_condition_state.if_unless = unless;
713    switch (code) {
714        case if_char_code:
715        case if_cat_code:
716            /*tex Test if two characters match. Seldom used, this one. */
717            {
718                halfword n, m;
719                tex_aux_get_x_token_or_active_char();
720                if ((cur_cmd > active_char_cmd) || (cur_chr > max_character_code)) {
721                    /*tex It's not a character. */
722                    m = relax_cmd;
723                    n = relax_code;
724                } else {
725                    m = cur_cmd;
726                    n = cur_chr;
727                }
728                tex_aux_get_x_token_or_active_char();
729                if ((cur_cmd > active_char_cmd) || (cur_chr > max_character_code)) {
730                    cur_cmd = relax_cmd;
731                    cur_chr = relax_code;
732                }
733                result = code == if_char_code ? (n == cur_chr) : (m == cur_cmd);
734            }
735            goto RESULT;
736        case if_int_code:
737        case if_abs_int_code:
738            {
739                halfword n1 = tex_scan_integer(0, NULL, NULL);
740                halfword cp = tex_aux_scan_comparison(code);
741                halfword n2 = tex_scan_integer(0, NULL, NULL);
742                if (code == if_abs_int_code) {
743                    if (n1 < 0) {
744                        n1 = -n1;
745                    }
746                    if (n2 < 0) {
747                        n2 = -n2;
748                    }
749                }
750                switch (cp) {
751                    case comparison_equal      : result = (n1 == n2); break;
752                    case comparison_less       : result = (n1 <  n2); break;
753                    case comparison_greater    : result = (n1  > n2); break;
754                    case comparison_not_equal  : result = (n1 != n2); break;
755                    case comparison_not_less   : result = (n1 >= n2); break;
756                    case comparison_not_greater: result = (n1 <= n2); break;
757                    case comparison_element    : result = (n1 & n2) == n1; break;
758                    case comparison_not_element: result = (n1 & n2) != n1; break;
759                }
760            }
761            goto RESULT;
762        case if_zero_int_code:
763            result = tex_scan_integer(0, NULL, NULL) == 0;
764            goto RESULT;
765        case if_interval_int_code:
766            {
767                scaled n0 = tex_scan_integer(0, NULL, NULL);
768                scaled n1 = tex_scan_integer(0, NULL, NULL);
769                scaled n2 = tex_scan_integer(0, NULL, NULL);
770                result = n1 - n2;
771                result = result == 0 ? 1 : (result > 0 ? result <= n0 : -result <= n0);
772            }
773            goto RESULT;
774        case if_posit_code:
775        case if_abs_posit_code:
776            {
777                halfword n1 = tex_scan_posit(0);
778                halfword cp = tex_aux_scan_comparison(code);
779                halfword n2 = tex_scan_posit(0);
780                if (code == if_abs_posit_code) {
781                    tex_posit zero = tex_integer_to_posit(0);
782                    if (tex_posit_lt(n1,zero.v)) {
783                        n1 = tex_posit_neg(n1);
784                    }
785                    if (tex_posit_lt(n2,zero.v)) {
786                        n2 = tex_posit_neg(n2);
787                    }
788                }
789                switch (cp) {
790                    case comparison_equal      : result = tex_posit_eq(n1,n2); break;
791                    case comparison_less       : result = tex_posit_lt(n1,n2); break;
792                    case comparison_greater    : result = tex_posit_gt(n1,n2); break;
793                    case comparison_not_equal  : result = tex_posit_ne(n1,n2); break;
794                    case comparison_not_less   : result = tex_posit_gt(n1,n2); break;
795                    case comparison_not_greater: result = tex_posit_lt(n1,n2); break;
796                    case comparison_element    : result = tex_posit_eq(tex_integer_to_posit(tex_posit_to_integer(n1) & tex_posit_to_integer(n2)).v,n1); break;
797                    case comparison_not_element: result = tex_posit_ne(tex_integer_to_posit(tex_posit_to_integer(n1) & tex_posit_to_integer(n2)).v,n1); break;
798                }
799            }
800            goto RESULT;
801        case if_zero_posit_code:
802            result = tex_posit_eq_zero(tex_scan_posit(0));
803            goto RESULT;
804        case if_interval_posit_code:
805            {
806                halfword n0 = tex_scan_posit(0);
807                halfword n1 = tex_scan_posit(0);
808                halfword n2 = tex_scan_posit(0);
809                result = tex_posit_sub(n1, n2);
810                result = tex_posit_eq_zero(result) ? 1 : (tex_posit_gt_zero(result) ? tex_posit_le(result, n0) : tex_posit_le(tex_posit_neg(result), n0));
811            }
812            goto RESULT;
813        case if_dim_code:
814        case if_abs_dim_code:
815            {
816                scaled n1 = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
817                halfword cp = tex_aux_scan_comparison(code);
818                scaled n2 = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
819                if (code == if_abs_dim_code) {
820                    if (n1 < 0) {
821                        n1 = -n1;
822                    }
823                    if (n2 < 0) {
824                        n2 = -n2;
825                    }
826                }
827                switch (cp) {
828                    case comparison_equal      : result = (n1 == n2); break;
829                    case comparison_less       : result = (n1 <  n2); break;
830                    case comparison_greater    : result = (n1  > n2); break;
831                    case comparison_not_equal  : result = (n1 != n2); break;
832                    case comparison_not_less   : result = (n1 >= n2); break;
833                    case comparison_not_greater: result = (n1 <= n2); break;
834                    case comparison_element    : result = (n1/65536 & n2/65536) == n1/65536; break; /* maybe we should round */
835                    case comparison_not_element: result = (n1/65536 & n2/65536) != n1/65536; break; /* maybe we should round */
836                }
837            }
838            goto RESULT;
839        case if_zero_dim_code:
840            result = tex_scan_dimension(0, 0, 0, 0, NULL, NULL) == 0;
841            goto RESULT;
842        case if_interval_dim_code:
843            {
844                scaled n0 = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
845                scaled n1 = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
846                scaled n2 = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
847                result = n1 - n2;
848                result = result == 0 ? 1 : (result > 0 ? result <= n0 : -result <= n0);
849            }
850            goto RESULT;
851        case if_odd_code:
852            result = odd(tex_scan_integer(0, NULL, NULL));
853            goto RESULT;
854        case if_vmode_code:
855            result = is_v_mode(cur_list.mode);
856            goto RESULT;
857        case if_hmode_code:
858            result = is_h_mode(cur_list.mode);
859            goto RESULT;
860        case if_mmode_code:
861            result = is_m_mode(cur_list.mode);
862            goto RESULT;
863        case if_inner_code:
864            result = cur_list.mode < nomode;
865            goto RESULT;
866        case if_void_code:
867            {
868                halfword n = tex_scan_box_register_number();
869                result = box_register(n) == null;
870            }
871            goto RESULT;
872        case if_hbox_code:
873            {
874                halfword n = tex_scan_box_register_number();
875                halfword p = box_register(n);
876                result = p && (node_type(p) == hlist_node);
877            }
878            goto RESULT;
879        case if_vbox_code:
880            {
881                halfword n = tex_scan_box_register_number();
882                halfword p = box_register(n);
883                result = p && (node_type(p) == vlist_node);
884            }
885            goto RESULT;
886        case if_tok_code:
887        case if_cstok_code:
888            {
889                halfword pp = null;
890                halfword qq = null;
891                halfword p, q;
892                int expand = code == if_tok_code;
893                int save_scanner_status = lmt_input_state.scanner_status;
894                lmt_input_state.scanner_status = scanner_is_normal;
895                p = tex_aux_grab_toks(expand, 1, &pp);
896                q = tex_aux_grab_toks(expand, 1, &qq);
897                if (p == q) {
898                    /* this is sneaky, a list always is different */
899                    result = 1;
900                } else {
901                    while (p && q) {
902                        if (token_info(p) != token_info(q)) {
903                         // p = null;
904                         // break;
905                            result = 0;
906                            goto IFTOKDONE;
907                        } else {
908                            p = token_link(p);
909                            q = token_link(q);
910                        }
911                    }
912                    result = (! p) && (! q);
913                }
914              IFTOKDONE:
915                if (pp) {
916                    tex_flush_token_list(pp);
917                }
918                if (qq) {
919                    tex_flush_token_list(qq);
920                }
921                lmt_input_state.scanner_status = save_scanner_status;
922            }
923            goto RESULT;
924        case if_last_named_cs_code:
925        case if_x_code:
926            {
927                /*tex
928                    Test if two tokens match. Note that |\ifx| will declare two macros different
929                    if one is |\long| or |\outer| and the other isn't, even though the texts of
930                    the macros are the same.
931
932                    We need to reset |scanner_status|, since |\outer| control sequences are
933                    allowed, but we might be scanning a macro definition or preamble.
934
935                    This is no longer true as we dropped these properties but it does apply to
936                    protected macros and such.
937                 */
938             // halfword p, q, n;
939                halfword p, q;
940                int save_scanner_status = lmt_input_state.scanner_status;
941                lmt_input_state.scanner_status = scanner_is_normal;
942                if (code == if_x_code) {
943                    tex_get_next();
944                 // n = cur_cs;
945                    p = cur_cmd;
946                    q = cur_chr;
947                } else {
948                 // n = lmt_scanner_state.last_cs_name;
949                    p = eq_type(lmt_scanner_state.last_cs_name); 
950                    q = eq_value(lmt_scanner_state.last_cs_name);
951                }
952                tex_get_next();
953                if ((p == constant_call_cmd && cur_cmd == call_cmd) || (p == call_cmd && cur_cmd == constant_call_cmd)) {
954                    /*tex This is a somewhat special case. */
955                    goto SOMECALLCMD;
956                } else if (cur_cmd != p) {
957                    result = 0;
958                } else if (cur_cmd < call_cmd) {
959                    result = cur_chr == q;
960                } else {
961                    SOMECALLCMD:
962                    /*tex
963                        Test if two macro texts match. Note also that |\ifx| decides that macros
964                        |\a| and |\b| are different in examples like this:
965
966                        \starttyping
967                        \def\a{\c}  \def\c{}
968                        \def\b{\d}  \def\d{}
969                        \stoptyping
970
971                        We acctually have commands beyond valid call commands but they are never 
972                        seen here. 
973                    */
974                    p = token_link(cur_chr);
975                    /*tex Omit reference counts. */
976                 // q = token_link(eq_value(n));
977                    q = token_link(q);
978                    if (p == q) {
979                        result = 1;
980                    /*
981                    } else if (! q) {
982                        result = 0;
983                    */
984                    } else {
985                        while (p && q) {
986                            if (token_info(p) != token_info(q)) {
987                             // p = null;
988                             // break;
989                                result = 0;
990                                goto IFXDONE;
991                            } else {
992                                p = token_link(p);
993                                q = token_link(q);
994                            }
995                        }
996                        result = (! p) && (! q);
997                    }
998                }
999              IFXDONE:
1000                lmt_input_state.scanner_status = save_scanner_status;
1001            }
1002            goto RESULT;
1003        case if_true_code:
1004            result = 1;
1005            goto RESULT;
1006        case if_false_code:
1007            result = 0;
1008            goto RESULT;
1009        case if_chk_int_code:
1010            {
1011                lmt_error_state.intercept = 1; /* maybe ++ and -- so that we can nest */
1012                lmt_error_state.last_intercept = 0;
1013                lmt_condition_state.chk_integer = 0;
1014                tex_scan_integer_validate(); 
1015                result = lmt_error_state.last_intercept ? check_error : check_okay;
1016                lmt_error_state.intercept = 0;
1017                lmt_error_state.last_intercept = 0;
1018             /* goto CASE; */
1019                goto CASECHECK;
1020            }
1021        case if_chk_integer_code:
1022            {
1023                lmt_error_state.intercept = 1; /* maybe ++ and -- so that we can nest */
1024                lmt_error_state.last_intercept = 0;
1025                lmt_condition_state.chk_integer = tex_scan_integer(0, NULL, NULL); 
1026                result = lmt_error_state.last_intercept ? check_error : check_okay;
1027                if (result == check_okay) { 
1028                    tex_aux_check_strict(&result);
1029                }
1030                lmt_error_state.intercept = 0;
1031                lmt_error_state.last_intercept = 0;
1032             /* goto CASE; */
1033                goto CASECHECK;
1034            }
1035        case if_chk_intexpr_code: /* numeric result check */
1036            lmt_error_state.intercept = 1;
1037            lmt_error_state.last_intercept = 0;
1038            lmt_condition_state.chk_integer = tex_scan_expr(integer_val_level);
1039            result = lmt_error_state.last_intercept ? check_error : check_okay;
1040            if (result == check_okay) { 
1041                tex_aux_check_strict(&result);
1042            }
1043            lmt_error_state.intercept = 0;
1044            lmt_error_state.last_intercept = 0;
1045            goto CASECHECK;
1046        case if_val_int_code:
1047            {
1048                lmt_error_state.intercept = 1;
1049                lmt_error_state.last_intercept = 0;
1050                lmt_condition_state.chk_integer = tex_scan_integer(0, NULL, NULL);
1051                result = lmt_error_state.last_intercept ? value_error : (lmt_condition_state.chk_integer < 0) ? value_less : (lmt_condition_state.chk_integer > 0) ? value_greater : value_equal;
1052                lmt_error_state.intercept = 0;
1053                lmt_error_state.last_intercept = 0;
1054                goto CASE;
1055            }
1056        case if_cmp_int_code:
1057            {
1058                halfword n1 = tex_scan_integer(0, NULL, NULL);
1059                halfword n2 = tex_scan_integer(0, NULL, NULL);
1060                result = (n1 < n2) ? 0 : (n1 > n2) ? 2 : 1;
1061                goto CASE;
1062            }
1063        case if_chk_dim_code:
1064            {
1065                lmt_error_state.intercept = 1;
1066                lmt_error_state.last_intercept = 0;
1067                lmt_condition_state.chk_dimension = 0;
1068                tex_scan_dimension_validate(); 
1069                result = lmt_error_state.last_intercept ? check_error : check_okay;
1070                lmt_error_state.intercept = 0;
1071                lmt_error_state.last_intercept = 0;
1072             /* goto CASE; */
1073                goto CASECHECK;
1074            }
1075        case if_chk_dimension_code:
1076            {
1077                lmt_error_state.intercept = 1;
1078                lmt_error_state.last_intercept = 0;
1079                lmt_condition_state.chk_dimension = tex_scan_dimension(0, 0, 0, 0, NULL, NULL); 
1080                result = lmt_error_state.last_intercept ? check_error : check_okay;
1081                if (result == check_okay) { 
1082                    tex_aux_check_strict(&result);
1083                }
1084                lmt_error_state.intercept = 0;
1085                lmt_error_state.last_intercept = 0;
1086             /* goto CASE; */
1087                goto CASECHECK;
1088            }
1089        case if_chk_dimexpr_code: /* dimension result check */
1090            lmt_error_state.intercept = 1;
1091            lmt_error_state.last_intercept = 0;
1092            lmt_condition_state.chk_dimension = tex_scan_expr(dimension_val_level);
1093            result = lmt_error_state.last_intercept ? check_error : check_okay;
1094            if (result == check_okay) { 
1095                tex_aux_check_strict(&result);
1096            }
1097            lmt_error_state.intercept = 0;
1098            lmt_error_state.last_intercept = 0;
1099            goto CASECHECK;
1100        case if_val_dim_code:
1101            {
1102                lmt_error_state.intercept = 1;
1103                lmt_error_state.last_intercept = 0;
1104                lmt_condition_state.chk_dimension = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
1105                result = lmt_error_state.last_intercept ? value_error : (lmt_condition_state.chk_dimension < 0) ? value_less : (lmt_condition_state.chk_dimension > 0) ? value_greater : value_equal;
1106                lmt_error_state.intercept = 0;
1107                lmt_error_state.last_intercept = 0;
1108                goto CASE;
1109            }
1110        case if_cmp_dim_code:
1111            {
1112                scaled n1 = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
1113                scaled n2 = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
1114                result = (n1 < n2) ? 0 : (n1 > n2) ? 2 : 1;
1115                goto CASE;
1116            }
1117        case if_case_code:
1118            /*tex 
1119                Select the appropriate case and |return| or |goto common_ending|. We could 
1120                support |\unless| for a limited case but let's not mess around to much; after 
1121                all it is an original \TEX\ primitive. 
1122            */
1123            result = tex_scan_integer(0, NULL, NULL);
1124            goto CASE;
1125        case if_defined_code:
1126            /*tex
1127                The conditional |\ifdefined| tests if a control sequence is defined. We need to
1128                reset |scanner_status|, since |\outer| control sequences are allowed, but we
1129                might be scanning a macro definition or preamble.
1130            */
1131            {
1132                int save_scanner_status = lmt_input_state.scanner_status;
1133                lmt_input_state.scanner_status = scanner_is_normal;
1134                tex_get_next();
1135                result = cur_cmd != undefined_cs_cmd;
1136                lmt_input_state.scanner_status = save_scanner_status;
1137                goto RESULT;
1138            }
1139        case if_csname_code:
1140            result = tex_is_valid_csname();
1141            goto RESULT;
1142        case if_in_csname_code:
1143            /*tex This one will go away. */
1144            result = lmt_expand_state.cs_name_level;
1145            goto RESULT;
1146        case if_font_char_code:
1147            /*tex The conditional |\iffontchar| tests the existence of a character in a font. */
1148            {
1149                halfword fnt = tex_scan_font_identifier(NULL);
1150                halfword chr = tex_scan_char_number(0);
1151                result = tex_char_exists(fnt, chr);
1152            }
1153            goto RESULT;
1154        case if_condition_code:
1155            /*tex This can't happen! */
1156            goto RESULT;
1157        case if_flags_code:
1158            {
1159                halfword cs; 
1160                singleword flag;
1161                tex_get_r_token();
1162                cs = cur_cs; 
1163                flag = eq_flag(cs);
1164                /* todo: each prefix */
1165                tex_get_token();
1166                if (cur_cmd == prefix_cmd) {
1167                    /* Not all prefixes are reflected cq. stored with a contrtol sequence. */
1168                    switch (cur_chr) {
1169                        /*tex We check flags: */
1170                        case frozen_code        : result = is_frozen   (flag); break;
1171                        case permanent_code     : result = is_permanent(flag); break;
1172                        case immutable_code     : result = is_immutable(flag); break;
1173                     /* case primitive_code     : result = is_primitive(flag); break; */
1174                        case mutable_code       : result = is_mutable  (flag); break;
1175                        case noaligned_code     : result = is_noaligned(flag); break;
1176                        case instance_code      : result = is_instance (flag); break;
1177                        case untraced_code      : result = is_untraced (flag); break;
1178                        /*tex We check cmd: */
1179                        case global_code        : result = eq_level(cs) == level_one; break;
1180                        case tolerant_code      : result = is_tolerant_cmd(eq_type(cs)); break;
1181                        case protected_code     : result = is_protected_cmd(eq_type(cs)); break;
1182                     /* case overloaded_code    : */
1183                     /* case aliased_code       : */
1184                     /* case immediate_code     : */
1185                     /* case deferred_code      : */
1186                     /*      conditional_code     */
1187                     /*      value_code           */
1188                        case semiprotected_code : result = is_semi_protected_cmd(eq_type(cs)); break;
1189                     /* case enforced_code      : */ 
1190                     /* case always_code        : */
1191                     /* case inherited_code     : */  
1192                        case constant_code      : result = is_constant_cmd(eq_type(cs)); break;
1193                     /* case retained_code      : */
1194                     /* case constrained_code   : */                                            
1195                    }
1196                } else {
1197                    int fl; 
1198                    tex_back_input(cur_tok);
1199                    fl = tex_scan_integer(1, NULL, NULL); 
1200                    result = (flag & fl) == fl;
1201                    if (! result) {
1202                        if (is_protected(fl)) {
1203                            result = is_protected_cmd(eq_type(cs));
1204                        } else if (is_semiprotected(fl)) {
1205                            result = is_semi_protected_cmd(eq_type(cs));
1206                        } else if (is_tolerant(fl)) {
1207                            result = is_tolerant_cmd(eq_type(cs));
1208                        } else if (is_global(fl)) {
1209                            result = eq_level(cs) == level_one;
1210                        }
1211                    }
1212                }
1213                goto RESULT;
1214            }
1215        case if_empty_code:
1216            {
1217                tex_get_token();
1218              EMPTY_CHECK_AGAIN:
1219                switch (cur_cmd) {
1220                    case call_cmd:
1221                    case constant_call_cmd:
1222                        result = ! token_link(cur_chr);
1223                        break;
1224                    case internal_toks_reference_cmd:
1225                    case register_toks_reference_cmd:
1226                        result = ! token_link(cur_chr);
1227                        break;
1228                    case register_cmd:
1229                        /*tex See |tex_aux_grab_toks|. */
1230                        if (cur_chr == token_val_level) {
1231                            halfword n = tex_scan_toks_register_number();
1232                            halfword p = eq_value(register_toks_location(n));
1233                            result = ! p || ! token_link(p);
1234                        } else {
1235                            result = 0;
1236                        }
1237                        break;
1238                    case internal_toks_cmd:
1239                    case register_toks_cmd:
1240                        { 
1241                            halfword p = eq_value(cur_chr);   
1242                            result = ! p || ! token_link(p);
1243                        }
1244                        break;
1245                    case left_brace_cmd:
1246                        {
1247                            halfword h = tex_scan_toks_expand(1, NULL, 0, 0);
1248                            result = token_link(h) == null; 
1249                            tex_flush_token_list(h);
1250                        }
1251                        break;
1252                    case cs_name_cmd:
1253                        if (cur_chr == last_named_cs_code && lmt_scanner_state.last_cs_name != null_cs) {
1254                            cur_cmd = eq_type(lmt_scanner_state.last_cs_name);
1255                            cur_chr = eq_value(lmt_scanner_state.last_cs_name);
1256                            goto EMPTY_CHECK_AGAIN;
1257                        } 
1258                        /* fall through */
1259                    default:
1260                        result = 0;
1261                }
1262                goto RESULT;
1263            }
1264        case if_relax_code:
1265            {
1266                tex_get_token();
1267                result = cur_cmd == relax_cmd;
1268                goto RESULT;
1269            }
1270        case if_boolean_code:
1271            result = tex_scan_integer(0, NULL, NULL) ? 1 : 0;
1272            goto RESULT;
1273        case if_numexpression_code: /* boolean check */
1274            result = tex_scanned_expression(integer_val_level) ? 1 : 0;
1275            goto RESULT;
1276        case if_dimexpression_code: /* boolean check */
1277            result = tex_scanned_expression(dimension_val_level) ? 1 : 0;
1278            goto RESULT;
1279        case if_math_parameter_code:
1280            /*tex
1281                A value of |1| means that the parameter is set to a non-zero value, while |2| means
1282                that it is unset.
1283            */
1284            {
1285                do {
1286                    tex_get_x_token();
1287                } while (cur_cmd == spacer_cmd);
1288                if (cur_cmd == math_parameter_cmd) {
1289                    int code = cur_chr;
1290                    int style = tex_scan_math_style_identifier(0, 0);
1291                    result = tex_get_math_parameter(style, code, NULL);
1292                    if (result == max_dimension) {
1293                        result = parameter_unset; 
1294                    } else if (result) {
1295                        result = parameter_set;
1296                    } else { 
1297                        result = parameter_zero; 
1298                    }
1299                } else {
1300                    tex_normal_error("mathparameter", "a valid parameter expected");
1301                    result = parameter_zero;
1302                }
1303                goto CASE;
1304            }
1305        case if_math_style_code:
1306            result = tex_current_math_style();
1307            goto CASE;
1308        case if_arguments_code:
1309            result = lmt_expand_state.arguments;
1310            goto CASE;
1311        case if_parameters_code:
1312            /*tex
1313                The result has the last non-null count. We could have the test in the for but let's
1314                keep it readable.
1315            */
1316            result = tex_get_parameter_count();
1317            goto CASE;
1318        case if_parameter_code:
1319            {
1320                /*tex
1321                    We need to pick up the next token but avoid replacement by the parameter which
1322                    happens in the getters: 0 = no parameter, 1 = okay, 2 = empty. This permits
1323                    usage like |\ifparameter#2\or yes\else no\fi| as with the other checkers.
1324                */
1325                if (lmt_input_state.cur_input.loc) {
1326                    halfword t = token_info(lmt_input_state.cur_input.loc);
1327                    if (t < cs_token_flag && token_cmd(t) == parameter_reference_cmd) {
1328                        lmt_input_state.cur_input.loc = token_link(lmt_input_state.cur_input.loc);
1329                        result = lmt_input_state.parameter_stack[lmt_input_state.cur_input.parameter_start + token_chr(t) - 1] != null ? 1 : 2;
1330                    } else { 
1331                        /*tex 
1332                            We have a replacement text so we check and backtrack. This is somewhat
1333                            tricky because a parameter can be a condition but we assume sane usage. 
1334                        */
1335                        tex_get_token();
1336                        switch (cur_cmd) { 
1337                            case if_test_cmd: 
1338                                result = 2;
1339                                break;
1340                            case index_cmd:
1341                                if (cur_chr >= 0 && cur_chr < lmt_input_state.parameter_stack_data.ptr) {
1342                                    result = lmt_input_state.parameter_stack[cur_chr] != null ? 1 : 2;
1343                                }
1344                                break;
1345                            default:
1346                                result = 1;
1347                                break;
1348                        }
1349                    }          
1350                    /*tex Because we only have two values we can actually support |\unless|. */
1351                 // if (unless) { 
1352                 //     result = result == 1 ? 2 : 1;
1353                 // }
1354                 // goto CASEWITHUNLESS;
1355                    goto CASECHECK;
1356                } else { 
1357                    goto CASE;
1358                }
1359            }
1360        case if_has_tok_code:
1361            result = tex_aux_count_tok(1);
1362            goto RESULT;
1363        case if_has_toks_code:
1364        case if_has_xtoks_code:
1365            result = tex_aux_count_toks(1, code == if_has_xtoks_code);
1366            goto RESULT;
1367        case if_has_char_code:
1368            result = tex_aux_count_char(1);
1369            goto RESULT;
1370        case if_insert_code:
1371            /* beware: it tests */
1372            result = ! tex_insert_is_void(tex_scan_integer(0, NULL, NULL));
1373            goto RESULT;
1374        case if_in_alignment_code:
1375            result = tex_in_alignment();
1376            goto RESULT;
1377        case if_cramped_code:
1378            result = tex_is_cramped_style(tex_scan_math_style_identifier(0, 0));
1379            goto RESULT;
1380        case if_list_code:
1381            {
1382                halfword n = tex_scan_box_register_number();
1383                result = box_register(n) != null && box_list(box_register(n)) != null;
1384            }
1385            goto RESULT;
1386     // case if_bitwise_and_code:
1387     //     {
1388     //         halfword n1 = scan_integer(0, NULL);
1389     //         halfword n2 = scan_integer(0, NULL);
1390     //         result = n1 & n2 ? 1 : 0;
1391     //         goto RESULT;
1392     //     }
1393        default:
1394            {
1395                int category;
1396                strnumber u = tex_save_cur_string();
1397                int save_scanner_status = lmt_input_state.scanner_status;
1398                lmt_input_state.scanner_status = scanner_is_normal;
1399                lmt_token_state.luacstrings = 0;
1400                category = lmt_function_call_by_category(code - last_if_test_code, 0, &result);
1401                tex_restore_cur_string(u);
1402                lmt_input_state.scanner_status = save_scanner_status;
1403                if (lmt_token_state.luacstrings > 0) {
1404                    tex_lua_string_start();
1405                }
1406                switch (category) {
1407                    case lua_value_integer_code:
1408                    case lua_value_cardinal_code:
1409                    case lua_value_dimension_code:
1410                        /* unless: we could swap 1 and 3 */
1411                        goto CASE;
1412                    case lua_value_boolean_code:
1413                        goto RESULT;
1414                    case lua_value_conditional_code:
1415                        /* can we stay in the condition */
1416                        tex_back_input(token_val(if_test_cmd, if_condition_code));
1417                        tex_aux_pop_condition_stack();
1418                        return;
1419                    default:
1420                        result = 0;
1421                        goto RESULT;
1422                }
1423            }
1424    }
1425  CASECHECK:
1426    if (unless) { 
1427        result = result == check_okay ? check_error : check_okay;
1428        goto CASEWITHUNLESS;
1429    } else { 
1430        goto CASE;
1431    }
1432  CASE:
1433    /*tex
1434       It is too messy to support |\unless| for case variants although for |\ifparameter| we do 
1435       support it (there are only a few outcomes there).
1436    */
1437    if (unless) {
1438        /*tex This could be a helper as we do this a few more times. */
1439        halfword online = tracing_online_par;
1440        tracing_online_par = 1;
1441        tex_begin_diagnostic();
1442        tex_print_format( "ignoring \\unless in %C test", if_test_cmd, code);
1443        tex_end_diagnostic();
1444        tracing_online_par = online;
1445    }
1446  CASEWITHUNLESS:
1447    if (tracing_commands > 1) {
1448        tex_aux_show_if_state(code, result);
1449    }
1450    while (result) {
1451        unless = tex_aux_pass_text_x(tracing_ifs, tracing_commands);
1452        if (tracing_both) {
1453            tex_show_cmd_chr(cur_cmd, cur_chr);
1454        }
1455        if (lmt_condition_state.cond_ptr == save_cond_ptr) {
1456            if (cur_chr >= first_real_if_test_code) {
1457                /*tex
1458                    We have an |or_else_cmd| here, but keep in mind that |\expandafter \ifx| and
1459                    |\unless \ifx| and |\ifcondition| don't work in such cases! We stay in this
1460                    function call.
1461                */
1462                if (cur_chr == if_condition_code) {
1463                 // goto COMMON_ENDING;
1464                    tex_aux_pop_condition_stack();
1465                    return;
1466                } else {
1467                    code = cur_chr;
1468                    goto HERE;
1469                }
1470            } else if (cur_chr == or_code) {
1471                --result;
1472            } else {
1473                goto COMMON_ENDING;
1474            }
1475        } else if (cur_chr == fi_code) {
1476            tex_aux_pop_condition_stack();
1477        }
1478    }
1479    tex_aux_change_if_limit(or_code, save_cond_ptr);
1480    /*tex Wait for |\or|, |\else|, or |\fi|. */
1481    return;
1482  RESULT:
1483    if (unless) {
1484        result = ! result;
1485    }
1486    if (tracing_commands > 1) {
1487        /*tex Display the value of |b|. */
1488        tex_begin_diagnostic();
1489        tex_print_str(result ? "{true}" : "{false}");
1490        tex_end_diagnostic();
1491    }
1492    if (result) {
1493        tex_aux_change_if_limit(else_code, save_cond_ptr);
1494        /*tex Wait for |\else| or |\fi|. */
1495        return;
1496    } else {
1497        /*tex
1498            Skip to |\else| or |\fi|, then |goto common_ending|. In a construction like |\if \iftrue
1499            abc\else d\fi|, the first |\else| that we come to after learning that the |\if| is false
1500            is not the |\else| we're looking for. Hence the following curious logic is needed.
1501        */
1502        while (1) {
1503            unless = tex_aux_pass_text_x(tracing_ifs, tracing_commands);
1504            if (tracing_both) {
1505                tex_show_cmd_chr(cur_cmd, cur_chr);
1506            }
1507            if (lmt_condition_state.cond_ptr == save_cond_ptr) {
1508                /* still fragile for |\unless| and |\expandafter| etc. */
1509                if (cur_chr >= first_real_if_test_code) {
1510                    if (cur_chr == if_condition_code) {
1511                     // goto COMMON_ENDING;
1512                        tex_aux_pop_condition_stack();
1513                        return;
1514                    } else {
1515                        code = cur_chr;
1516                        goto HERE;
1517                    }
1518                } else if (cur_chr != or_code) {
1519                    goto COMMON_ENDING;
1520                } else {
1521                    tex_handle_error(
1522                        normal_error_type,
1523                        "Extra \\or",
1524                        "I'm ignoring this; it doesn't match any \\if."
1525                    );
1526                }
1527            } else if (cur_chr == fi_code) {
1528                tex_aux_pop_condition_stack();
1529            }
1530        }
1531    }
1532  COMMON_ENDING:
1533    if (cur_chr == fi_code) {
1534        tex_aux_pop_condition_stack();
1535    } else {
1536        /*tex Wait for |\fi|. */
1537//lmt_condition_state.if_step = code;
1538        lmt_condition_state.if_limit = fi_code;
1539    }
1540}
1541
1542/*tex
1543    Terminate the current conditional and skip to |\fi| The processing of conditionals is complete
1544    except for the following code, which is actually part of |expand|. It comes into play when
1545    |\or|, |\else|, or |\fi| is scanned.
1546*/
1547
1548void tex_conditional_fi_or_else(void)
1549{
1550    int tracing_ifs = tracing_ifs_par > 0;
1551    if (tracing_ifs && tracing_commands_par <= 1) {
1552        tex_show_cmd_chr(if_test_cmd, cur_chr);
1553    }
1554    if (cur_chr == or_else_code || cur_chr == or_unless_code) {
1555        tex_get_next_non_spacer();
1556    } else if (cur_chr > lmt_condition_state.if_limit) {
1557        if (lmt_condition_state.if_limit == if_code) {
1558            /*tex The condition is not yet evaluated. */
1559            tex_insert_relax_and_cur_cs();
1560        } else {
1561            tex_handle_error(normal_error_type,
1562                "Extra %C",
1563                if_test_cmd, cur_chr,
1564                "I'm ignoring this; it doesn't match any \\if."
1565            );
1566        }
1567        /*tex We don't pop the stack! */
1568        return;
1569    }
1570    /*tex Skip to |\fi|. */
1571    while (! (cur_cmd == if_test_cmd && cur_chr == fi_code)) {
1572        tex_aux_pass_text();
1573        if (tracing_ifs) {
1574            tex_show_cmd_chr(cur_cmd, cur_chr);
1575        }
1576    }
1577    tex_aux_pop_condition_stack();
1578}
1579
1580/*tex
1581
1582    Negate a boolean conditional and |goto reswitch|. The result of a boolean condition is reversed
1583    when the conditional is preceded by |\unless|. We silently ignore |\unless| for those tests that
1584    act like an |\ifcase|. In \ETEX\ there was an error message.
1585
1586*/
1587
1588void tex_conditional_unless(void)
1589{
1590    tex_get_token();
1591    if (cur_cmd == if_test_cmd) {
1592        if (tracing_commands_par > 1) {
1593            tex_show_cmd_chr(cur_cmd, cur_chr);
1594        }
1595        if (cur_chr != if_condition_code) {;
1596            tex_conditional_if(cur_chr, 1);
1597        }
1598    } else {
1599        tex_handle_error(back_error_type,
1600            "You can't use '\\unless' before '%C'",
1601            cur_cmd, cur_chr,
1602            "Continue, and I'll forget that it ever happened."
1603        );
1604    }
1605}
1606
1607void tex_show_ifs(void)
1608{
1609    if (lmt_condition_state.cond_ptr) {
1610        /*tex First we determine the |\if ... \fi| nesting. */
1611        int n = 0;
1612        {
1613            /*tex We start at the tail of a token list to show. */
1614            halfword p = lmt_condition_state.cond_ptr;
1615            do {
1616                ++n;
1617                p = node_next(p);
1618            } while (p);
1619        }
1620        /*tex Now reporting can start. */
1621        {
1622            halfword cond_ptr = lmt_condition_state.cond_ptr;
1623            int cur_if = lmt_condition_state.cur_if;
1624            int cur_unless = lmt_condition_state.cur_unless;
1625            int if_step = lmt_condition_state.if_step;
1626            int if_unless = lmt_condition_state.if_unless;
1627            int if_line = lmt_condition_state.if_line;
1628            int if_limit = lmt_condition_state.if_limit;
1629            do {
1630                if (cur_unless) {
1631                    if (if_line) {
1632                        tex_print_format("[conditional: level %i, current %C %C, limit %C, %sstep %C, line %i]",
1633                            n,
1634                            expand_after_cmd, expand_unless_code,
1635                            if_test_cmd, cur_if,
1636                            if_test_cmd, if_limit,
1637                            if_unless ? "unless " : "",
1638                            if_test_cmd, if_step,
1639                            if_line
1640                       );
1641                    } else {
1642                        tex_print_format("[conditional: level %i, current %C %C, limit %C, %sstep %C]",
1643                            n,
1644                            expand_after_cmd, expand_unless_code,
1645                            if_test_cmd, cur_if,
1646                            if_test_cmd, if_limit,
1647                            if_unless ? "unless " : "",
1648                            if_test_cmd, if_step
1649                        );
1650                    }
1651                } else {
1652                    if (if_line) {
1653                        tex_print_format("[conditional: level %i, current %C, limit %C, %sstep %C, line %i]",
1654                            n,
1655                            if_test_cmd, cur_if,
1656                            if_test_cmd, if_limit,
1657                            if_unless ? "unless " : "",
1658                            if_test_cmd, if_step,
1659                            if_line
1660                        );
1661                    } else {
1662                        tex_print_format("[conditional: level %i, current %C, limit %C, %sstep %C]",
1663                            n,
1664                            if_test_cmd, cur_if,
1665                            if_test_cmd, if_limit,
1666                            if_unless ? "unless " : "",
1667                            if_test_cmd, if_step
1668                        );
1669                    }
1670                }
1671                --n;
1672                cur_if = if_limit_subtype(cond_ptr);
1673                cur_unless = if_limit_unless(cond_ptr);;
1674                if_step = if_limit_step(cond_ptr);;
1675                if_unless = if_limit_stepunless(cond_ptr);;
1676                if_line = if_limit_line(cond_ptr);;
1677                if_limit = if_limit_type(cond_ptr);;
1678                cond_ptr = node_next(cond_ptr);
1679                if (cond_ptr) {
1680                    tex_print_levels();
1681                }
1682            } while (cond_ptr);
1683        }
1684    } else {
1685        tex_print_str("[conditional: none active]");
1686    }
1687}
1688
1689void tex_conditional_catch_up(void)
1690{
1691    condition_state_info saved_condition_state = lmt_condition_state;
1692    while (lmt_input_state.in_stack[lmt_input_state.in_stack_data.ptr].if_ptr != lmt_condition_state.cond_ptr) {
1693        /* todo, more info */
1694        tex_print_nlp();
1695        tex_print_format("Warning: end of file when %C", if_test_cmd, lmt_condition_state.cur_if);
1696        if (lmt_condition_state.if_limit == fi_code) {
1697            tex_print_str_esc("else");
1698        }
1699        if (lmt_condition_state.if_line) {
1700            tex_print_format(" entered on line %i", lmt_condition_state.if_line);
1701        }
1702        tex_print_str(" is incomplete");
1703        lmt_condition_state.cur_if = if_limit_subtype(lmt_condition_state.cond_ptr);
1704        lmt_condition_state.cur_unless = if_limit_unless(lmt_condition_state.cond_ptr);
1705        lmt_condition_state.if_step = if_limit_step(lmt_condition_state.cond_ptr);
1706        lmt_condition_state.if_unless = if_limit_stepunless(lmt_condition_state.cond_ptr);
1707        lmt_condition_state.if_limit = if_limit_type(lmt_condition_state.cond_ptr);
1708        lmt_condition_state.if_line = if_limit_line(lmt_condition_state.cond_ptr);
1709        lmt_condition_state.cond_ptr = node_next(lmt_condition_state.cond_ptr);
1710    }
1711    lmt_condition_state = saved_condition_state;
1712}
1713
1714/*tex 
1715
1716    There is no gain over |\expandafter| because backing up is what takes time. We just keep this 
1717    as reference.
1718*/ 
1719
1720/*
1721void tex_conditional_after_fi(void)
1722{
1723    halfword t = tex_get_token();
1724    int tracing_ifs = tracing_ifs_par > 0;
1725    int tracing_commands = tracing_commands_par > 0;
1726    while (1) {
1727        tex_aux_pass_text_x(tracing_ifs, tracing_commands);
1728        if (cur_chr == fi_code) {
1729            tex_aux_pop_condition_stack();
1730            break;
1731        } else {
1732            // some error
1733        }
1734    }
1735    tex_back_input(t);
1736}
1737*/
1738