/* See license.txt in the root of this project. */ # include "luametatex.h" /*tex In \LUAMETATEX\ The condition code has been upgraded. Bits and pieces have been optimized and on top of the extra checks in \LUATEX|\ we have a few more here. In order to get nicer looking nested conditions |\orelse| has been introduced. Some conditionals are not really needed but they give less noise when tracing macros. It's also possible to let \LUA\ code behave like a test. */ /*tex We consider now the way \TEX\ handles various kinds of |\if| commands. Conditions can be inside conditions, and this nesting has a stack that is independent of the |save_stack|. Four global variables represent the top of the condition stack: |cond_ptr| points to pushed-down entries, if any; |if_limit| specifies the largest code of a |fi_or_else| command that is syntactically legal; |cur_if| is the name of the current type of conditional; and |if_line| is the line number at which it began. If no conditions are currently in progress, the condition stack has the special state |cond_ptr = null|, |if_limit = normal|, |cur_if = 0|, |if_line = 0|. Otherwise |cond_ptr| points to a two-word node; the |type|, |subtype|, and |link| fields of the first word contain |if_limit|, |cur_if|, and |cond_ptr| at the next level, and the second word contains the corresponding |if_line|. In |cond_ptr| we keep track of the top of the condition stack while |if_limit| holds the upper bound on |fi_or_else| codes. The type of conditional being worked on is stored in cur_if and |if_line| keeps track of the line where that conditional began. When we skip conditional text, |skip_line| keeps track of the line number where skipping began, for use in error messages. All these variables are collected in: */ condition_state_info lmt_condition_state = { .cond_ptr = null, .cur_if = 0, .cur_unless = 0, .if_step = 0, .if_unless = 0, .if_limit = 0, .if_line = 0, .skip_line = 0, .chk_num = 0, .chk_dim = 0, .if_nesting = 0, .padding = 0, }; /*tex Here is a procedure that ignores text until coming to an |\or|, |\else|, or |\fi| at level zero of |\if| \unknown |\fi| nesting. After it has acted, |cur_chr| will indicate the token that was found, but |cur_tok| will not be set (because this makes the procedure run faster). With |l| we keep track of the level of |\if|\unknown|\fi| nesting and |scanner_status| let us return to the entry status. The |pass_text| function only returns when we have a |fi_or_else|. */ static void tex_aux_pass_text(void) { int level = 0; int status = lmt_input_state.scanner_status; lmt_input_state.scanner_status = scanner_is_skipping; lmt_condition_state.skip_line = lmt_input_state.input_line; while (1) { tex_get_next(); if (cur_cmd == if_test_cmd) { switch (cur_chr) { case fi_code: if (level == 0) { lmt_input_state.scanner_status = status; return; } else { --level; break; } case else_code: case or_code: if (level == 0) { lmt_input_state.scanner_status = status; return; } else { break; } case or_else_code: case or_unless_code: tex_get_next_non_spacer(); /*tex So we skip the token after |\orelse| or |\orunless| without testing it! */ break; default: ++level; break; } } } } /*tex We return when we have a |fi_or_else| or when we have a valid |or_else| followed by an |if_test_cmd|. */ static int tex_aux_pass_text_x(int tracing_ifs, int tracing_commands) { int level = 0; int status = lmt_input_state.scanner_status; lmt_input_state.scanner_status = scanner_is_skipping; lmt_condition_state.skip_line = lmt_input_state.input_line; while (1) { tex_get_next(); if (cur_cmd == if_test_cmd) { switch (cur_chr) { case fi_code: if (level == 0) { lmt_input_state.scanner_status = status; return 0; } else { --level; break; } case else_code: case or_code: if (level == 0) { lmt_input_state.scanner_status = status; return 0; } else { break; } case or_else_code: case or_unless_code: if (level == 0) { int unless = cur_chr == or_unless_code; if (tracing_commands > 1) { tex_begin_diagnostic(); tex_print_str(unless ? "{orunless}" : "{orelse}"); tex_end_diagnostic(); } else if (tracing_ifs) { tex_show_cmd_chr(cur_cmd, cur_chr); } tex_get_next_non_spacer(); if (lmt_condition_state.if_limit == if_code) { if (cur_cmd == if_test_cmd && cur_chr >= first_real_if_test_code) { goto OKAY; } tex_handle_error( normal_error_type, unless ? "No condition after \\orunless" : "No condition after \\orelse", "I'd expected a proper if test command." ); OKAY: lmt_input_state.scanner_status = status; return unless; } } else { --level; } break; default: ++level; break; } } } } /*tex When we begin to process a new |\if|, we set |if_limit = if_code|; then, if |\or| or |\else| or |\fi| occurs before the current |\if| condition has been evaluated, |\relax| will be inserted. For example, a sequence of commands like |\ifvoid 1 \else ... \fi| would otherwise require something after the |1|. When a conditional ends that was apparently started in a different input file, the |if_warning| procedure is invoked in order to update the |if_stack|. If moreover |\tracingnesting| is positive we want to give a warning message (with the same complications as above). */ static void tex_aux_if_warning(void) { /*tex Do we need a warning? */ int warning = 0; int index = lmt_input_state.in_stack_data.ptr; lmt_input_state.base_ptr = lmt_input_state.input_stack_data.ptr; /*tex Store current state. */ lmt_input_state.input_stack[lmt_input_state.base_ptr] = lmt_input_state.cur_input; while (lmt_input_state.in_stack[index].if_ptr == lmt_condition_state.cond_ptr) { /*tex Set variable |w| to. */ if (tracing_nesting_par > 0) { 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)) { --lmt_input_state.base_ptr; } if (lmt_input_state.input_stack[lmt_input_state.base_ptr].name > 17) { warning = 1; } } lmt_input_state.in_stack[index].if_ptr = node_next(lmt_condition_state.cond_ptr); --index; } if (warning) { tex_begin_diagnostic(); 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); tex_end_diagnostic(); if (tracing_nesting_par > 1) { tex_show_context(); } if (lmt_error_state.history == spotless) { lmt_error_state.history = warning_issued; } } } /*tex We can consider a dedicated condition stack so that we can copy faster. Or we can just emulate an if node in |lmt_condition_state|. */ static void tex_aux_push_condition_stack(int code, int unless) { halfword p = tex_get_node(if_node_size); node_type(p) = if_node; node_subtype(p) = 0; node_next(p) = lmt_condition_state.cond_ptr; if_limit_type(p) = (quarterword) lmt_condition_state.if_limit; if_limit_subtype(p) = (quarterword) lmt_condition_state.cur_if; if_limit_step(p) = (singleword) lmt_condition_state.cur_unless; if_limit_unless(p) = (singleword) lmt_condition_state.if_unless; if_limit_stepunless(p) = (singleword) lmt_condition_state.if_unless; if_limit_line(p) = lmt_condition_state.if_line; lmt_condition_state.cond_ptr = p; lmt_condition_state.cur_if = cur_chr; lmt_condition_state.cur_unless = unless; lmt_condition_state.if_step = code; lmt_condition_state.if_limit = if_code; lmt_condition_state.if_line = lmt_input_state.input_line; ++lmt_condition_state.if_nesting; } static void tex_aux_pop_condition_stack(void) { halfword p; if (lmt_input_state.in_stack[lmt_input_state.in_stack_data.ptr].if_ptr == lmt_condition_state.cond_ptr) { /*tex Conditionals are possibly not properly nested with files. This test can become an option. */ tex_aux_if_warning(); } p = lmt_condition_state.cond_ptr; --lmt_condition_state.if_nesting; lmt_condition_state.if_line = if_limit_line(p); lmt_condition_state.cur_if = if_limit_subtype(p); lmt_condition_state.cur_unless = if_limit_unless(p); lmt_condition_state.if_step = if_limit_step(p); lmt_condition_state.if_unless = if_limit_stepunless(p); lmt_condition_state.if_limit = if_limit_type(p); lmt_condition_state.cond_ptr = node_next(p); tex_free_node(p, if_node_size); } /*tex Here's a procedure that changes the |if_limit| code corresponding to a given value of |cond_ptr|. */ inline static void tex_aux_change_if_limit(int l, halfword p) { if (p == lmt_condition_state.cond_ptr) { lmt_condition_state.if_limit = l; } else { halfword q = lmt_condition_state.cond_ptr; while (q) { if (node_next(q) == p) { if_limit_type(q) = (quarterword) l; return; } else { q = node_next(q); } } tex_confusion("if"); } } /*tex The conditional|\ifcsname| is equivalent to |\expandafter| |\expandafter| |\ifdefined| |\csname|, except that no new control sequence will be entered into the hash table (once all tokens preceding the mandatory |\endcsname| have been expanded). Because we have \UTF 8, we find plenty of small helpers that are used in conversion. A csname resolve can itself have nested csname resolving. We keep track of the nesting level and also remember the last match. */ /* moved to texexpand */ /*tex An active character will be treated as category 13 following |\if \noexpand| or following |\ifcat \noexpand|. */ static void tex_aux_get_x_token_or_active_char(void) { tex_get_x_token(); // if (cur_cmd == relax_cmd && cur_chr == no_expand_flag && tex_is_active_cs(cs_text(cur_cs))) { if (cur_cmd == relax_cmd && cur_chr == no_expand_relax_code && tex_is_active_cs(cs_text(cur_cs))) { cur_cmd = active_char_cmd; cur_chr = active_cs_value(cs_text(cur_tok - cs_token_flag)); } } /*tex A condition is started when the |expand| procedure encounters an |if_test| command; in that case |expand| reduces to |conditional|, which is a recursive procedure. */ static void tex_aux_missing_equal_error(int code) { tex_handle_error(back_error_type, "Missing = inserted for %C", if_test_cmd, code, "I was expecting to see '<', '=', or '>'. Didn't." ); } /*tex This is an important function because a bit larger macro package does lots of testing. Compared to regular \TEX\ there is of course the penalty of larger data structures but there's not much we can do about that. Then there are more variants, which in turn can lead to a performance hit as there is more to test and more code involved, which might influence cache hits and such. However, I already optimized the \LUATEX\ code a bit and here there are some more tiny potential speedups. But \unknown\ they are hard to measure and especially their impact on a normal run: \TEX\ is already pretty fast and often these tests themselves are not biggest bottleneck, at least not in \CONTEXT. My guess is that the speedups compensate the extra if tests so in the end we're still okay. Expansion, pushing back tokens, accessing memory all over the place, excessive use of \LUA\ \unknown\ all that has probably way more impact on a run. But I keep an eye on the next one anyway. */ static void tex_aux_show_if_state(halfword code, halfword case_value) { tex_begin_diagnostic(); switch (code) { case if_chk_int_code : tex_print_format("{chknum %i}", case_value); break; case if_val_int_code : tex_print_format("{numval %i}", case_value); break; case if_cmp_int_code : tex_print_format("{cmpnum %i}", case_value); break; case if_chk_dim_code : tex_print_format("{chkdim %i}", case_value); break; case if_val_dim_code : tex_print_format("{dimval %i}", case_value); break; case if_cmp_dim_code : tex_print_format("{cmpdim %i}", case_value); break; case if_case_code : tex_print_format("{case %i}", case_value); break; case if_math_parameter_code: tex_print_format("{mathparameter %i}", case_value); break; case if_math_style_code : tex_print_format("{mathstyle %i}", case_value); break; case if_arguments_code : tex_print_format("{arguments %i}", case_value); break; case if_parameters_code : tex_print_format("{parameter %i}", case_value); break; case if_parameter_code : tex_print_format("{parameters %i}", case_value); break; default : tex_print_format("{todo %i}", case_value); break; } tex_end_diagnostic(); } /*tex Why do we skip over relax? */ inline static halfword tex_aux_grab_toks(int expand, int expandlist, int *head) { halfword p = null; if (expand) { do { tex_get_x_token(); } while (cur_cmd == spacer_cmd || cur_cmd == relax_cmd); } else { do { tex_get_token(); } while (cur_cmd == spacer_cmd || cur_cmd == relax_cmd); } switch (cur_cmd) { case left_brace_cmd: p = expandlist ? tex_scan_toks_expand(1, NULL, 0) : tex_scan_toks_normal(1, NULL); *head = p; break; case register_cmd: /* is this okay? probably not as cur_val can be way to large */ if (cur_chr == tok_val_level) { halfword n = tex_scan_toks_register_number(); p = eq_value(register_toks_location(n)); break; } else { goto DEFAULT; } case internal_toks_cmd: case register_toks_cmd: p = eq_value(cur_chr); break; case call_cmd: case protected_call_cmd: case semi_protected_call_cmd: case tolerant_call_cmd: case tolerant_protected_call_cmd: case tolerant_semi_protected_call_cmd: p = eq_value(cur_cs); break; default: DEFAULT: { halfword n; tex_back_input(cur_tok); n = tex_scan_toks_register_number(); p = eq_value(register_toks_location(n)); break; } } /* skip over the ref count */ return p ? token_link(p) : null; } // inline static halfword tex_aux_scan_comparison(int code) // { // halfword r; // do { // tex_get_x_token(); // } while (cur_cmd == spacer_cmd); // r = cur_tok - other_token; // if ((r < '<') || (r > '>')) { // tex_aux_missing_equal_error(code); // return '='; // } else { // return r; // } // } //inline static halfword tex_aux_scan_comparison(int code) //{ // do { // tex_get_x_token(); // } while (cur_cmd == spacer_cmd); // if (cur_cmd != other_char_cmd || (cur_chr < '<') || (cur_chr > '>')) { // tex_aux_missing_equal_error(code); // return '='; // } else { // return cur_chr; // } //} inline static halfword tex_aux_scan_comparison(int code) { int negate = 0; while (1) { tex_get_x_token(); switch (cur_cmd) { case letter_cmd: case other_char_cmd: switch (cur_chr) { /* traditional */ case '=' : return negate ? 3 : 0; case '<' : return negate ? 4 : 1; case '>' : return negate ? 5 : 2; /* bonus */ case '!' : negate = ! negate ; continue; /* neat */ case 0x2208: return negate ? 7 : 6; /* element of */ case 0x2209: return negate ? 6 : 7; /* not element of */ case 0x2260: return negate ? 0 : 3; /* not equal */ case 0x2264: return negate ? 2 : 5; /* less equal */ case 0x2265: return negate ? 1 : 4; /* greater equal */ case 0x2270: return negate ? 2 : 5; /* not less equal */ case 0x2271: return negate ? 1 : 4; /* not greater equal */ } case spacer_cmd: continue; default: tex_aux_missing_equal_error(code); return 0; } } } void tex_conditional_if(halfword code, int unless) { /*tex The result or case value. */ int result = 0; /*tex The |cond_ptr| corresponding to this conditional: */ halfword save_cond_ptr; /*tex Tracing options */ int tracing_ifs = tracing_ifs_par > 0; int tracing_commands = tracing_commands_par; int tracing_both = tracing_ifs && (tracing_commands <= 1); if (tracing_both) { tex_show_cmd_chr(cur_cmd, cur_chr); } tex_aux_push_condition_stack(code, unless); save_cond_ptr = lmt_condition_state.cond_ptr; /*tex Either process |\ifcase| or set |b| to the value of a boolean condition. */ HERE: /*tex We can get back here so we need to make sure result is always set! */ lmt_condition_state.if_step = code; lmt_condition_state.if_unless = unless; switch (code) { case if_char_code: case if_cat_code: /*tex Test if two characters match. Seldom used, this one. */ { halfword n, m; tex_aux_get_x_token_or_active_char(); if ((cur_cmd > active_char_cmd) || (cur_chr > max_character_code)) { /*tex It's not a character. */ m = relax_cmd; n = relax_code; } else { m = cur_cmd; n = cur_chr; } tex_aux_get_x_token_or_active_char(); if ((cur_cmd > active_char_cmd) || (cur_chr > max_character_code)) { cur_cmd = relax_cmd; cur_chr = relax_code; } if (code == if_char_code) { result = (n == cur_chr); } else { result = (m == cur_cmd); } } goto RESULT; case if_abs_int_code: case if_int_code: { halfword n1 = tex_scan_int(0, NULL); halfword cp = tex_aux_scan_comparison(code); halfword n2 = tex_scan_int(0, NULL); if (code == if_abs_int_code) { if (n1 < 0) { n1 = -n1; } if (n2 < 0) { n2 = -n2; } } switch (cp) { case 0: result = (n1 == n2); break; case 1: result = (n1 < n2); break; case 2: result = (n1 > n2); break; case 3: result = (n1 != n2); break; case 4: result = (n1 >= n2); break; case 5: result = (n1 <= n2); break; case 6: result = (n1 & n2) == n1; break; case 7: result = (n1 & n2) != n1; break; } } goto RESULT; case if_zero_int_code: result = tex_scan_int(0, NULL) == 0; goto RESULT; case if_abs_dim_code: case if_dim_code: { scaled n1 = tex_scan_dimen(0, 0, 0, 0, NULL); halfword cp = tex_aux_scan_comparison(code); scaled n2 = tex_scan_dimen(0, 0, 0, 0, NULL); if (code == if_abs_dim_code) { if (n1 < 0) { n1 = -n1; } if (n2 < 0) { n2 = -n2; } } switch (cp) { case 0: result = (n1 == n2); break; case 1: result = (n1 < n2); break; case 2: result = (n1 > n2); break; case 3: result = (n1 != n2); break; case 4: result = (n1 >= n2); break; case 5: result = (n1 <= n2); break; /* maybe we should round */ case 6: result = (n1/65536 & n2/65536) == n1/65536; break; case 7: result = (n1/65536 & n2/65536) != n1/65536; break; } } goto RESULT; case if_zero_dim_code: result = tex_scan_dimen(0, 0, 0, 0, NULL) == 0; goto RESULT; case if_odd_code: result = odd(tex_scan_int(0, NULL)); goto RESULT; case if_vmode_code: result = is_v_mode(cur_list.mode); goto RESULT; case if_hmode_code: result = is_h_mode(cur_list.mode); goto RESULT; case if_mmode_code: result = is_m_mode(cur_list.mode); goto RESULT; case if_inner_code: result = cur_list.mode < nomode; goto RESULT; case if_void_code: { halfword n = tex_scan_box_register_number(); result = box_register(n) == null; } goto RESULT; case if_hbox_code: { halfword n = tex_scan_box_register_number(); halfword p = box_register(n); result = p && (node_type(p) == hlist_node); } goto RESULT; case if_vbox_code: { halfword n = tex_scan_box_register_number(); halfword p = box_register(n); result = p && (node_type(p) == vlist_node); } goto RESULT; case if_tok_code: case if_cstok_code: { halfword pp = null; halfword qq = null; halfword p, q; int expand = code == if_tok_code; int save_scanner_status = lmt_input_state.scanner_status; lmt_input_state.scanner_status = scanner_is_normal; p = tex_aux_grab_toks(expand, 1, &pp); q = tex_aux_grab_toks(expand, 1, &qq); if (p == q) { /* this is sneaky, a list always is different */ result = 1; } else { while (p && q) { if (token_info(p) != token_info(q)) { p = null; break; } else { p = token_link(p); q = token_link(q); } } result = (! p) && (! q); } if (pp) { tex_flush_token_list(pp); } if (qq) { tex_flush_token_list(qq); } lmt_input_state.scanner_status = save_scanner_status; } goto RESULT; case if_x_code: { /*tex Test if two tokens match. Note that |\ifx| will declare two macros different if one is |\long| or |\outer| and the other isn't, even though the texts of the macros are the same. We need to reset |scanner_status|, since |\outer| control sequences are allowed, but we might be scanning a macro definition or preamble. This is no longer true as we dropped these properties but it does apply to protected macros and such. */ halfword p, q, n; int save_scanner_status = lmt_input_state.scanner_status; lmt_input_state.scanner_status = scanner_is_normal; tex_get_next(); n = cur_cs; p = cur_cmd; q = cur_chr; tex_get_next(); if (cur_cmd != p) { result = 0; } else if (cur_cmd < call_cmd) { result = cur_chr == q; } else { /*tex Test if two macro texts match. Note also that |\ifx| decides that macros |\a| and |\b| are different in examples like this: \starttyping \def\a{\c} \def\c{} \def\b{\d} \def\d{} \stoptyping */ p = token_link(cur_chr); /*tex Omit reference counts. */ q = token_link(eq_value(n)); // is: q = token_link(q); if (p == q) { result = 1; /* } else if (! q) { result = 0; */ } else { while (p && q) { if (token_info(p) != token_info(q)) { p = null; break; } else { p = token_link(p); q = token_link(q); } } result = (! p) && (! q); } } lmt_input_state.scanner_status = save_scanner_status; } goto RESULT; case if_true_code: result = 1; goto RESULT; case if_false_code: result = 0; goto RESULT; case if_chk_int_code: { lmt_error_state.intercept = 1; /* maybe ++ and -- so that we can nest */ lmt_error_state.last_intercept = 0; lmt_condition_state.chk_num = tex_scan_int(0, NULL); /* value is ignored */ result = lmt_error_state.last_intercept ? 2 : 1; lmt_error_state.intercept = 0; lmt_error_state.last_intercept = 0; goto CASE; } case if_val_int_code: { lmt_error_state.intercept = 1; lmt_error_state.last_intercept = 0; lmt_condition_state.chk_num = tex_scan_int(0, NULL); result = lmt_error_state.last_intercept ? 4 : (lmt_condition_state.chk_num < 0) ? 1 : (lmt_condition_state.chk_num > 0) ? 3 : 2; lmt_error_state.intercept = 0; lmt_error_state.last_intercept = 0; goto CASE; } case if_cmp_int_code: { halfword n1 = tex_scan_int(0, NULL); halfword n2 = tex_scan_int(0, NULL); result = (n1 < n2) ? 0 : (n1 > n2) ? 2 : 1; goto CASE; } case if_chk_dim_code: { lmt_error_state.intercept = 1; lmt_error_state.last_intercept = 0; lmt_condition_state.chk_dim = tex_scan_dimen(0, 0, 0, 0, NULL); /* value is ignored */ result = lmt_error_state.last_intercept ? 2 : 1; lmt_error_state.intercept = 0; lmt_error_state.last_intercept = 0; goto CASE; } case if_val_dim_code: { lmt_error_state.intercept = 1; lmt_error_state.last_intercept = 0; lmt_condition_state.chk_dim = tex_scan_dimen(0, 0, 0, 0, NULL); result = lmt_error_state.last_intercept ? 4 : (lmt_condition_state.chk_dim < 0) ? 1 : (lmt_condition_state.chk_dim > 0) ? 3 : 2; lmt_error_state.intercept = 0; lmt_error_state.last_intercept = 0; goto CASE; } case if_cmp_dim_code: { scaled n1 = tex_scan_dimen(0, 0, 0, 0, NULL); scaled n2 = tex_scan_dimen(0, 0, 0, 0, NULL); result = (n1 < n2) ? 0 : (n1 > n2) ? 2 : 1; goto CASE; } case if_case_code: /*tex Select the appropriate case and |return| or |goto common_ending|. */ result = tex_scan_int(0, NULL); goto CASE; case if_def_code: /*tex The conditional |\ifdefined| tests if a control sequence is defined. We need to reset |scanner_status|, since |\outer| control sequences are allowed, but we might be scanning a macro definition or preamble. */ { int save_scanner_status = lmt_input_state.scanner_status; lmt_input_state.scanner_status = scanner_is_normal; tex_get_next(); result = cur_cmd != undefined_cs_cmd; lmt_input_state.scanner_status = save_scanner_status; goto RESULT; } case if_cs_code: result = tex_is_valid_csname(); goto RESULT; case if_in_csname_code: /*tex This one will go away. */ result = lmt_expand_state.cs_name_level; goto RESULT; case if_font_char_code: /*tex The conditional |\iffontchar| tests the existence of a character in a font. */ { halfword fnt = tex_scan_font_identifier(NULL); halfword chr = tex_scan_char_number(0); result = tex_char_exists(fnt, chr); } goto RESULT; case if_condition_code: /*tex This can't happen! */ goto RESULT; case if_flags_code: { halfword cs; singleword flag; tex_get_r_token(); cs = cur_cs; flag = eq_flag(cs); /* todo: each prefix */ tex_get_token(); if (cur_cmd == prefix_cmd) { switch (cur_chr) { /*tex We check flags: */ case frozen_code : result = is_frozen (flag); break; case permanent_code : result = is_permanent(flag); break; case immutable_code : result = is_immutable(flag); break; /* case primitive_code : result = is_primitive(flag); break; */ case mutable_code : result = is_mutable (flag); break; case noaligned_code : result = is_noaligned(flag); break; case instance_code : result = is_instance (flag); break; case untraced_code : result = is_untraced (flag); break; /*tex We check cmd: */ case global_code : result = eq_level(cs) == level_one;; break; case tolerant_code : result = is_tolerant_cmd(eq_type(cs)); break; case protected_code : result = is_protected_cmd(eq_type(cs)); break; case semiprotected_code : result = is_semi_protected_cmd(eq_type(cs)); break; } } else { int fl; tex_back_input(cur_tok); fl = tex_scan_int(1, NULL); result = (flag & fl) == fl; if (! result) { if (is_protected(fl)) { result = is_protected_cmd(eq_type(cs)); } else if (is_semiprotected(fl)) { result = is_semi_protected_cmd(eq_type(cs)); } else if (is_tolerant(fl)) { result = is_tolerant_cmd(eq_type(cs)); } else if (is_global(fl)) { result = eq_level(cs) == level_one; } } } goto RESULT; } case if_empty_cmd_code: { tex_get_token(); EMPTY_CHECK_AGAIN: switch (cur_cmd) { case call_cmd: result = ! token_link(cur_chr); break; case internal_toks_reference_cmd: case register_toks_reference_cmd: result = ! token_link(cur_chr); break; case register_cmd: /*tex See |tex_aux_grab_toks|. */ if (cur_chr == tok_val_level) { halfword n = tex_scan_toks_register_number(); halfword p = eq_value(register_toks_location(n)); result = ! p || ! token_link(p); } else { result = 0; } break; case internal_toks_cmd: case register_toks_cmd: { halfword p = eq_value(cur_chr); result = ! p || ! token_link(p); } break; case cs_name_cmd: if (cur_chr == last_named_cs_code && lmt_scanner_state.last_cs_name != null_cs) { cur_cmd = eq_type(lmt_scanner_state.last_cs_name); cur_chr = eq_value(lmt_scanner_state.last_cs_name); goto EMPTY_CHECK_AGAIN; } /* fall through */ default: result = 0; } goto RESULT; } case if_relax_cmd_code: { tex_get_token(); result = cur_cmd == relax_cmd; goto RESULT; } case if_boolean_code: result = tex_scan_int(0, NULL) ? 1 : 0; goto RESULT; case if_numexpression_code: result = tex_scanned_expression(int_val_level) ? 1 : 0; goto RESULT; case if_dimexpression_code: result = tex_scanned_expression(dimen_val_level) ? 1 : 0; goto RESULT; case if_math_parameter_code: /*tex A value of |1| means that the parameter is set to a non-zero value, while |2| means that it is unset. */ { // result = 0; do { tex_get_x_token(); } while (cur_cmd == spacer_cmd); if (cur_cmd == set_math_parameter_cmd) { int code = cur_chr; int style = tex_scan_math_style_identifier(0, 0); if (tex_get_math_parameter(style, code, NULL) == max_dimen) { result = 2; } else if (result) { result = 1; } } else { tex_normal_error("mathparameter", "a valid parameter expected"); result = 0; } goto CASE; } case if_math_style_code: result = tex_current_math_style(); goto CASE; case if_arguments_code: result = lmt_expand_state.arguments; goto CASE; case if_parameters_code: /*tex The result has the last non-null count. We could have the test in the for but let's keep it readable. */ result = tex_get_parameter_count(); goto CASE; case if_parameter_code: { /*tex We need to pick up the next token but avoid replacement by the parameter which happens in the getters: 0 = no parameter, 1 = okay, 2 = empty. This permits usage like |\ifparameter#2\or yes\else no\fi| as with the other checkers. */ if (lmt_input_state.cur_input.loc) { halfword t = token_info(lmt_input_state.cur_input.loc); lmt_input_state.cur_input.loc = token_link(lmt_input_state.cur_input.loc); if (t < cs_token_flag && token_cmd(t) == parameter_reference_cmd) { result = lmt_input_state.parameter_stack[lmt_input_state.cur_input.parameter_start + token_chr(t) - 1] != null ? 1 : 2; } } goto CASE; } case if_has_tok_code: { halfword qq = null; halfword p, q; int save_scanner_status = lmt_input_state.scanner_status; lmt_input_state.scanner_status = scanner_is_normal; p = tex_get_token(); q = tex_aux_grab_toks(0, 0, &qq); if (p == q) { result = 1; } else { result = 0; while (q) { if (p == token_info(q)) { result = 1; break; } else { q = token_link(q); } } } if (qq) { tex_flush_token_list(qq); } lmt_input_state.scanner_status = save_scanner_status; goto RESULT; } case if_has_toks_code: case if_has_xtoks_code: { halfword pp = null; halfword p; int expand = code == if_has_xtoks_code; int save_scanner_status = lmt_input_state.scanner_status; lmt_input_state.scanner_status = scanner_is_normal; p = tex_aux_grab_toks(expand, expand, &pp); if (p) { halfword qq = null; halfword q = tex_aux_grab_toks(expand, expand, &qq); if (p == q) { result = 1; } else { int qh = q; int ph = p; result = 0; while (p && q) { halfword pt = token_info(p); halfword qt = token_info(q); AGAIN: if (pt == qt) { p = token_link(p); q = token_link(q); } else if (token_cmd(pt) == ignore_cmd && token_cmd(qt) >= ignore_cmd && token_cmd(qt) <= other_char_cmd) { p = token_link(p); if (token_chr(pt) == token_chr(qt)) { q = token_link(q); } else { pt = token_info(p); goto AGAIN; } } else { p = ph; q = token_link(qh); qh = q; } if (! p) { result = 1; break; } } } if (qq) { tex_flush_token_list(qq); } } if (pp) { tex_flush_token_list(pp); } lmt_input_state.scanner_status = save_scanner_status; goto RESULT; } case if_has_char_code: { halfword tok; halfword qq = null; halfword q; int save_scanner_status = lmt_input_state.scanner_status; lmt_input_state.scanner_status = scanner_is_normal; tok = tex_get_token(); q = tex_aux_grab_toks(0, 0, &qq); if (q) { int nesting = 0; result = 0; while (q) { if (! nesting && token_info(q) == tok) { result = 1; break; } else if (token_cmd(token_info(q)) == left_brace_cmd) { nesting += 1; } else if (token_cmd(token_info(q)) == right_brace_cmd) { nesting -= 1; } q = token_link(q); } } if (qq) { tex_flush_token_list(qq); } lmt_input_state.scanner_status = save_scanner_status; goto RESULT; } case if_insert_code: { /* beware: it tests */ result = ! tex_insert_is_void(tex_scan_int(0, NULL)); goto RESULT; } // case if_bitwise_and_code: // { // halfword n1 = scan_int(0, NULL); // halfword n2 = scan_int(0, NULL); // result = n1 & n2 ? 1 : 0; // goto RESULT; // } default: { int category; strnumber u = tex_save_cur_string(); int save_scanner_status = lmt_input_state.scanner_status; lmt_input_state.scanner_status = scanner_is_normal; lmt_token_state.luacstrings = 0; category = lmt_function_call_by_category(code - last_if_test_code, 0, &result); tex_restore_cur_string(u); lmt_input_state.scanner_status = save_scanner_status; if (lmt_token_state.luacstrings > 0) { tex_lua_string_start(); /* bad */ } switch (category) { case lua_value_integer_code: case lua_value_cardinal_code: case lua_value_dimension_code: goto CASE; case lua_value_boolean_code: goto RESULT; default: result = 0; goto RESULT; } } } CASE: /*tex To be considered: |if (unless) { result = max_integer - result; }| so that we hit |\else| and can do |\unless \ifcase \zero... \else \fi|. */ if (tracing_commands > 1) { tex_aux_show_if_state(code, result); } while (result) { unless = tex_aux_pass_text_x(tracing_ifs, tracing_commands); if (tracing_both) { tex_show_cmd_chr(cur_cmd, cur_chr); } if (lmt_condition_state.cond_ptr == save_cond_ptr) { if (cur_chr >= first_real_if_test_code) { /*tex We have an |or_else_cmd| here, but keep in mind that |\expandafter \ifx| and |\unless \ifx| and |\ifcondition| don't work in such cases! We stay in this function call. */ if (cur_chr == if_condition_code) { // goto COMMON_ENDING; tex_aux_pop_condition_stack(); return; } else { code = cur_chr; goto HERE; } } else if (cur_chr == or_code) { --result; } else { goto COMMON_ENDING; } } else if (cur_chr == fi_code) { tex_aux_pop_condition_stack(); } } tex_aux_change_if_limit(or_code, save_cond_ptr); /*tex Wait for |\or|, |\else|, or |\fi|. */ return; RESULT: if (unless) { result = ! result; } if (tracing_commands > 1) { /*tex Display the value of |b|. */ tex_begin_diagnostic(); tex_print_str(result ? "{true}" : "{false}"); tex_end_diagnostic(); } if (result) { tex_aux_change_if_limit(else_code, save_cond_ptr); /*tex Wait for |\else| or |\fi|. */ return; } else { /*tex Skip to |\else| or |\fi|, then |goto common_ending|. In a construction like |\if \iftrue abc\else d\fi|, the first |\else| that we come to after learning that the |\if| is false is not the |\else| we're looking for. Hence the following curious logic is needed. */ while (1) { unless = tex_aux_pass_text_x(tracing_ifs, tracing_commands); if (tracing_both) { tex_show_cmd_chr(cur_cmd, cur_chr); } if (lmt_condition_state.cond_ptr == save_cond_ptr) { /* still fragile for |\unless| and |\expandafter| etc. */ if (cur_chr >= first_real_if_test_code) { if (cur_chr == if_condition_code) { // goto COMMON_ENDING; tex_aux_pop_condition_stack(); return; } else { code = cur_chr; goto HERE; } } else if (cur_chr != or_code) { goto COMMON_ENDING; } else { tex_handle_error( normal_error_type, "Extra \\or", "I'm ignoring this; it doesn't match any \\if." ); } } else if (cur_chr == fi_code) { tex_aux_pop_condition_stack(); } } } COMMON_ENDING: if (cur_chr == fi_code) { tex_aux_pop_condition_stack(); } else { /*tex Wait for |\fi|. */ //lmt_condition_state.if_step = code; lmt_condition_state.if_limit = fi_code; } } /*tex Terminate the current conditional and skip to |\fi| The processing of conditionals is complete except for the following code, which is actually part of |expand|. It comes into play when |\or|, |\else|, or |\fi| is scanned. */ void tex_conditional_fi_or_else(void) { int tracing_ifs = tracing_ifs_par > 0; if (tracing_ifs && tracing_commands_par <= 1) { tex_show_cmd_chr(if_test_cmd, cur_chr); } if (cur_chr == or_else_code || cur_chr == or_unless_code) { tex_get_next_non_spacer(); } else if (cur_chr > lmt_condition_state.if_limit) { if (lmt_condition_state.if_limit == if_code) { /*tex The condition is not yet evaluated. */ tex_insert_relax_and_cur_cs(); } else { tex_handle_error(normal_error_type, "Extra %C", if_test_cmd, cur_chr, "I'm ignoring this; it doesn't match any \\if." ); } /*tex We don't pop the stack! */ return; } /*tex Skip to |\fi|. */ while (! (cur_cmd == if_test_cmd && cur_chr == fi_code)) { tex_aux_pass_text(); if (tracing_ifs) { tex_show_cmd_chr(cur_cmd, cur_chr); } } /*tex Inline variant: */ /* if (! (cur_cmd == if_test_cmd && cur_chr == fi_code)) { int level = 0; int status = input_state.scanner_status; input_state.scanner_status = scanner_is_skipping; while (1) { RESTART: condition_state.skip_line = input_state.input_line; while (1) { get_next(); if (cur_cmd == if_test_cmd) { switch (cur_chr) { case fi_code: if (level == 0) { goto DONE; } else { --level; break; } case else_code: case or_code: if (level == 0) { if (tracing_ifs) { show_cmd_chr(cur_cmd, cur_chr); } goto RESTART; } else { break; } case or_else_code: do { get_next(); } while (cur_cmd == spacer_cmd); break; default: ++level; break; } } } } DONE: if (tracing_ifs) { show_cmd_chr(cur_cmd, cur_chr); } input_state.scanner_status = status; } */ tex_aux_pop_condition_stack(); } /*tex Negate a boolean conditional and |goto reswitch|. The result of a boolean condition is reversed when the conditional is preceded by |\unless|. We silently ignore |\unless| for those tests that act like an |\ifcase|. In \ETEX\ there was an error message. */ void tex_conditional_unless(void) { tex_get_token(); if (cur_cmd == if_test_cmd) { if (tracing_commands_par > 1) { tex_show_cmd_chr(cur_cmd, cur_chr); } if (cur_chr != if_condition_code) {; tex_conditional_if(cur_chr, 1); } } else { tex_handle_error(back_error_type, "You can't use '\\unless' before '%C'", cur_cmd, cur_chr, "Continue, and I'll forget that it ever happened." ); } } void tex_show_ifs(void) { if (lmt_condition_state.cond_ptr) { /*tex First we determine the of |\if ... \fi| nesting. */ int n = 0; { /*tex We start at the tail of a token list to show. */ halfword p = lmt_condition_state.cond_ptr; do { ++n; p = node_next(p); } while (p); } /*tex Now reporting can start. */ { halfword cond_ptr = lmt_condition_state.cond_ptr; int cur_if = lmt_condition_state.cur_if; int cur_unless = lmt_condition_state.cur_unless; int if_step = lmt_condition_state.if_step; int if_unless = lmt_condition_state.if_unless; int if_line = lmt_condition_state.if_line; int if_limit = lmt_condition_state.if_limit; do { if (cur_unless) { if (if_line) { tex_print_format("[conditional: level %i, current %C %C, limit %C, %sstep %C, line %i]", n, expand_after_cmd, expand_unless_code, if_test_cmd, cur_if, if_test_cmd, if_limit, if_unless ? "unless " : "", if_test_cmd, if_step, if_line ); } else { tex_print_format("[conditional: level %i, current %C %C, limit %C, %sstep %C]", n, expand_after_cmd, expand_unless_code, if_test_cmd, cur_if, if_test_cmd, if_limit, if_unless ? "unless " : "", if_test_cmd, if_step ); } } else { if (if_line) { tex_print_format("[conditional: level %i, current %C, limit %C, %sstep %C, line %i]", n, if_test_cmd, cur_if, if_test_cmd, if_limit, if_unless ? "unless " : "", if_test_cmd, if_step, if_line ); } else { tex_print_format("[conditional: level %i, current %C, limit %C, %sstep %C]", n, if_test_cmd, cur_if, if_test_cmd, if_limit, if_unless ? "unless " : "", if_test_cmd, if_step ); } } --n; cur_if = if_limit_subtype(cond_ptr); cur_unless = if_limit_unless(cond_ptr);; if_step = if_limit_step(cond_ptr);; if_unless = if_limit_stepunless(cond_ptr);; if_line = if_limit_line(cond_ptr);; if_limit = if_limit_type(cond_ptr);; cond_ptr = node_next(cond_ptr); if (cond_ptr) { tex_print_levels(); } } while (cond_ptr); } } else { tex_print_str("[conditional: none active]"); } } /* void tex_conditional_after_fi(void) { halfword t = get_token(); int tracing_ifs = tracing_ifs_par > 0; int tracing_commands = tracing_commands_par > 0; while (1) { pass_text_x(tracing_ifs, tracing_commands); if (cur_chr == fi_code) { pop_condition_stack(); break; } else { // some error } } back_input(t); } */