% conditional.w % % Copyright 2009-2010 Taco Hoekwater % This file is part of LuaTeX. % LuaTeX is free software; you can redistribute it and/or modify it under % the terms of the GNU General Public License as published by the Free % Software Foundation; either version 2 of the License, or (at your % option) any later version. % LuaTeX is distributed in the hope that it will be useful, but WITHOUT % ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or % FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public % License for more details. % You should have received a copy of the GNU General Public License along % with LuaTeX; if not, see . @ @c #include "ptexlib.h" static const char _svn_version[] = "$Id: conditional.w 3955 2010-11-12 13:28:03Z taco $" "$URL: http://foundry.supelec.fr/svn/luatex/tags/beta-0.70.1/source/texk/web2c/luatexdir/tex/conditional.w $"; @ @c #define box(A) eqtb[box_base+(A)].hh.rh @* 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|. @c halfword cond_ptr; /* top of the condition stack */ int if_limit; /* upper bound on |fi_or_else| codes */ int cur_if; /* type of conditional being worked on */ int if_line; /* line where that conditional began */ @ When we skip conditional text, we keep track of the line number where skipping began, for use in error messages. @c int skip_line; /* skipping began here */ @ Here is a procedure that ignores text until coming to an \.{\\or}, \.{\\else}, or \.{\\fi} at level zero of $\.{\\if}\ldots\.{\\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). @c void pass_text(void) { int l; /* level of $\.{\\if}\ldots\.{\\fi}$ nesting */ int save_scanner_status; /* |scanner_status| upon entry */ save_scanner_status = scanner_status; scanner_status = skipping; l = 0; skip_line = line; while (1) { get_token_lua(); if (cur_cmd == fi_or_else_cmd) { if (l == 0) break; if (cur_chr == fi_code) decr(l); } else if (cur_cmd == if_test_cmd) { incr(l); } } scanner_status = save_scanner_status; if (int_par(tracing_ifs_code) > 0) show_cur_cmd_chr(); } @ 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 `\.{\\ifvoid1\\else...\\fi}' would otherwise require something after the `\.1'. @c void push_condition_stack(void) { halfword p = new_node(if_node, 0); vlink(p) = cond_ptr; if_limit_type(p) = (quarterword) if_limit; if_limit_subtype(p) = (quarterword) cur_if; if_line_field(p) = if_line; cond_ptr = p; cur_if = cur_chr; if_limit = if_code; if_line = line; } void pop_condition_stack(void) { halfword p; if (if_stack[in_open] == cond_ptr) if_warning(); /* conditionals possibly not properly nested with files */ p = cond_ptr; if_line = if_line_field(p); cur_if = if_limit_subtype(p); if_limit = if_limit_type(p); cond_ptr = vlink(p); flush_node(p); } @ Here's a procedure that changes the |if_limit| code corresponding to a given value of |cond_ptr|. @c void change_if_limit(int l, halfword p) { halfword q; if (p == cond_ptr) { if_limit = l; /* that's the easy case */ } else { q = cond_ptr; while (1) { if (q == null) confusion("if"); if (vlink(q) == p) { if_limit_type(q) = (quarterword) l; return; } q = vlink(q); } } } @ 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). @c static boolean test_for_cs(void) { boolean b; /*is the condition true? */ int m, s; /*to be tested against the second operand */ halfword n, p, q; /*for traversing token lists in \.{\\ifx} tests */ n = get_avail(); p = n; /*head of the list of characters */ b = false; while (1) { get_x_token(); if (cur_cs != 0) break; store_new_token(cur_tok); } if (cur_cmd != end_cs_name_cmd) { if (int_par(suppress_ifcsname_error_code)) { do { get_x_token(); } while (cur_cmd != end_cs_name_cmd); flush_list(n); return b; } else { complain_missing_csname(); } } /* Look up the characters of list |n| in the hash table, and set |cur_cs| */ m = first; p = token_link(n); while (p != null) { if (m >= max_buf_stack) { max_buf_stack = m + 4; if (max_buf_stack >= buf_size) check_buffer_overflow(max_buf_stack); } s = token_chr(token_info(p)); if (s <= 0x7F) { buffer[m++] = (packed_ASCII_code) s; } else if (s <= 0x7FF) { buffer[m++] = (packed_ASCII_code) (0xC0 + s / 0x40); buffer[m++] = (packed_ASCII_code) (0x80 + s % 0x40); } else if (s <= 0xFFFF) { buffer[m++] = (packed_ASCII_code) (0xE0 + s / 0x1000); buffer[m++] = (packed_ASCII_code) (0x80 + (s % 0x1000) / 0x40); buffer[m++] = (packed_ASCII_code) (0x80 + (s % 0x1000) % 0x40); } else { buffer[m++] = (packed_ASCII_code) (0xF0 + s / 0x40000); buffer[m++] = (packed_ASCII_code) (0x80 + (s % 0x40000) / 0x1000); buffer[m++] = (packed_ASCII_code) (0x80 + ((s % 0x40000) % 0x1000) / 0x40); buffer[m++] = (packed_ASCII_code) (0x80 + ((s % 0x40000) % 0x1000) % 0x40); } p = token_link(p); } if (m > first) { cur_cs = id_lookup(first, m - first); /*|no_new_control_sequence| is |true| */ } else if (m == first) { cur_cs = null_cs; /*the list is empty */ } b = (eq_type(cur_cs) != undefined_cs_cmd); flush_list(n); return b; } @ An active character will be treated as category 13 following \.{\\if\\noexpand} or following \.{\\ifcat\\noexpand}. @c #define get_x_token_or_active_char() do { \ get_x_token(); \ if (cur_cmd==relax_cmd && cur_chr==no_expand_flag) { \ if (is_active_cs(cs_text(cur_cs))) { \ cur_cmd=active_char_cmd; \ cur_chr=active_cs_value(cs_text(cur_tok-cs_token_flag)); \ } \ } \ } while (0) @ 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. @^recursion@> @c void conditional(void) { boolean b; /*is the condition true? */ int r; /*relation to be evaluated */ int m, n; /*to be tested against the second operand */ halfword p, q; /*for traversing token lists in \.{\\ifx} tests */ int save_scanner_status; /*|scanner_status| upon entry */ halfword save_cond_ptr; /*|cond_ptr| corresponding to this conditional */ int this_if; /*type of this conditional */ boolean is_unless; /*was this if preceded by `\.{\\unless}' ? */ b = false; /*default is false, just in case */ if (int_par(tracing_ifs_code) > 0) if (int_par(tracing_commands_code) <= 1) show_cur_cmd_chr(); push_condition_stack(); save_cond_ptr = cond_ptr; is_unless = (cur_chr >= unless_code); this_if = cur_chr % unless_code; /* Either process \.{\\ifcase} or set |b| to the value of a boolean condition */ switch (this_if) { case if_char_code: case if_cat_code: /* Test if two characters match */ get_x_token_or_active_char(); if ((cur_cmd > active_char_cmd) || (cur_chr > biggest_char)) { /*not a character */ m = relax_cmd; n = too_big_char; } else { m = cur_cmd; n = cur_chr; } get_x_token_or_active_char(); if ((cur_cmd > active_char_cmd) || (cur_chr > biggest_char)) { cur_cmd = relax_cmd; cur_chr = too_big_char; } if (this_if == if_char_code) b = (n == cur_chr); else b = (m == cur_cmd); break; case if_int_code: case if_dim_code: case if_abs_dim_code: case if_abs_num_code: /* Test relation between integers or dimensions */ /* Here we use the fact that |"<"|, |"="|, and |">"| are consecutive ASCII codes. */ if (this_if == if_int_code || this_if == if_abs_num_code) scan_int(); else scan_normal_dimen(); n = cur_val; if (n < 0) if (this_if == if_abs_dim_code || this_if == if_abs_num_code) negate(n); /* Get the next non-blank non-call... */ do { get_x_token(); } while (cur_cmd == spacer_cmd); if ((cur_tok >= other_token + '<') && (cur_tok <= other_token + '>')) { r = cur_tok - other_token; } else { print_err("Missing = inserted for "); print_cmd_chr(if_test_cmd, this_if); help1("I was expecting to see `<', `=', or `>'. Didn't."); back_error(); r = '='; } if (this_if == if_int_code || this_if == if_abs_num_code) scan_int(); else scan_normal_dimen(); if (cur_val < 0) if (this_if == if_abs_dim_code || this_if == if_abs_num_code) negate(cur_val); switch (r) { case '<': b = (n < cur_val); break; case '=': b = (n == cur_val); break; case '>': b = (n > cur_val); break; default: b = false; break; /*can't happen */ } break; case if_odd_code: /* Test if an integer is odd */ scan_int(); b = odd(cur_val); break; case if_vmode_code: b = (abs(cur_list.mode_field) == vmode); break; case if_hmode_code: b = (abs(cur_list.mode_field) == hmode); break; case if_mmode_code: b = (abs(cur_list.mode_field) == mmode); break; case if_inner_code: b = (cur_list.mode_field < 0); break; case if_void_code: case if_hbox_code: case if_vbox_code: /* Test box register status */ scan_register_num(); p = box(cur_val); if (this_if == if_void_code) b = (p == null); else if (p == null) b = false; else if (this_if == if_hbox_code) b = (type(p) == hlist_node); else b = (type(p) == vlist_node); break; case ifx_code: /* 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. */ save_scanner_status = scanner_status; scanner_status = normal; get_token_lua(); n = cur_cs; p = cur_cmd; q = cur_chr; get_token_lua(); if (cur_cmd != p) { b = false; } else if (cur_cmd < call_cmd) { b = (cur_chr == q); } else { /* Test if two macro texts match */ /* Note also that `\.{\\ifx}' decides that macros \.{\\a} and \.{\\b} are different in examples like this: $$\vbox{\halign{\.{#}\hfil&\qquad\.{#}\hfil\cr {}\\def\\a\{\\c\}& {}\\def\\c\{\}\cr {}\\def\\b\{\\d\}& {}\\def\\d\{\}\cr}}$$ */ p = token_link(cur_chr); q = token_link(equiv(n)); /*omit reference counts */ if (p == q) { b = true; } else { while ((p != null) && (q != null)) { if (token_info(p) != token_info(q)) { p = null; } else { p = token_link(p); q = token_link(q); } } b = ((p == null) && (q == null)); } } scanner_status = save_scanner_status; break; case if_eof_code: scan_four_bit_int_or_18(); if (cur_val == 18) b = !shellenabledp; else b = (read_open[cur_val] == closed); break; case if_true_code: b = true; break; case if_false_code: b = false; break; case if_case_code: /* Select the appropriate case and |return| or |goto common_ending| */ scan_int(); n = cur_val; /*|n| is the number of cases to pass */ if (int_par(tracing_commands_code) > 1) { begin_diagnostic(); tprint("{case "); print_int(n); print_char('}'); end_diagnostic(false); } while (n != 0) { pass_text(); if (cond_ptr == save_cond_ptr) { if (cur_chr == or_code) decr(n); else goto COMMON_ENDING; } else if (cur_chr == fi_code) { pop_condition_stack(); } } change_if_limit(or_code, save_cond_ptr); return; /*wait for \.{\\or}, \.{\\else}, or \.{\\fi} */ break; case if_primitive_code: save_scanner_status = scanner_status; scanner_status = normal; get_token_lua(); scanner_status = save_scanner_status; m = prim_lookup(cs_text(cur_cs)); b = ((cur_cmd != undefined_cs_cmd) && (m != undefined_primitive) && (cur_cmd == get_prim_eq_type(m)) && (cur_chr == get_prim_equiv(m))); break; case if_def_code: /* 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. */ save_scanner_status = scanner_status; scanner_status = normal; get_token_lua(); b = (cur_cmd != undefined_cs_cmd); scanner_status = save_scanner_status; break; case if_cs_code: b = test_for_cs(); break; case if_in_csname_code: b = is_in_csname; break; case if_font_char_code: /* The conditional \.{\\iffontchar} tests the existence of a character in a font. */ scan_font_ident(); n = cur_val; scan_char_num(); b = char_exists(n, cur_val); break; default: /* there are no other cases, but for -Wall: */ b = false; } if (is_unless) b = !b; if (int_par(tracing_commands_code) > 1) { /* Display the value of |b| */ begin_diagnostic(); if (b) tprint("{true}"); else tprint("{false}"); end_diagnostic(false); } if (b) { change_if_limit(else_code, save_cond_ptr); return; /*wait for \.{\\else} or \.{\\fi} */ } /* 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) { pass_text(); if (cond_ptr == save_cond_ptr) { if (cur_chr != or_code) goto COMMON_ENDING; print_err("Extra \\or"); help1("I'm ignoring this; it doesn't match any \\if."); error(); } else if (cur_chr == fi_code) { pop_condition_stack(); } } COMMON_ENDING: if (cur_chr == fi_code) pop_condition_stack(); else if_limit = fi_code; /*wait for \.{\\fi} */ }