texconditional.c /size: 61 Kb    last modification: 2024-01-16 10:22
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    .cur_unless    = 0,
46    .if_step       = 0,
47    .if_limit      = 0,
48    .if_line       = 0,
49    .if_nesting    = 0,
50    .if_unless     = 0,
51    .skip_line     = 0,
52    .chk_integer   = 0,
53    .chk_dimension = 0,
54    .padding       = 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
285inline static 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_val_int_code       : tex_print_format("{numval %i}",        case_value); break;
370        case if_cmp_int_code       : tex_print_format("{cmpnum %i}",        case_value); break;
371        case if_chk_dim_code       : tex_print_format("{chkdim %i}",        case_value); break;
372        case if_val_dim_code       : tex_print_format("{dimval %i}",        case_value); break;
373        case if_cmp_dim_code       : tex_print_format("{cmpdim %i}",        case_value); break;
374        case if_case_code          : tex_print_format("{case %i}",          case_value); break;
375        case if_math_parameter_code: tex_print_format("{mathparameter %i}", case_value); break;
376        case if_math_style_code    : tex_print_format("{mathstyle %i}",     case_value); break;
377        case if_arguments_code     : tex_print_format("{arguments %i}",     case_value); break;
378        case if_parameters_code    : tex_print_format("{parameter %i}",     case_value); break;
379        case if_parameter_code     : tex_print_format("{parameters %i}",    case_value); break;
380        default                    : tex_print_format("{todo %i}",          case_value); break;
381    }
382    tex_end_diagnostic();
383}
384
385/*tex Why do we skip over relax? */
386
387inline static halfword tex_aux_grab_toks(int expand, int expandlist, int *head)
388{
389    halfword p = null;
390    if (expand) {
391        do {
392            tex_get_x_token();
393        } while (cur_cmd == spacer_cmd || cur_cmd == relax_cmd);
394    } else {
395        do {
396            tex_get_token();
397        } while (cur_cmd == spacer_cmd || cur_cmd == relax_cmd);
398    }
399    switch (cur_cmd) {
400        case left_brace_cmd:
401            p = expandlist ? tex_scan_toks_expand(1, NULL, 0, 0) : tex_scan_toks_normal(1, NULL);
402            *head = p;
403            break;
404        case internal_toks_cmd:
405        case register_toks_cmd:
406            p = eq_value(cur_chr);
407            break;
408        case register_cmd:
409            /* is this okay? probably not as cur_val can be way to large */
410            if (cur_chr == token_val_level) {
411                halfword n = tex_scan_toks_register_number();
412                p = eq_value(register_toks_location(n));
413                break;
414            } else {
415                goto DEFAULT;
416            }
417        case cs_name_cmd:
418            if (cur_chr == last_named_cs_code) {
419                if (lmt_scanner_state.last_cs_name != null_cs) {
420                    p = eq_value(lmt_scanner_state.last_cs_name);
421                }
422                break;
423            } else { 
424                /* fall through */
425            }
426        case call_cmd:
427        case protected_call_cmd:
428        case semi_protected_call_cmd:
429        case constant_call_cmd:
430        case tolerant_call_cmd:
431        case tolerant_protected_call_cmd:
432        case tolerant_semi_protected_call_cmd:
433            p = eq_value(cur_cs);
434            break;
435        default:
436          DEFAULT:
437            {
438                halfword n;
439                tex_back_input(cur_tok);
440                n = tex_scan_toks_register_number();
441                p = eq_value(register_toks_location(n));
442                break;
443            }
444    }
445    /* skip over the ref count */
446    return p ? token_link(p) : null;
447}
448
449// inline static halfword tex_aux_scan_comparison(int code)
450// {
451//     halfword r;
452//     do {
453//         tex_get_x_token();
454//     } while (cur_cmd == spacer_cmd);
455//     r = cur_tok - other_token;
456//     if ((r < '<') || (r > '>')) {
457//         tex_aux_missing_equal_error(code);
458//         return '=';
459//     } else {
460//         return r;
461//     }
462// }
463
464//inline static halfword tex_aux_scan_comparison(int code)
465//{
466//    do {
467//        tex_get_x_token();
468//    } while (cur_cmd == spacer_cmd);
469//    if (cur_cmd != other_char_cmd || (cur_chr < '<') || (cur_chr > '>')) {
470//        tex_aux_missing_equal_error(code);
471//        return '=';
472//    } else {
473//        return cur_chr;
474//    }
475//}
476
477typedef enum comparisons { 
478    /* case 0 .. 7 */
479    comparison_equal       = 0,
480    comparison_less        = 1,
481    comparison_greater     = 2,
482    comparison_not_equal   = 3,
483    comparison_not_less    = 4,
484    comparison_not_greater = 5,
485    comparison_element     = 6,
486    comparison_not_element = 7,
487} comparisons; 
488
489typedef enum values { 
490    /* case 1 .. 4 */
491    value_less    = 1,
492    value_equal   = 2,
493    value_greater = 3,
494    value_error   = 4,
495} values;
496
497typedef enum checks { 
498    /* case 1 .. 2 */
499    check_okay    = 1,
500    check_error   = 2,
501} checks;
502
503typedef enum parameterstates { 
504    parameter_zero  = 0,
505    parameter_set   = 1,
506    parameter_unset = 2,
507} parameterstates;
508
509inline static halfword tex_aux_scan_comparison(int code)
510{
511    bool negate = false;
512    while (1) {
513        tex_get_x_token();
514        switch (cur_cmd) { 
515            case letter_cmd: 
516            case other_char_cmd: 
517                switch (cur_chr) { 
518                    /* traditional */
519                    case '='   : return negate ? comparison_not_equal   : comparison_equal;
520                    case '<'   : return negate ? comparison_not_less    : comparison_less;
521                    case '>'   : return negate ? comparison_not_greater : comparison_greater;
522                    /* bonus */
523                    case '!'   : negate = ! negate ; continue;
524                    /* neat */
525                    case 0x2208: return negate ? comparison_not_element : comparison_element; 
526                    case 0x2209: return negate ? comparison_element     : comparison_not_element;
527                    case 0x2260: return negate ? comparison_equal       : comparison_not_equal;
528                    case 0x2264: return negate ? comparison_greater     : comparison_not_greater;
529                    case 0x2265: return negate ? comparison_less        : comparison_not_less; 
530                    case 0x2270: return negate ? comparison_greater     : comparison_not_greater;
531                    case 0x2271: return negate ? comparison_less        : comparison_not_less; 
532                }
533            case spacer_cmd: 
534                continue;
535            default:
536                tex_aux_missing_equal_error(code);
537                return 0;
538        }
539    } 
540}
541
542inline static void tex_aux_check_strict(int *result)
543{
544    tex_get_x_token();
545    switch (cur_cmd) { 
546        case relax_cmd:
547        case spacer_cmd: 
548        case if_test_cmd:
549            break;
550        default: 
551            *result = check_error;
552            break;
553    }
554    tex_back_input(cur_tok);
555}
556
557void tex_conditional_if(halfword code, int unless)
558{
559    /*tex The result or case value. */
560    int result = 0;
561    /*tex The |cond_ptr| corresponding to this conditional: */
562    halfword save_cond_ptr;
563    /*tex Tracing options */
564    int tracing_ifs = tracing_ifs_par > 0;
565    int tracing_commands = tracing_commands_par;
566    int tracing_both = tracing_ifs && (tracing_commands <= 1);
567    if (tracing_both) {
568        tex_show_cmd_chr(cur_cmd, cur_chr);
569    }
570    tex_aux_push_condition_stack(code, unless);
571    save_cond_ptr = lmt_condition_state.cond_ptr;
572    /*tex Either process |\ifcase| or set |b| to the value of a boolean condition. */
573  HERE:
574    /*tex We can get back here so we need to make sure result is always set! */
575    lmt_condition_state.if_step = code;
576    lmt_condition_state.if_unless = unless;
577    switch (code) {
578        case if_char_code:
579        case if_cat_code:
580            /*tex Test if two characters match. Seldom used, this one. */
581            {
582                halfword n, m;
583                tex_aux_get_x_token_or_active_char();
584                if ((cur_cmd > active_char_cmd) || (cur_chr > max_character_code)) {
585                    /*tex It's not a character. */
586                    m = relax_cmd;
587                    n = relax_code;
588                } else {
589                    m = cur_cmd;
590                    n = cur_chr;
591                }
592                tex_aux_get_x_token_or_active_char();
593                if ((cur_cmd > active_char_cmd) || (cur_chr > max_character_code)) {
594                    cur_cmd = relax_cmd;
595                    cur_chr = relax_code;
596                }
597                result = code == if_char_code ? (n == cur_chr) : (m == cur_cmd);
598            }
599            goto RESULT;
600        case if_int_code:
601        case if_abs_int_code:
602            {
603                halfword n1 = tex_scan_integer(0, NULL);
604                halfword cp = tex_aux_scan_comparison(code);
605                halfword n2 = tex_scan_integer(0, NULL);
606                if (code == if_abs_int_code) {
607                    if (n1 < 0) {
608                        n1 = -n1;
609                    }
610                    if (n2 < 0) {
611                        n2 = -n2;
612                    }
613                }
614                switch (cp) {
615                    case comparison_equal      : result = (n1 == n2); break;
616                    case comparison_less       : result = (n1 <  n2); break;
617                    case comparison_greater    : result = (n1  > n2); break;
618                    case comparison_not_equal  : result = (n1 != n2); break;
619                    case comparison_not_less   : result = (n1 >= n2); break;
620                    case comparison_not_greater: result = (n1 <= n2); break;
621                    case comparison_element    : result = (n1 & n2) == n1; break;
622                    case comparison_not_element: result = (n1 & n2) != n1; break;
623                }
624            }
625            goto RESULT;
626        case if_zero_int_code:
627            result = tex_scan_integer(0, NULL) == 0;
628            goto RESULT;
629        case if_interval_int_code:
630            {
631                scaled n0 = tex_scan_integer(0, NULL);
632                scaled n1 = tex_scan_integer(0, NULL);
633                scaled n2 = tex_scan_integer(0, NULL);
634                result = n1 - n2;
635                result = result == 0 ? 1 : (result > 0 ? result <= n0 : -result <= n0);
636            }
637            goto RESULT;
638        case if_posit_code:
639        case if_abs_posit_code:
640            {
641                halfword n1 = tex_scan_posit(0);
642                halfword cp = tex_aux_scan_comparison(code);
643                halfword n2 = tex_scan_posit(0);
644                if (code == if_abs_posit_code) {
645                    tex_posit zero = tex_integer_to_posit(0);
646                    if (tex_posit_lt(n1,zero.v)) {
647                        n1 = tex_posit_neg(n1);
648                    }
649                    if (tex_posit_lt(n2,zero.v)) {
650                        n2 = tex_posit_neg(n2);
651                    }
652                }
653                switch (cp) {
654                    case comparison_equal      : result = tex_posit_eq(n1,n2); break;
655                    case comparison_less       : result = tex_posit_lt(n1,n2); break;
656                    case comparison_greater    : result = tex_posit_gt(n1,n2); break;
657                    case comparison_not_equal  : result = tex_posit_ne(n1,n2); break;
658                    case comparison_not_less   : result = tex_posit_gt(n1,n2); break;
659                    case comparison_not_greater: result = tex_posit_lt(n1,n2); break;
660                    case comparison_element    : result = tex_posit_eq(tex_integer_to_posit(tex_posit_to_integer(n1) & tex_posit_to_integer(n2)).v,n1); break;
661                    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;
662                }
663            }
664            goto RESULT;
665        case if_zero_posit_code:
666            result = tex_posit_eq_zero(tex_scan_posit(0));
667            goto RESULT;
668        case if_interval_posit_code:
669            {
670                halfword n0 = tex_scan_posit(0);
671                halfword n1 = tex_scan_posit(0);
672                halfword n2 = tex_scan_posit(0);
673                result = tex_posit_sub(n1, n2);
674                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));
675            }
676            goto RESULT;
677        case if_dim_code:
678        case if_abs_dim_code:
679            {
680                scaled n1 = tex_scan_dimension(0, 0, 0, 0, NULL);
681                halfword cp = tex_aux_scan_comparison(code);
682                scaled n2 = tex_scan_dimension(0, 0, 0, 0, NULL);
683                if (code == if_abs_dim_code) {
684                    if (n1 < 0) {
685                        n1 = -n1;
686                    }
687                    if (n2 < 0) {
688                        n2 = -n2;
689                    }
690                }
691                switch (cp) {
692                    case comparison_equal      : result = (n1 == n2); break;
693                    case comparison_less       : result = (n1 <  n2); break;
694                    case comparison_greater    : result = (n1  > n2); break;
695                    case comparison_not_equal  : result = (n1 != n2); break;
696                    case comparison_not_less   : result = (n1 >= n2); break;
697                    case comparison_not_greater: result = (n1 <= n2); break;
698                    case comparison_element    : result = (n1/65536 & n2/65536) == n1/65536; break; /* maybe we should round */
699                    case comparison_not_element: result = (n1/65536 & n2/65536) != n1/65536; break; /* maybe we should round */
700                }
701            }
702            goto RESULT;
703        case if_zero_dim_code:
704            result = tex_scan_dimension(0, 0, 0, 0, NULL) == 0;
705            goto RESULT;
706        case if_interval_dim_code:
707            {
708                scaled n0 = tex_scan_dimension(0, 0, 0, 0, NULL);
709                scaled n1 = tex_scan_dimension(0, 0, 0, 0, NULL);
710                scaled n2 = tex_scan_dimension(0, 0, 0, 0, NULL);
711                result = n1 - n2;
712                result = result == 0 ? 1 : (result > 0 ? result <= n0 : -result <= n0);
713            }
714            goto RESULT;
715        case if_odd_code:
716            result = odd(tex_scan_integer(0, NULL));
717            goto RESULT;
718        case if_vmode_code:
719            result = is_v_mode(cur_list.mode);
720            goto RESULT;
721        case if_hmode_code:
722            result = is_h_mode(cur_list.mode);
723            goto RESULT;
724        case if_mmode_code:
725            result = is_m_mode(cur_list.mode);
726            goto RESULT;
727        case if_inner_code:
728            result = cur_list.mode < nomode;
729            goto RESULT;
730        case if_void_code:
731            {
732                halfword n = tex_scan_box_register_number();
733                result = box_register(n) == null;
734            }
735            goto RESULT;
736        case if_hbox_code:
737            {
738                halfword n = tex_scan_box_register_number();
739                halfword p = box_register(n);
740                result = p && (node_type(p) == hlist_node);
741            }
742            goto RESULT;
743        case if_vbox_code:
744            {
745                halfword n = tex_scan_box_register_number();
746                halfword p = box_register(n);
747                result = p && (node_type(p) == vlist_node);
748            }
749            goto RESULT;
750        case if_tok_code:
751        case if_cstok_code:
752            {
753                halfword pp = null;
754                halfword qq = null;
755                halfword p, q;
756                int expand = code == if_tok_code;
757                int save_scanner_status = lmt_input_state.scanner_status;
758                lmt_input_state.scanner_status = scanner_is_normal;
759                p = tex_aux_grab_toks(expand, 1, &pp);
760                q = tex_aux_grab_toks(expand, 1, &qq);
761                if (p == q) {
762                    /* this is sneaky, a list always is different */
763                    result = 1;
764                } else {
765                    while (p && q) {
766                        if (token_info(p) != token_info(q)) {
767                         // p = null;
768                         // break;
769                            result = 0;
770                            goto IFTOKDONE;
771                        } else {
772                            p = token_link(p);
773                            q = token_link(q);
774                        }
775                    }
776                    result = (! p) && (! q);
777                }
778             IFTOKDONE:
779                if (pp) {
780                    tex_flush_token_list(pp);
781                }
782                if (qq) {
783                    tex_flush_token_list(qq);
784                }
785                lmt_input_state.scanner_status = save_scanner_status;
786            }
787            goto RESULT;
788        case if_last_named_cs_code:
789        case if_x_code:
790            {
791                /*tex
792                    Test if two tokens match. Note that |\ifx| will declare two macros different
793                    if one is |\long| or |\outer| and the other isn't, even though the texts of
794                    the macros are the same.
795
796                    We need to reset |scanner_status|, since |\outer| control sequences are
797                    allowed, but we might be scanning a macro definition or preamble.
798
799                    This is no longer true as we dropped these properties but it does apply to
800                    protected macros and such.
801                 */
802             // halfword p, q, n;
803                halfword p, q;
804                int save_scanner_status = lmt_input_state.scanner_status;
805                lmt_input_state.scanner_status = scanner_is_normal;
806                if (code == if_x_code) {
807                    tex_get_next();
808                 // n = cur_cs;
809                    p = cur_cmd;
810                    q = cur_chr;
811                } else {
812                 // n = lmt_scanner_state.last_cs_name;
813                    p = eq_type(lmt_scanner_state.last_cs_name); 
814                    q = eq_value(lmt_scanner_state.last_cs_name);
815                }
816                tex_get_next();
817                if ((p == constant_call_cmd && cur_cmd == call_cmd) || (p == call_cmd && cur_cmd == constant_call_cmd)) {
818                    /*tex This is a somewhat special case. */
819                    goto SOMECALLCMD;
820                } else if (cur_cmd != p) {
821                    result = 0;
822                } else if (cur_cmd < call_cmd) {
823                    result = cur_chr == q;
824                } else {
825                    SOMECALLCMD:
826                    /*tex
827                        Test if two macro texts match. Note also that |\ifx| decides that macros
828                        |\a| and |\b| are different in examples like this:
829
830                        \starttyping
831                        \def\a{\c}  \def\c{}
832                        \def\b{\d}  \def\d{}
833                        \stoptyping
834
835                        We acctually have commands beyond valid call commands but they are never 
836                        seen here. 
837                    */
838                    p = token_link(cur_chr);
839                    /*tex Omit reference counts. */
840                 // q = token_link(eq_value(n));
841                    q = token_link(q);
842                    if (p == q) {
843                        result = 1;
844                    /*
845                    } else if (! q) {
846                        result = 0;
847                    */
848                    } else {
849                        while (p && q) {
850                            if (token_info(p) != token_info(q)) {
851                             // p = null;
852                             // break;
853                                result = 0;
854                                goto IFXDONE;
855                            } else {
856                                p = token_link(p);
857                                q = token_link(q);
858                            }
859                        }
860                        result = (! p) && (! q);
861                    }
862                }
863              IFXDONE:
864                lmt_input_state.scanner_status = save_scanner_status;
865            }
866            goto RESULT;
867        case if_true_code:
868            result = 1;
869            goto RESULT;
870        case if_false_code:
871            result = 0;
872            goto RESULT;
873        case if_chk_int_code:
874            {
875                lmt_error_state.intercept = 1; /* maybe ++ and -- so that we can nest */
876                lmt_error_state.last_intercept = 0;
877                lmt_condition_state.chk_integer = 0;
878                tex_scan_integer_validate(); 
879                result = lmt_error_state.last_intercept ? check_error : check_okay;
880                lmt_error_state.intercept = 0;
881                lmt_error_state.last_intercept = 0;
882                goto CASE;
883            }
884        case if_chk_integer_code:
885            {
886                lmt_error_state.intercept = 1; /* maybe ++ and -- so that we can nest */
887                lmt_error_state.last_intercept = 0;
888                lmt_condition_state.chk_integer = tex_scan_integer(0, NULL); 
889                result = lmt_error_state.last_intercept ? check_error : check_okay;
890                if (result == check_okay) { 
891                    tex_aux_check_strict(&result);
892                }
893                lmt_error_state.intercept = 0;
894                lmt_error_state.last_intercept = 0;
895                goto CASE;
896            }
897        case if_val_int_code:
898            {
899                lmt_error_state.intercept = 1;
900                lmt_error_state.last_intercept = 0;
901                lmt_condition_state.chk_integer = tex_scan_integer(0, NULL);
902                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;
903                lmt_error_state.intercept = 0;
904                lmt_error_state.last_intercept = 0;
905                goto CASE;
906            }
907        case if_cmp_int_code:
908            {
909                halfword n1 = tex_scan_integer(0, NULL);
910                halfword n2 = tex_scan_integer(0, NULL);
911                result = (n1 < n2) ? 0 : (n1 > n2) ? 2 : 1;
912                goto CASE;
913            }
914        case if_chk_dim_code:
915            {
916                lmt_error_state.intercept = 1;
917                lmt_error_state.last_intercept = 0;
918                lmt_condition_state.chk_dimension = 0;
919                tex_scan_dimension_validate(); 
920                result = lmt_error_state.last_intercept ? check_error : check_okay;
921                lmt_error_state.intercept = 0;
922                lmt_error_state.last_intercept = 0;
923                goto CASE;
924            }
925        case if_chk_dimension_code:
926            {
927                lmt_error_state.intercept = 1;
928                lmt_error_state.last_intercept = 0;
929                lmt_condition_state.chk_dimension = tex_scan_dimension(0, 0, 0, 0, NULL); 
930                result = lmt_error_state.last_intercept ? check_error : check_okay;
931                if (result == check_okay) { 
932                    tex_aux_check_strict(&result);
933                }
934                lmt_error_state.intercept = 0;
935                lmt_error_state.last_intercept = 0;
936                goto CASE;
937            }
938        case if_val_dim_code:
939            {
940                lmt_error_state.intercept = 1;
941                lmt_error_state.last_intercept = 0;
942                lmt_condition_state.chk_dimension = tex_scan_dimension(0, 0, 0, 0, NULL);
943                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;
944                lmt_error_state.intercept = 0;
945                lmt_error_state.last_intercept = 0;
946                goto CASE;
947            }
948        case if_cmp_dim_code:
949            {
950                scaled n1 = tex_scan_dimension(0, 0, 0, 0, NULL);
951                scaled n2 = tex_scan_dimension(0, 0, 0, 0, NULL);
952                result = (n1 < n2) ? 0 : (n1 > n2) ? 2 : 1;
953                goto CASE;
954            }
955        case if_case_code:
956            /*tex Select the appropriate case and |return| or |goto common_ending|. */
957            result = tex_scan_integer(0, NULL);
958            goto CASE;
959        case if_defined_code:
960            /*tex
961                The conditional |\ifdefined| tests if a control sequence is defined. We need to
962                reset |scanner_status|, since |\outer| control sequences are allowed, but we
963                might be scanning a macro definition or preamble.
964            */
965            {
966                int save_scanner_status = lmt_input_state.scanner_status;
967                lmt_input_state.scanner_status = scanner_is_normal;
968                tex_get_next();
969                result = cur_cmd != undefined_cs_cmd;
970                lmt_input_state.scanner_status = save_scanner_status;
971                goto RESULT;
972            }
973        case if_csname_code:
974            result = tex_is_valid_csname();
975            goto RESULT;
976        case if_in_csname_code:
977            /*tex This one will go away. */
978            result = lmt_expand_state.cs_name_level;
979            goto RESULT;
980        case if_font_char_code:
981            /*tex The conditional |\iffontchar| tests the existence of a character in a font. */
982            {
983                halfword fnt = tex_scan_font_identifier(NULL);
984                halfword chr = tex_scan_char_number(0);
985                result = tex_char_exists(fnt, chr);
986            }
987            goto RESULT;
988        case if_condition_code:
989            /*tex This can't happen! */
990            goto RESULT;
991        case if_flags_code:
992            {
993                halfword cs; 
994                singleword flag;
995                tex_get_r_token();
996                cs = cur_cs; 
997                flag = eq_flag(cs);
998                /* todo: each prefix */
999                tex_get_token();
1000                if (cur_cmd == prefix_cmd) {
1001                    switch (cur_chr) {
1002                        /*tex We check flags: */
1003                        case frozen_code        : result = is_frozen   (flag); break;
1004                        case permanent_code     : result = is_permanent(flag); break;
1005                        case immutable_code     : result = is_immutable(flag); break;
1006                     /* case primitive_code     : result = is_primitive(flag); break; */
1007                        case mutable_code       : result = is_mutable  (flag); break;
1008                        case noaligned_code     : result = is_noaligned(flag); break;
1009                        case instance_code      : result = is_instance (flag); break;
1010                        case untraced_code      : result = is_untraced (flag); break;
1011                        /*tex We check cmd: */
1012                        case global_code        : result = eq_level(cs) == level_one;; break;
1013                        case tolerant_code      : result = is_tolerant_cmd(eq_type(cs)); break;
1014                        case protected_code     : result = is_protected_cmd(eq_type(cs)); break;
1015                        case semiprotected_code : result = is_semi_protected_cmd(eq_type(cs)); break;
1016                        case constant_code      : result = is_constant_cmd(eq_type(cs)); break;
1017                    }
1018                } else {
1019                    int fl; 
1020                    tex_back_input(cur_tok);
1021                    fl = tex_scan_integer(1, NULL); 
1022                    result = (flag & fl) == fl;
1023                    if (! result) {
1024                        if (is_protected(fl)) {
1025                            result = is_protected_cmd(eq_type(cs));
1026                        } else if (is_semiprotected(fl)) {
1027                            result = is_semi_protected_cmd(eq_type(cs));
1028                        } else if (is_tolerant(fl)) {
1029                            result = is_tolerant_cmd(eq_type(cs));
1030                        } else if (is_global(fl)) {
1031                            result = eq_level(cs) == level_one;
1032                        }
1033                    }
1034                }
1035                goto RESULT;
1036            }
1037        case if_empty_code:
1038            {
1039                tex_get_token();
1040              EMPTY_CHECK_AGAIN:
1041                switch (cur_cmd) {
1042                    case call_cmd:
1043                    case constant_call_cmd:
1044                        result = ! token_link(cur_chr);
1045                        break;
1046                    case internal_toks_reference_cmd:
1047                    case register_toks_reference_cmd:
1048                        result = ! token_link(cur_chr);
1049                        break;
1050                    case register_cmd:
1051                        /*tex See |tex_aux_grab_toks|. */
1052                        if (cur_chr == token_val_level) {
1053                            halfword n = tex_scan_toks_register_number();
1054                            halfword p = eq_value(register_toks_location(n));
1055                            result = ! p || ! token_link(p);
1056                        } else {
1057                            result = 0;
1058                        }
1059                        break;
1060                    case internal_toks_cmd:
1061                    case register_toks_cmd:
1062                        { 
1063                            halfword p = eq_value(cur_chr);   
1064                            result = ! p || ! token_link(p);
1065                        }
1066                        break;
1067                    case left_brace_cmd:
1068                        {
1069                            halfword h = tex_scan_toks_expand(1, NULL, 0, 0);
1070                            result = token_link(h) == null; 
1071                            tex_flush_token_list(h);
1072                        }
1073                        break;
1074                    case cs_name_cmd:
1075                        if (cur_chr == last_named_cs_code && lmt_scanner_state.last_cs_name != null_cs) {
1076                            cur_cmd = eq_type(lmt_scanner_state.last_cs_name);
1077                            cur_chr = eq_value(lmt_scanner_state.last_cs_name);
1078                            goto EMPTY_CHECK_AGAIN;
1079                        } 
1080                        /* fall through */
1081                    default:
1082                        result = 0;
1083                }
1084                goto RESULT;
1085            }
1086        case if_relax_code:
1087            {
1088                tex_get_token();
1089                result = cur_cmd == relax_cmd;
1090                goto RESULT;
1091            }
1092        case if_boolean_code:
1093            result = tex_scan_integer(0, NULL) ? 1 : 0;
1094            goto RESULT;
1095        case if_numexpression_code:
1096            result = tex_scanned_expression(integer_val_level) ? 1 : 0;
1097            goto RESULT;
1098        case if_dimexpression_code:
1099            result = tex_scanned_expression(dimension_val_level) ? 1 : 0;
1100            goto RESULT;
1101        case if_math_parameter_code:
1102            /*tex
1103                A value of |1| means that the parameter is set to a non-zero value, while |2| means
1104                that it is unset.
1105            */
1106            {
1107                do {
1108                    tex_get_x_token();
1109                } while (cur_cmd == spacer_cmd);
1110                if (cur_cmd == math_parameter_cmd) {
1111                    int code = cur_chr;
1112                    int style = tex_scan_math_style_identifier(0, 0);
1113                    result = tex_get_math_parameter(style, code, NULL);
1114                    if (result == max_dimension) {
1115                        result = parameter_unset; 
1116                    } else if (result) {
1117                        result = parameter_set;
1118                    } else { 
1119                        result = parameter_zero; 
1120                    }
1121                } else {
1122                    tex_normal_error("mathparameter", "a valid parameter expected");
1123                    result = parameter_zero;
1124                }
1125                goto CASE;
1126            }
1127        case if_math_style_code:
1128            result = tex_current_math_style();
1129            goto CASE;
1130        case if_arguments_code:
1131            result = lmt_expand_state.arguments;
1132            goto CASE;
1133        case if_parameters_code:
1134            /*tex
1135                The result has the last non-null count. We could have the test in the for but let's
1136                keep it readable.
1137            */
1138            result = tex_get_parameter_count();
1139            goto CASE;
1140        case if_parameter_code:
1141            {
1142                /*tex
1143                    We need to pick up the next token but avoid replacement by the parameter which
1144                    happens in the getters: 0 = no parameter, 1 = okay, 2 = empty. This permits
1145                    usage like |\ifparameter#2\or yes\else no\fi| as with the other checkers.
1146                */
1147                if (lmt_input_state.cur_input.loc) {
1148                    halfword t = token_info(lmt_input_state.cur_input.loc);
1149                    if (t < cs_token_flag && token_cmd(t) == parameter_reference_cmd) {
1150                        lmt_input_state.cur_input.loc = token_link(lmt_input_state.cur_input.loc);
1151                        result = lmt_input_state.parameter_stack[lmt_input_state.cur_input.parameter_start + token_chr(t) - 1] != null ? 1 : 2;
1152                    } else {
1153                        /*tex 
1154                            We have a replacement text so we check and backtrack. This is somewhat
1155                            tricky because a parameter can be a condition but we assume sane usage. 
1156                        */
1157                        tex_get_token();
1158                        result = cur_cmd == if_test_cmd ? 2 : 1;
1159                    }          
1160                }
1161                goto CASE;
1162            }
1163        case if_has_tok_code:
1164            {
1165                halfword qq = null;
1166                halfword p, q;
1167                int save_scanner_status = lmt_input_state.scanner_status;
1168                lmt_input_state.scanner_status = scanner_is_normal;
1169                p = tex_get_token();
1170                q = tex_aux_grab_toks(0, 0, &qq);
1171                if (p == q) {
1172                    result = 1;
1173                } else {
1174                    result = 0;
1175                    while (q) {
1176                        if (p == token_info(q)) {
1177                            result = 1;
1178                            break;
1179                        } else {
1180                            q = token_link(q);
1181                        }
1182                    }
1183                }
1184                if (qq) {
1185                    tex_flush_token_list(qq);
1186                }
1187                lmt_input_state.scanner_status = save_scanner_status;
1188                goto RESULT;
1189            }
1190        case if_has_toks_code:
1191        case if_has_xtoks_code:
1192            {
1193                halfword pp = null;
1194                halfword p;
1195                int expand = code == if_has_xtoks_code;
1196                int save_scanner_status = lmt_input_state.scanner_status;
1197                lmt_input_state.scanner_status = scanner_is_normal;
1198                p = tex_aux_grab_toks(expand, expand, &pp);
1199                if (p) {
1200                    halfword qq = null;
1201                    halfword q = tex_aux_grab_toks(expand, expand, &qq);
1202                    if (p == q) {
1203                        result = 1;
1204                    } else {
1205                        int qh = q;
1206                        int ph = p;
1207                        result = 0;
1208                        while (p && q) {
1209                            halfword pt = token_info(p);
1210                            halfword qt = token_info(q);
1211                          AGAIN:
1212                            if (pt == qt) {
1213                                p = token_link(p);
1214                                q = token_link(q);
1215                            } else if (token_cmd(pt) == ignore_cmd && token_cmd(qt) >= ignore_cmd && token_cmd(qt) <= other_char_cmd) {
1216                                p = token_link(p);
1217                                if (token_chr(pt) == token_chr(qt)) {
1218                                    q = token_link(q);
1219                                } else {
1220                                    pt = token_info(p);
1221                                    goto AGAIN;
1222                                }
1223                            } else {
1224                                p = ph;
1225                                q = token_link(qh);
1226                                qh = q;
1227                            }
1228                            if (! p) {
1229                                result = 1;
1230                                break;
1231                            }
1232                        }
1233                    }
1234                    if (qq) {
1235                        tex_flush_token_list(qq);
1236                    }
1237                }
1238                if (pp) {
1239                    tex_flush_token_list(pp);
1240                }
1241                lmt_input_state.scanner_status = save_scanner_status;
1242                goto RESULT;
1243            }
1244        case if_has_char_code:
1245            {
1246                halfword tok;
1247                halfword qq = null;
1248                halfword q;
1249                int save_scanner_status = lmt_input_state.scanner_status;
1250                lmt_input_state.scanner_status = scanner_is_normal;
1251                tok = tex_get_token();
1252                q = tex_aux_grab_toks(0, 0, &qq);
1253                if (q) {
1254                    int nesting = 0;
1255                    result = 0;
1256                    while (q) {
1257                        if (! nesting && token_info(q) == tok) {
1258                            result = 1;
1259                            break;
1260                        } else if (token_cmd(token_info(q)) == left_brace_cmd) {
1261                            nesting += 1;
1262                        } else if (token_cmd(token_info(q)) == right_brace_cmd) {
1263                            nesting -= 1;
1264                        }
1265                        q = token_link(q);
1266                    }
1267                }
1268                if (qq) {
1269                    tex_flush_token_list(qq);
1270                }
1271                lmt_input_state.scanner_status = save_scanner_status;
1272                goto RESULT;
1273            }
1274        case if_insert_code:
1275            {
1276                /* beware: it tests */
1277                result = ! tex_insert_is_void(tex_scan_integer(0, NULL));
1278                goto RESULT;
1279            }
1280        case if_in_alignment_code:
1281            {
1282                result = tex_in_alignment();
1283                goto RESULT;
1284            }
1285     // case if_bitwise_and_code:
1286     //     {
1287     //         halfword n1 = scan_integer(0, NULL);
1288     //         halfword n2 = scan_integer(0, NULL);
1289     //         result = n1 & n2 ? 1 : 0;
1290     //         goto RESULT;
1291     //     }
1292        default:
1293            {
1294                int category;
1295                strnumber u = tex_save_cur_string();
1296                int save_scanner_status = lmt_input_state.scanner_status;
1297                lmt_input_state.scanner_status = scanner_is_normal;
1298                lmt_token_state.luacstrings = 0;
1299                category = lmt_function_call_by_category(code - last_if_test_code, 0, &result);
1300                tex_restore_cur_string(u);
1301                lmt_input_state.scanner_status = save_scanner_status;
1302                if (lmt_token_state.luacstrings > 0) {
1303                    tex_lua_string_start();
1304                }
1305                switch (category) {
1306                    case lua_value_integer_code:
1307                    case lua_value_cardinal_code:
1308                    case lua_value_dimension_code:
1309                        goto CASE;
1310                    case lua_value_boolean_code:
1311                        goto RESULT;
1312                    case lua_value_conditional_code:
1313                        /* can we stay in the condition */
1314                        tex_back_input(token_val(if_test_cmd, if_condition_code));
1315                        tex_aux_pop_condition_stack();
1316                        return;
1317                    default:
1318                        result = 0;
1319                        goto RESULT;
1320                }
1321            }
1322    }
1323  CASE:
1324    /*tex
1325       To be considered: |if (unless) { result = max_integer - result; }| so that we hit |\else|
1326       and can do |\unless \ifcase \zero... \else \fi|.
1327    */
1328    if (tracing_commands > 1) {
1329        tex_aux_show_if_state(code, result);
1330    }
1331    while (result) {
1332        unless = tex_aux_pass_text_x(tracing_ifs, tracing_commands);
1333        if (tracing_both) {
1334            tex_show_cmd_chr(cur_cmd, cur_chr);
1335        }
1336        if (lmt_condition_state.cond_ptr == save_cond_ptr) {
1337            if (cur_chr >= first_real_if_test_code) {
1338                /*tex
1339                    We have an |or_else_cmd| here, but keep in mind that |\expandafter \ifx| and
1340                    |\unless \ifx| and |\ifcondition| don't work in such cases! We stay in this
1341                    function call.
1342                */
1343                if (cur_chr == if_condition_code) {
1344                 // goto COMMON_ENDING;
1345                    tex_aux_pop_condition_stack();
1346                    return;
1347                } else {
1348                    code = cur_chr;
1349                    goto HERE;
1350                }
1351            } else if (cur_chr == or_code) {
1352                --result;
1353            } else {
1354                goto COMMON_ENDING;
1355            }
1356        } else if (cur_chr == fi_code) {
1357            tex_aux_pop_condition_stack();
1358        }
1359    }
1360    tex_aux_change_if_limit(or_code, save_cond_ptr);
1361    /*tex Wait for |\or|, |\else|, or |\fi|. */
1362    return;
1363  RESULT:
1364    if (unless) {
1365        result = ! result;
1366    }
1367    if (tracing_commands > 1) {
1368        /*tex Display the value of |b|. */
1369        tex_begin_diagnostic();
1370        tex_print_str(result ? "{true}" : "{false}");
1371        tex_end_diagnostic();
1372    }
1373    if (result) {
1374        tex_aux_change_if_limit(else_code, save_cond_ptr);
1375        /*tex Wait for |\else| or |\fi|. */
1376        return;
1377    } else {
1378        /*tex
1379            Skip to |\else| or |\fi|, then |goto common_ending|. In a construction like |\if \iftrue
1380            abc\else d\fi|, the first |\else| that we come to after learning that the |\if| is false
1381            is not the |\else| we're looking for. Hence the following curious logic is needed.
1382        */
1383        while (1) {
1384            unless = tex_aux_pass_text_x(tracing_ifs, tracing_commands);
1385            if (tracing_both) {
1386                tex_show_cmd_chr(cur_cmd, cur_chr);
1387            }
1388            if (lmt_condition_state.cond_ptr == save_cond_ptr) {
1389                /* still fragile for |\unless| and |\expandafter| etc. */
1390                if (cur_chr >= first_real_if_test_code) {
1391                    if (cur_chr == if_condition_code) {
1392                     // goto COMMON_ENDING;
1393                        tex_aux_pop_condition_stack();
1394                        return;
1395                    } else {
1396                        code = cur_chr;
1397                        goto HERE;
1398                    }
1399                } else if (cur_chr != or_code) {
1400                    goto COMMON_ENDING;
1401                } else {
1402                    tex_handle_error(
1403                        normal_error_type,
1404                        "Extra \\or",
1405                        "I'm ignoring this; it doesn't match any \\if."
1406                    );
1407                }
1408            } else if (cur_chr == fi_code) {
1409                tex_aux_pop_condition_stack();
1410            }
1411        }
1412    }
1413  COMMON_ENDING:
1414    if (cur_chr == fi_code) {
1415        tex_aux_pop_condition_stack();
1416    } else {
1417        /*tex Wait for |\fi|. */
1418//lmt_condition_state.if_step = code;
1419        lmt_condition_state.if_limit = fi_code;
1420    }
1421}
1422
1423/*tex
1424    Terminate the current conditional and skip to |\fi| The processing of conditionals is complete
1425    except for the following code, which is actually part of |expand|. It comes into play when
1426    |\or|, |\else|, or |\fi| is scanned.
1427*/
1428
1429void tex_conditional_fi_or_else(void)
1430{
1431    int tracing_ifs = tracing_ifs_par > 0;
1432    if (tracing_ifs && tracing_commands_par <= 1) {
1433        tex_show_cmd_chr(if_test_cmd, cur_chr);
1434    }
1435    if (cur_chr == or_else_code || cur_chr == or_unless_code) {
1436        tex_get_next_non_spacer();
1437    } else if (cur_chr > lmt_condition_state.if_limit) {
1438        if (lmt_condition_state.if_limit == if_code) {
1439            /*tex The condition is not yet evaluated. */
1440            tex_insert_relax_and_cur_cs();
1441        } else {
1442            tex_handle_error(normal_error_type,
1443                "Extra %C",
1444                if_test_cmd, cur_chr,
1445                "I'm ignoring this; it doesn't match any \\if."
1446            );
1447        }
1448        /*tex We don't pop the stack! */
1449        return;
1450    }
1451    /*tex Skip to |\fi|. */
1452    while (! (cur_cmd == if_test_cmd && cur_chr == fi_code)) {
1453        tex_aux_pass_text();
1454        if (tracing_ifs) {
1455            tex_show_cmd_chr(cur_cmd, cur_chr);
1456        }
1457    }
1458    tex_aux_pop_condition_stack();
1459}
1460
1461/*tex
1462
1463    Negate a boolean conditional and |goto reswitch|. The result of a boolean condition is reversed
1464    when the conditional is preceded by |\unless|. We silently ignore |\unless| for those tests that
1465    act like an |\ifcase|. In \ETEX\ there was an error message.
1466
1467*/
1468
1469void tex_conditional_unless(void)
1470{
1471    tex_get_token();
1472    if (cur_cmd == if_test_cmd) {
1473        if (tracing_commands_par > 1) {
1474            tex_show_cmd_chr(cur_cmd, cur_chr);
1475        }
1476        if (cur_chr != if_condition_code) {;
1477            tex_conditional_if(cur_chr, 1);
1478        }
1479    } else {
1480        tex_handle_error(back_error_type,
1481            "You can't use '\\unless' before '%C'",
1482            cur_cmd, cur_chr,
1483            "Continue, and I'll forget that it ever happened."
1484        );
1485    }
1486}
1487
1488void tex_show_ifs(void)
1489{
1490    if (lmt_condition_state.cond_ptr) {
1491        /*tex First we determine the |\if ... \fi| nesting. */
1492        int n = 0;
1493        {
1494            /*tex We start at the tail of a token list to show. */
1495            halfword p = lmt_condition_state.cond_ptr;
1496            do {
1497                ++n;
1498                p = node_next(p);
1499            } while (p);
1500        }
1501        /*tex Now reporting can start. */
1502        {
1503            halfword cond_ptr = lmt_condition_state.cond_ptr;
1504            int cur_if = lmt_condition_state.cur_if;
1505            int cur_unless = lmt_condition_state.cur_unless;
1506            int if_step = lmt_condition_state.if_step;
1507            int if_unless = lmt_condition_state.if_unless;
1508            int if_line = lmt_condition_state.if_line;
1509            int if_limit = lmt_condition_state.if_limit;
1510            do {
1511                if (cur_unless) {
1512                    if (if_line) {
1513                        tex_print_format("[conditional: level %i, current %C %C, limit %C, %sstep %C, line %i]",
1514                            n,
1515                            expand_after_cmd, expand_unless_code,
1516                            if_test_cmd, cur_if,
1517                            if_test_cmd, if_limit,
1518                            if_unless ? "unless " : "",
1519                            if_test_cmd, if_step,
1520                            if_line
1521                       );
1522                    } else {
1523                        tex_print_format("[conditional: level %i, current %C %C, limit %C, %sstep %C]",
1524                            n,
1525                            expand_after_cmd, expand_unless_code,
1526                            if_test_cmd, cur_if,
1527                            if_test_cmd, if_limit,
1528                            if_unless ? "unless " : "",
1529                            if_test_cmd, if_step
1530                        );
1531                    }
1532                } else {
1533                    if (if_line) {
1534                        tex_print_format("[conditional: level %i, current %C, limit %C, %sstep %C, line %i]",
1535                            n,
1536                            if_test_cmd, cur_if,
1537                            if_test_cmd, if_limit,
1538                            if_unless ? "unless " : "",
1539                            if_test_cmd, if_step,
1540                            if_line
1541                        );
1542                    } else {
1543                        tex_print_format("[conditional: level %i, current %C, limit %C, %sstep %C]",
1544                            n,
1545                            if_test_cmd, cur_if,
1546                            if_test_cmd, if_limit,
1547                            if_unless ? "unless " : "",
1548                            if_test_cmd, if_step
1549                        );
1550                    }
1551                }
1552                --n;
1553                cur_if = if_limit_subtype(cond_ptr);
1554                cur_unless = if_limit_unless(cond_ptr);;
1555                if_step = if_limit_step(cond_ptr);;
1556                if_unless = if_limit_stepunless(cond_ptr);;
1557                if_line = if_limit_line(cond_ptr);;
1558                if_limit = if_limit_type(cond_ptr);;
1559                cond_ptr = node_next(cond_ptr);
1560                if (cond_ptr) {
1561                    tex_print_levels();
1562                }
1563            } while (cond_ptr);
1564        }
1565    } else {
1566        tex_print_str("[conditional: none active]");
1567    }
1568}
1569
1570/*tex 
1571
1572    There is no gain over |\expandafter| because backing up is what takes time. We just keep this 
1573    as reference.
1574*/ 
1575
1576/*
1577void tex_conditional_after_fi(void)
1578{
1579    halfword t = tex_get_token();
1580    int tracing_ifs = tracing_ifs_par > 0;
1581    int tracing_commands = tracing_commands_par > 0;
1582    while (1) {
1583        tex_aux_pass_text_x(tracing_ifs, tracing_commands);
1584        if (cur_chr == fi_code) {
1585            tex_aux_pop_condition_stack();
1586            break;
1587        } else {
1588            // some error
1589        }
1590    }
1591    tex_back_input(t);
1592}
1593*/