diff options
Diffstat (limited to 'source/luametatex/source/tex/texconditional.c')
-rw-r--r-- | source/luametatex/source/tex/texconditional.c | 1386 |
1 files changed, 1386 insertions, 0 deletions
diff --git a/source/luametatex/source/tex/texconditional.c b/source/luametatex/source/tex/texconditional.c new file mode 100644 index 000000000..95035f43e --- /dev/null +++ b/source/luametatex/source/tex/texconditional.c @@ -0,0 +1,1386 @@ +/* + 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, + .if_limit = 0, + .cur_if = 0, + .if_line = 0, + .skip_line = 0, + .chk_num = 0, + .chk_dim = 0, + .if_nesting = 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: + do { + tex_get_next(); + } while (cur_cmd == spacer_cmd); + 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); + } + do { + tex_get_next(); + } while (cur_cmd == spacer_cmd); + 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; + } + } +} + +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_style_code: tex_print_format("{mathstyle %i}", case_value); break; + case if_arguments_code : tex_print_format("{arguments %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; + } +} + +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: + /*tex + Test the relation between integers or dimensions. Here we use the fact that |<|, + |=|, and |>| are consecutive ASCII codes. + */ + { + 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 '<': result = (n1 < n2); break; + /* case '=': result = (n1 == n2); break; */ + case '>': result = (n1 > n2); break; + /* default: break; */ + default : result = (n1 == n2); break; + } + } + goto RESULT; + case if_abs_dim_code: + case if_dim_code: + /*tex + Test the relation between integers or dimensions. Here we use the fact that |<|, + |=|, and |>| are consecutive ASCII codes. + */ + { + 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 '<': result = (n1 < n2); break; + /* case '=': result = (n1 == n2); break; */ + case '>': result = (n1 > n2); break; + /* default: break; */ + default : result = (n1 == n2); break; + } + } + goto RESULT; + case if_odd_code: + /*tex Test if an integer is odd. */ + { + halfword v = tex_scan_int(0, NULL); + result = odd(v); + } + goto RESULT; + case if_vmode_code: + result = abs(cur_list.mode) == vmode; + goto RESULT; + case if_hmode_code: + result = abs(cur_list.mode) == hmode; + goto RESULT; + case if_mmode_code: + result = abs(cur_list.mode) == mmode; + 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: + { + singleword flag, fl; + tex_get_r_token(); + flag = eq_flag(cur_cs); + /* todo: each prefix */ + tex_get_token(); + if (cur_cmd == prefix_cmd) { + switch (cur_chr) { + case permanent_code : result = is_permanent (flag); break; + case immutable_code : result = is_immutable (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; + case global_code : result = is_global (flag); break; + case tolerant_code : result = is_tolerant (flag); break; + case protected_code : result = is_protected (flag); break; + case overloaded_code : result = is_overloaded (flag); break; + case aliased_code : result = is_aliased (flag); break; + case immediate_code : result = is_immediate (flag); break; + case semiprotected_code : result = is_semiprotected(flag); break; + } + } else { + tex_back_input(cur_tok); + fl = (singleword) tex_scan_int(1, NULL); /* maybe some checking or masking is needed here */ + result = (flag & fl) == fl; + if (! result) { + if (is_protected(fl)) { + result = is_protected_cmd(eq_type(cur_cs)); + } else if (is_tolerant(fl)) { + result = is_tolerant_cmd(eq_type(cur_cs)); + } else if (is_global(fl)) { + result = eq_level(cur_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 = token_info(input_state.parameter_stack[input_state.cur_input.parameter_start + token_chr(t) - 1]) != null ? 1 : 2; + 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 class; + 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; + class = lmt_function_call_by_class(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 (class) { + 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) { + do { + tex_get_next(); + } while (cur_cmd == spacer_cmd); + } 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); +} +*/
\ No newline at end of file |