diff options
Diffstat (limited to 'source/luametatex/source/tex/texmaincontrol.c')
-rw-r--r-- | source/luametatex/source/tex/texmaincontrol.c | 6412 |
1 files changed, 6412 insertions, 0 deletions
diff --git a/source/luametatex/source/tex/texmaincontrol.c b/source/luametatex/source/tex/texmaincontrol.c new file mode 100644 index 000000000..a1983ac4f --- /dev/null +++ b/source/luametatex/source/tex/texmaincontrol.c @@ -0,0 +1,6412 @@ +/* + See license.txt in the root of this project. +*/ + +# include "luametatex.h" + +/*tex + + We come now to the |main_control| routine, which contains the master switch that causes all the + various pieces of \TEX\ to do their things, in the right order. + + In a sense, this is the grand climax of the program: It applies all the tools that we have + worked so hard to construct. In another sense, this is the messiest part of the program: It + necessarily refers to other pieces of code all over the place, so that a person can't fully + understand what is going on without paging back and forth to be reminded of conventions that + are defined elsewhere. We are now at the hub of the web, the central nervous system that + touches most of the other parts and ties them together. + + The structure of |main_control| itself is quite simple. There's a label called |big_switch|, + at which point the next token of input is fetched using |get_x_token|. Then the program + branches at high speed into one of about 100 possible directions, based on the value of the + current mode and the newly fetched command code; the sum |abs(mode) + cur_cmd| indicates what + to do next. For example, the case |vmode + letter| arises when a letter occurs in vertical + mode (or internal vertical mode); this case leads to instructions that initialize a new + paragraph and enter horizontal mode.p + + The big |case| statement that contains this multiway switch has been labeled |reswitch|, so + that the program can |goto reswitch| when the next token has already been fetched. Most of + the cases are quite short; they call an \quote {action procedure} that does the work for that + case, and then they either |goto reswitch| or they \quote {fall through} to the end of the + |case| statement, which returns control back to |big_switch|. Thus, |main_control| is not an + extremely large procedure, in spite of the multiplicity of things it must do; it is small + enough to be handled by \PASCAL\ compilers that put severe restrictions on procedure size. + + One case is singled out for special treatment, because it accounts for most of \TEX's + activities in typical applications. The process of reading simple text and converting it + into |char_node| records, while looking for ligatures and kerns, is part of \TEX's \quote + {inner loop}; the whole program runs efficiently when its inner loop is fast, so this part + has been written with particular care. (This is no longer true in \LUATEX.) + + We leave the |space_factor| unchanged if |sf_code(cur_chr) = 0|; otherwise we set it equal + to |sf_code(cur_chr)|, except that it should never change from a value less than 1000 to a + value exceeding 1000. The most common case is |sf_code(cur_chr)=1000|, so we want that case to + be fast. + + All action is done via runners in the function table. Some runners are implemented here, + others are spread over modules. In due time I will use more prefixes to indicate where they + belong. Also, more runners will move to their respective modules, a stepwise process. This + split up is not always consistent which relates to the fact that \TEX\ is a monolothic program + which in turn means that we keep all the smaller (and more dependen) bits here. There are + subsystems but they hook into each other, take inserts and adjusts that hook into the builders + and packagers. + +*/ + +main_control_state_info lmt_main_control_state = { + .control_state = goto_next_state, + .local_level = 0, + .after_token = null, + .after_tokens = null, + .last_par_context = 0, + .loop_iterator = 0, + .loop_nesting = 0, + .quit_loop = 0, +}; + +/*tex + A few helpers: +*/ + +inline scaled tex_aux_checked_dimen1(scaled v) +{ + if (v > max_dimen) { + return max_dimen; + } else if (v < -max_dimen) { + return -max_dimen; + } else { + return v; + } +} + +inline scaled tex_aux_checked_dimen2(scaled v) +{ + if (v > max_dimen) { + return max_dimen; + } else if (v < 0) { + return 0; + } else { + return v; + } +} + +/*tex + These two helpers, of which the second one is still experimental, actually belong in another + file so then might be moved. Watch how the first one has the |unsave| call! + */ + +static void tex_aux_fixup_directions_and_unsave(void) +{ + int saved_par_state = internal_par_state_par; + int saved_dir_state = internal_dir_state_par; + int saved_direction = text_direction_par; + tex_pop_text_dir_ptr(); + tex_unsave(); + if (cur_mode == hmode) { + if (saved_dir_state) { + /* Add local dir node. */ + tex_tail_append(tex_new_dir(cancel_dir_subtype, text_direction_par)); + dir_direction(cur_list.tail) = saved_direction; + } + if (saved_par_state) { + /*tex Add local paragraph node. This resets after a group. */ + tex_tail_append(tex_new_par_node(hmode_par_par_subtype)); + } + } +} + +static void tex_aux_fixup_directions_only(void) +{ + int saved_dir_state = internal_dir_state_par; + int saved_direction = text_direction_par; + tex_pop_text_dir_ptr(); + if (saved_dir_state) { + /* Add local dir node. */ + tex_tail_append(tex_new_dir(cancel_dir_subtype, saved_direction)); + } +} + +static void tex_aux_fixup_math_and_unsave(void) +{ + int saved_math_style = internal_math_style_par; + int saved_math_scale = internal_math_scale_par; + tex_unsave(); + if (cur_mode == mmode) { + if (saved_math_style >= 0 && saved_math_style != cur_list.math_style) { + halfword noad = tex_new_node(style_node, (quarterword) saved_math_style); + cur_list.math_style = saved_math_style; + tex_tail_append(noad); + } + if (saved_math_scale != cur_list.math_scale) { + halfword noad = tex_new_node(style_node, scaled_math_style); + style_scale(noad) = saved_math_scale; + tex_tail_append(noad); + } + } +} + +/*tex + + If the user says, e.g., |\global \global|, the redundancy is silently accepted. The different + types of code values have different legal ranges; the following program is careful to check + each case properly. + +*/ + +static void tex_aux_out_of_range_error(halfword val, halfword max) +{ + tex_handle_error( + normal_error_type, + "Invalid code (%i), should be in the range %i..%i", + val, 0, max, + "I'm going to use 0 instead of that illegal code value." + ); +} + +/*tex + + The |run_| functions hook in the main control handler. Some immediately do something, others + trigger a follow up scan, driven by the cmd code. Here come some forward declarations; there + are more that the following |run_| functions. Some runners are defined in other modules. Some + runners finish what another started, for instance when we see a left brace, depending on state + another runner can kick in. + + */ + +static void tex_aux_adjust_space_factor(halfword chr) +{ + halfword s = tex_get_sf_code(chr); + if (s == 1000) { + cur_list.space_factor = 1000; + } else if (s < 1000) { + if (s > 0) { + cur_list.space_factor = s; + } else { + /* s <= 0 */ + } + } else if (cur_list.space_factor < 1000) { + cur_list.space_factor = 1000; + } else { + cur_list.space_factor = s; + } +} + +static void tex_aux_run_text_char_number(void) +{ + switch (cur_chr) { + case char_number_code: + { + halfword chr = tex_scan_char_number(0); + tex_aux_adjust_space_factor(chr); + tex_tail_append(tex_new_char_node(glyph_unset_subtype, cur_font_par, chr, 1)); + break; + } + case glyph_number_code: + { + scaled xoffset = glyph_x_offset_par; + scaled yoffset = glyph_y_offset_par; + halfword xscale = glyph_x_scale_par; + halfword yscale = glyph_y_scale_par; + halfword scale = glyph_scale_par; + halfword options = glyph_options_par; + halfword font = cur_font_par; + scaled left = 0; + scaled right = 0; + scaled raise = 0; + halfword chr = 0; + halfword glyph; + while (1) { + switch (tex_scan_character("xyofislrXYOFISLR", 0, 1, 0)) { + case 0: + goto DONE; + case 'x': case 'X': + switch (tex_scan_character("osOS", 0, 0, 0)) { + case 'o': case 'O': + if (tex_scan_mandate_keyword("xoffset", 2)) { + xoffset = tex_scan_dimen(0, 0, 0, 0, NULL); + } + break; + case 's': case 'S': + if (tex_scan_mandate_keyword("xscale", 2)) { + xscale = tex_scan_int(0, NULL); + } + break; + default: + tex_aux_show_keyword_error("xoffset|xscale"); + goto DONE; + } + break; + case 'y': case 'Y': + switch (tex_scan_character("osOS", 0, 0, 0)) { + case 'o': case 'O': + if (tex_scan_mandate_keyword("yoffset", 2)) { + yoffset = tex_scan_dimen(0, 0, 0, 0, NULL); + } + break; + case 's': case 'S': + if (tex_scan_mandate_keyword("yscale", 2)) { + yscale = tex_scan_int(0, NULL); + } + break; + default: + tex_aux_show_keyword_error("yoffset|yscale"); + goto DONE; + } + break; + case 'o': case 'O': + if (tex_scan_mandate_keyword("options", 1)) { + options = tex_scan_int(0, NULL); + if (options < glyph_option_normal_glyph) { + options = glyph_option_normal_glyph; + } else if (options > glyph_option_all) { + options = glyph_option_all; + } + } + break; + case 'f': case 'F': + if (tex_scan_mandate_keyword("font", 1)) { + font = tex_scan_font_identifier(NULL); + } + break; + case 'i': case 'I': + if (tex_scan_mandate_keyword("id", 1)) { + halfword f = tex_scan_int(0, NULL); + if (f > 0 && tex_is_valid_font(f)) { + font = f; + } + } + break; + case 's': case 'S': + if (tex_scan_mandate_keyword("scale", 1)) { + yscale = tex_scan_int(0, NULL); + } + break; + case 'l': case 'L': + if (tex_scan_mandate_keyword("left", 1)) { + left = tex_scan_dimen(0, 0, 0, 0, NULL); + } + break; + case 'r': case 'R': + switch (tex_scan_character("aiAI", 0, 0, 0)) { + case 'i': case 'I': + if (tex_scan_mandate_keyword("right", 2)) { + right = tex_scan_dimen(0, 0, 0, 0, NULL); + } + break; + case 'a': case 'A': + if (tex_scan_mandate_keyword("raise", 2)) { + raise = tex_scan_dimen(0, 0, 0, 0, NULL); + } + break; + default: + tex_aux_show_keyword_error("right|raise"); + goto DONE; + } + break; + default: + goto DONE; + } + } + DONE: + chr = tex_scan_char_number(0); + tex_aux_adjust_space_factor(chr); + glyph = tex_new_char_node(glyph_unset_subtype, font, chr, 1); + set_glyph_x_offset(glyph, xoffset); + set_glyph_y_offset(glyph, yoffset); + set_glyph_scale(glyph, scale); + set_glyph_x_scale(glyph, xscale); + set_glyph_y_scale(glyph, yscale); + set_glyph_left(glyph, left); + set_glyph_right(glyph, right); + set_glyph_raise(glyph, raise); + set_glyph_options(glyph, options); + tex_tail_append(glyph); + break; + } + } +} + +static void tex_aux_run_text_letter(void) { + tex_aux_adjust_space_factor(cur_chr); + tex_tail_append(tex_new_char_node(glyph_unset_subtype, cur_font_par, cur_chr, 1)); +} + +/*tex + + Here are all the functions that are called from |main_control| that are not already defined + elsewhere. For the moment, this list simply in the order that the appear in |init_main_control|, + below. + +*/ + +static void tex_aux_run_node(void) { + halfword n = cur_chr; + if (node_token_flagged(n)) { + tex_get_token(); + n = node_token_sum(n,cur_chr); + } + if (copy_lua_input_nodes_par) { + n = tex_copy_node_list(n, null); + } + tex_tail_append(n); + if (tex_nodetype_has_attributes(node_type(n)) && ! node_attr(n)) { + attach_current_attribute_list(n); + } + while (node_next(n)) { + n = node_next(n); + tex_tail_append(n); + if (tex_nodetype_has_attributes(node_type(n)) && ! node_attr(n)) { + attach_current_attribute_list(n); + } + } +} + +/* */ + +inline static void lmt_bytecode_run(int index) +{ + strnumber u = tex_save_cur_string(); + lmt_token_state.luacstrings = 0; + lmt_bytecode_call(index); + tex_restore_cur_string(u); + if (lmt_token_state.luacstrings > 0) { + tex_lua_string_start(); + } +} + +inline static void lmt_lua_run(int reference, int prefix) +{ + strnumber u = tex_save_cur_string(); + lmt_token_state.luacstrings = 0; + lmt_function_call(reference, prefix); + tex_restore_cur_string(u); + if (lmt_token_state.luacstrings > 0) { + tex_lua_string_start(); + } +} + +static void tex_aux_run_lua_protected_call(void) { + if (cur_chr > 0) { + lmt_lua_run(cur_chr, 0); + } else { + tex_normal_error("luacall", "invalid number"); + } +} + +static void tex_aux_set_lua_value(int a) { + if (cur_chr > 0) { + lmt_lua_run(cur_chr, a); + } else { + tex_normal_error("luavalue", "invalid number"); + } +} + +/*tex + + The occurrence of blank spaces is almost part of \TEX's inner loop, since we usually encounter + about one space for every five non-blank characters. Therefore |main_control| gives second + highest priority to ordinary spaces. + + When a glue parameter like |\spaceskip| is set to |0pt|, we will see to it later that the + corresponding glue specification is precisely |zero_glue|, not merely a pointer to some + specification that happens to be full of zeroes. Therefore it is simple to test whether a glue + parameter is zero or~not. + + There is a special treatment for spaces when |space_factor <> 1000|. + + */ + +static void tex_aux_run_math_space(void) { + if (! disable_spaces_par) { + if (node_type(cur_list.tail) == simple_noad) { + noad_options(cur_list.tail) |= noad_option_followed_by_space; + } + } +} + +static void tex_aux_run_space(void) { + switch (disable_spaces_par) { + case 1: + /*tex Don't inject anything, not even zero skip. */ + return; + case 2: + /*tex Inject nothing but zero glue. */ + tex_tail_append(tex_new_glue_node(zero_glue, zero_space_skip_glue)); /* todo: subtype, zero_space_glue? */ + break; + default: + /*tex + The tradional treatment. A difference with other \TEX's is that we store the spacing + in the node instead of using the (end of) paragraph bound value. + */ + { + halfword p; + if (cur_mode == hmode && cur_cmd == spacer_cmd && cur_list.space_factor != 1000) { + if ((cur_list.space_factor >= 2000) && (! tex_glue_is_zero(xspace_skip_par))) { + p = tex_get_scaled_parameter_glue(xspace_skip_code, xspace_skip_glue); + } else { + halfword cur_font = cur_font_par; + if (tex_glue_is_zero(space_skip_par)) { + p = tex_get_scaled_glue(cur_font); + } else { + p = tex_get_parameter_glue(space_skip_code, space_skip_glue); /* not scaled */ + } + /* Modify the glue specification in |q| according to the space factor */ + if (cur_list.space_factor >= 2000) { + glue_amount(p) += tex_get_scaled_extra_space(cur_font); + } + glue_stretch(p) = tex_xn_over_d(glue_stretch(p), cur_list.space_factor, 1000); + glue_shrink(p) = tex_xn_over_d(glue_shrink(p), 1000, cur_list.space_factor); + } + } else if (tex_glue_is_zero(space_skip_par)) { + /*tex Find the glue specification for text spaces in the current font. */ + p = tex_get_scaled_glue(cur_font_par); + } else { + /*tex Append a normal inter-word space to the current list. */ + p = tex_get_parameter_glue(space_skip_code, space_skip_glue); /* not scaled */ + } + tex_tail_append(p); + } + } +} + +/*tex A fast one, also used to silently ignore |\par|s in a math formula. */ + +static void tex_aux_run_relax(void) { + return; +} + +/*tex + + |ignore_spaces| is a special case: after it has acted, |get_x_token| has already fetched the + next token from the input, so that operation in |main_control| should be skipped. + +*/ + +static void tex_aux_run_ignore_something(void) { + switch (cur_chr) { + case ignore_space_code: + /*tex Get the next non-blank call. */ + do { + tex_get_x_token(); + } while (cur_cmd == spacer_cmd); + lmt_main_control_state.control_state = goto_skip_token_state; + break; + case ignore_par_code: + /*tex Get the next non-blank/par call. */ + do { + tex_get_x_token(); + } while (cur_cmd == spacer_cmd || cur_cmd == end_paragraph_cmd); + lmt_main_control_state.control_state = goto_skip_token_state; + break; + case ignore_argument_code: + /*tex There is nothing to show here. */ + break; + default: + break; + } +} + +/* */ + +static void tex_aux_run_math_non_math(void) { + if (tracing_commands_par >= 4) { + tex_begin_diagnostic(); + tex_print_format("[math: pushing back %C]", cur_cmd, cur_chr); + tex_end_diagnostic(); + } + tex_back_input(cur_tok); + tex_begin_paragraph(1, math_char_par_begin); +} + +/*tex + + The most important parts of |main_control| are concerned with \TEX's chief mission of box + making. We need to control the activities that put entries on vlists and hlists, as well as + the activities that convert those lists into boxes. All of the necessary machinery has already + been developed; it remains for us to \quote {push the buttons} at the right times. + + As an introduction to these routines, let's consider one of the simplest cases: What happens + when |\hrule| occurs in vertical mode, or |\vrule| in horizontal mode or math mode? The code + in |main_control| is short, since the |scan_rule_spec| routine already does most of what is + required; thus, there is no need for a special action procedure. + + Note that baselineskip calculations are disabled after a rule in vertical mode, by setting + |prev_depth := ignore_depth|. + + First we define a procedure that returns a pointer to a rule node. This routine is called just + after \TEX\ has seen |\hrule| or |\vrule|; therefore |cur_cmd| will be either |hrule| or + |vrule|. The idea is to store the default rule dimensions in the node, then to override them if + |height| or |width| or |depth| specifications are found (in any order). + + For a moment I considered this: + + \starttyping + if (scan_keyword("to")) { + scan_dimen(0, 0, 0, 0); rule_width(q) = cur_val; + scan_dimen(0, 0, 0, 0); rule_height(q) = cur_val; + scan_dimen(0, 0, 0, 0); rule_depth(q) = cur_val; + return q; + } + \stoptyping + +*/ + + +/*tex + + Many of the actions related to box-making are triggered by the appearance of braces in the + input. For example, when the user says |\hbox to 100pt {<hlist>}| in vertical mode, the + information about the box size (100pt, |exactly|) is put onto |save_stack| with a level + boundary word just above it, and |cur_group:=adjusted_hbox_group|; \TEX\ enters restricted + horizontal mode to process the hlist. The right brace eventually causes |save_stack| to be + restored to its former state, at which time the information about the box size (100pt, + |exactly|) is available once again; a box is packaged and we leave restricted horizontal mode, + appending the new box to the current list of the enclosing mode (in this case to the current + list of vertical mode), followed by any vertical adjustments that were removed from the box by + |hpack|. + + The next few sections of the program are therefore concerned with the treatment of left and + right curly braces. + + If a left brace occurs in the middle of a page or paragraph, it simply introduces a new level + of grouping, and the matching right brace will not have such a drastic effect. Such grouping + affects neither the mode nor the current list. + +*/ + +static void tex_aux_run_left_brace(void) { + tex_new_save_level(simple_group); + update_tex_internal_par_state(0); + update_tex_internal_dir_state(0); +} + +/*tex + + The |also_simple_group| variant is triggered by |\beginsimplegroup|. It permits a mixed group + ending model: + + \starttyping + \def\foo{\beginsimplegroup\bf\let\next} \foo{text} + \stoptyping + + So, such a group can end with |\endgroup| as well as |\egroup| or equivalents. This trick is + mostly meant for math where a complex group produces a list which in turn influences spacing. + +*/ + +static void tex_aux_run_begin_group(void) { + switch (cur_chr) { + case semi_simple_group_code: + case also_simple_group_code: + tex_new_save_level(cur_chr ? also_simple_group : semi_simple_group); + update_tex_internal_par_state(0); + update_tex_internal_dir_state(0); + break; + case math_simple_group_code: + tex_new_save_level(math_simple_group); + update_tex_internal_math_style(cur_mode == mmode ? cur_list.math_style : -1); + update_tex_internal_math_scale(cur_mode == mmode ? cur_list.math_scale : 0); + break; + } +} + +static void tex_aux_run_end_group(void) { +// /* cur_chr can be 1 for a endsimplegroup but it's equivalent */ +// if (cur_group == semi_simple_group || cur_group == also_simple_group) { +// tex_aux_fixup_directions_and_unsave(); /*tex Includes the |save()| call! */ +// } else { +// tex_off_save(); /*tex Recover with error. */ +// } + switch (cur_group) { + case semi_simple_group: + case also_simple_group: + tex_aux_fixup_directions_and_unsave(); /*tex Includes the |save()| call! */ + break; + case math_simple_group: + tex_aux_fixup_math_and_unsave(); /*tex Includes the |save()| call! */ + break; + default: + tex_off_save(); /*tex Recover with error. */ + break; + } +} + +/*tex + + Constructions that require a box are started by calling |scan_box| with a specified context + code. The |scan_box| routine verifies that a |make_box| command comes next and then it calls + |begin_box|. + + Maybe we should just have three variants as sharing this makes it messy: |cur_cmd| combined + with |cur_chr| and funny flags for leaders. Due to grouping we have a shared |box_end| so + it doesn't become much prettier anyway. + + */ + +static void tex_aux_scan_box(int boxcontext, int optional_equal, scaled shift) +{ + /*tex Get the next non-blank non-relax... and optionally skip an equal sign */ + while (1) { + tex_get_x_token(); + if (cur_cmd == spacer_cmd) { + /*tex Go on. */ + } else if (cur_cmd == relax_cmd) { + optional_equal = 0; + } else if (optional_equal && cur_tok == equal_token) { + optional_equal = 0; + } else { + break; + } + } + switch (cur_cmd) { + case make_box_cmd: + { + tex_begin_box(boxcontext, shift); + return; + } + case vcenter_cmd: + { + tex_run_vcenter(); + return; + } + case lua_call_cmd: + case lua_protected_call_cmd: + { + if (box_leaders_flag(boxcontext)) { + tex_aux_run_lua_protected_call(); + tex_get_next(); + if (cur_cmd == node_cmd) { + /*tex So we only fetch the tail; the rest can mess up in the current list! */ + halfword boxnode = null; + tex_aux_run_node(); + boxnode = tex_pop_tail(); + if (boxnode) { + switch (node_type(boxnode)) { + case hlist_node: + case vlist_node: + case rule_node: + case glyph_node: + tex_box_end(boxcontext, boxnode, shift, unset_noad_class); + return; + } + } + } + tex_formatted_error("lua", "invalid function call, proper leader content expected"); + return; + } + break; + } + case lua_value_cmd: + { + halfword v = tex_scan_lua_value(cur_chr); + switch (v) { + case no_val_level: + tex_box_end(boxcontext, null, shift, unset_noad_class); + return; + case list_val_level: + if (box_leaders_flag(boxcontext)) { + switch (node_type(cur_val)) { + case hlist_node: + case vlist_node: + case rule_node: + // case glyph_node: + tex_box_end(boxcontext, cur_val, shift, unset_noad_class); + return; + } + } else { + switch (node_type(cur_val)) { + case hlist_node: + case vlist_node: + tex_box_end(boxcontext, cur_val, shift, unset_noad_class); + return; + } + } + } + tex_formatted_error("lua", "invalid function call, return type %i instead of %i", v, list_val_level); + return; + } + case hrule_cmd: + case vrule_cmd: + { + if (box_leaders_flag(boxcontext)) { + halfword rulenode = tex_aux_scan_rule_spec(cur_cmd == hrule_cmd ? h_rule_type : (cur_cmd == vrule_cmd ? v_rule_type : m_rule_type), cur_chr); + tex_box_end(boxcontext, rulenode, shift, unset_noad_class); + return; + } else { + break; + } + } + case char_number_cmd: + { + if (cur_mode == hmode && box_leaders_flag(boxcontext)) { + /*tex We cheat by just appending to the current list. */ + halfword boxnode = null; + tex_aux_run_text_char_number(); + boxnode = tex_pop_tail(); + tex_box_end(boxcontext, boxnode, shift, unset_noad_class); + return; + } else { + break; + } + } + } + tex_handle_error( + back_error_type, + "A <box> was supposed to be here", + "I was expecting to see \\hbox or \\vbox or \\copy or \\box or something like\n" + "that. So you might find something missing in your output. But keep trying; you\n" + "can fix this later." + ); + if (boxcontext == lua_scan_flag) { + tex_box_end(boxcontext, null, shift, unset_noad_class); + } +} + +/*tex + The |tex_aux_scan_box| call takes a |context| parameter and that is is somewhat weird: it + can be a box number, a flag signaling a special kind of box like a leader, or it can be the + shift in a move. It all relates to passing something in a way that make it possible to pick + it up later. +*/ + +static void tex_aux_run_move(void) { + int code = cur_chr; + halfword val = tex_scan_dimen(0, 0, 0, 0, NULL); + tex_aux_scan_box(0, 0, code == move_forward_code ? val : - val); +} + +/*tex + Local boxes are something that comes from \OMEGA\ but we implement them somewhat differently. + When we finish, the test for |p != null| ensures that empty |\localleftbox| and |\localrightbox| + commands are not applied. But it is stull kind of a mess, this mechanism. Resetting these boxes + involves registering a state but now we also check if it has been set at all. When I need this + feature I will probably check it out and redo some of the code. + + Options: \quote {par} will set the initial par node, when present. + +*/ + +typedef enum saved_localbox_items { + saved_localbox_item_location = 0, + saved_localbox_item_index = 1, + saved_localbox_item_options = 2, + saved_localbox_n_of_items = 3, +} saved_localbox_items; + +static void tex_aux_scan_local_box(int code) { + quarterword options = 0; + halfword class = 0; + tex_scan_local_boxes_keys(&options, &class); + tex_set_saved_record(saved_localbox_item_location, saved_local_box_location, 0, code); + tex_set_saved_record(saved_localbox_item_index, saved_local_box_index, 0, class); + tex_set_saved_record(saved_localbox_item_options, saved_local_box_options, 0, options); + lmt_save_state.save_stack_data.ptr += saved_localbox_n_of_items; + tex_new_save_level(local_box_group); + tex_scan_left_brace(); + tex_push_nest(); + cur_list.mode = -hmode; + cur_list.space_factor = 1000; +} + +static void tex_aux_finish_local_box(void) +{ + tex_unsave(); + if (saved_type(saved_localbox_item_location - saved_localbox_n_of_items) == saved_local_box_location) { + halfword p; + halfword location = saved_value(saved_localbox_item_location - saved_localbox_n_of_items); + quarterword options = (quarterword) saved_value(saved_localbox_item_options - saved_localbox_n_of_items); + halfword index = saved_value(saved_localbox_item_index - saved_localbox_n_of_items); + int islocal = (options & local_box_local_option) == local_box_local_option; + int keep = (options & local_box_keep_option) == local_box_keep_option; + int atpar = (options & local_box_par_option) == local_box_par_option; + lmt_save_state.save_stack_data.ptr -= saved_localbox_n_of_items; + p = node_next(cur_list.head); + tex_pop_nest(); + if (p) { + /*tex Somehow |filtered_hpack| goes beyond the first node so we loose it. */ + node_prev(p) = null; + if (tex_list_has_glyph(p)) { + tex_handle_hyphenation(p, null); + p = tex_handle_glyphrun(p, local_box_group, text_direction_par); + } + if (p) { + p = lmt_hpack_filter_callback(p, 0, packing_additional, local_box_group, direction_unknown, null); + } + /*tex + We really need something packed so we play safe! This feature is inherited but could + have been delegated to a callback anyway. + */ + p = tex_hpack(p, 0, packing_additional, direction_unknown, holding_none_option); + // node_subtype(p) = location == local_left_box_code ? local_left_list : local_right_list; + node_subtype(p) = local_list; + box_index(p) = index; + // attach_current_attribute_list(p); // leaks + } + // what to do with reset + if (islocal) { + /*tex There no copy needed either! */ + } else { + tex_update_local_boxes(p, index, location); + } + // if (cur_mode == hmode) { + if (cur_mode == hmode || cur_mode == mmode) { + if (atpar) { + halfword par = tex_find_par_par(cur_list.head); + if (par) { + if (p && ! islocal) { + p = tex_copy_node(p); + } + tex_replace_local_boxes(par, p, index, location); + } + } else { + /*tex + We had a null check here but we also want to be able to reset these boxes so we + no longer check. + */ + tex_tail_append(tex_new_par_node(local_box_par_subtype)); + if (! keep) { + /*tex So we can group and keep it. */ + update_tex_internal_par_state(internal_par_state_par + 1); + } + } + } + } else { + tex_confusion("build local box"); + } +} + +// static void tex_aux_run_leader(void) { +// switch (cur_chr) { +// case a_leaders_code: +// tex_aux_scan_box(a_leaders_flag, 0, 0); +// break; +// case c_leaders_code: +// tex_aux_scan_box(c_leaders_flag, 0, 0); +// break; +// case x_leaders_code: +// tex_aux_scan_box(x_leaders_flag, 0, 0); +// break; +// case g_leaders_code: +// tex_aux_scan_box(g_leaders_flag, 0, 0); +// break; +// } +// } + +static int leader_flags[] = { + a_leaders_flag, + c_leaders_flag, + x_leaders_flag, + g_leaders_flag, + u_leaders_flag, +}; + +static void tex_aux_run_leader(void) { + tex_aux_scan_box(leader_flags[cur_chr], 0, null_flag); +} + +static void tex_aux_run_legacy(void) { + switch (cur_chr) { + case shipout_code: + tex_aux_scan_box(shipout_flag, 0, null_flag); + break; + default: + /* cant_happen */ + break; + } +} + +static void tex_aux_run_local_box(void) { + tex_aux_scan_local_box(cur_chr); +} + +static void tex_aux_run_make_box(void) { + tex_begin_box(0, null_flag); +} + +/*tex + + There is a really small patch to add a new primitive called |\quitvmode|. In vertical modes, it + is identical to |\indent|, but in horizontal and math modes it is really a no-op (as opposed to + |\indent|, which executes the |indent_in_hmode| procedure). + + A paragraph begins when horizontal-mode material occurs in vertical mode, or when the paragraph + is explicitly started by |\quitvmode|, |\indent| or |\noindent|. We can revert this to zero + while at the same time keeping the node. + + To be considered: delay (as with parfilskip), skip + boundary, pre/post anchor etc. + +*/ + +static void tex_aux_insert_parindent(int indented) +{ + if (normalize_line_mode_permitted(normalize_line_mode_par, parindent_skip_mode)) { + /*tex We cannot use |new_param_glue| yet, because it's a dimen */ + halfword p = tex_new_glue_node(zero_glue, indent_skip_glue); + if (indented) { + glue_amount(p) = par_indent_par; + } + tex_tail_append(p); + } else if (indented) { + halfword p = tex_new_null_box_node(hlist_node, indent_list); + box_dir(p) = (singleword) par_direction_par; + box_width(p) = par_indent_par; + tex_tail_append(p); + } +} + +static void tex_aux_remove_parindent(void) +{ + halfword tail = cur_list.tail; + switch (node_type(tail)) { + case glue_node: + if (tex_is_par_init_glue(tail)) { + glue_amount(tail) = 0; + } + break; + case hlist_node: + if (node_subtype(tail) == indent_list) { + box_width(tail) = 0; + } + break; + } +} + +static void tex_aux_run_begin_paragraph_vmode(void) { + switch (cur_chr) { + case noindent_par_code: + tex_begin_paragraph(0, no_indent_par_begin); + break; + case indent_par_code: + tex_begin_paragraph(1, indent_par_begin); + break; + case quitvmode_par_code: + tex_begin_paragraph(1, force_par_begin); + break; + case snapshot_par_code: + /* silently ignore */ + tex_scan_int(0, NULL); + break; + case attribute_par_code: + /* silently ignore */ + tex_scan_attribute_register_number(); + tex_scan_int(1, NULL); + break; + case wrapup_par_code: + tex_you_cant_error(NULL); + break; + } +} + +static void tex_aux_run_begin_paragraph_hmode(void) { + switch (cur_chr) { + case noindent_par_code: + /*tex We do as traditional \TEX, so no zero skip either when normalizing */ + break; + case indent_par_code: + /*tex We can have |\hbox {\indent x\indent x\indent}| */ + tex_aux_insert_parindent(1); + break; + case undent_par_code: + tex_aux_remove_parindent(); + break; + case snapshot_par_code: + { + halfword tag = tex_scan_int(0, NULL); + halfword par = tex_find_par_par(cur_list.head); + if (par) { + tex_snapshot_par(par, tag); + } + break; + } + case attribute_par_code: + { + halfword att = tex_scan_attribute_register_number(); + halfword val = tex_scan_int(1, NULL); + halfword par = tex_find_par_par(cur_list.head); + if (par) { + if (val == unused_attribute_value) { + tex_unset_attribute(par, att, val); + } else { + tex_set_attribute(par, att, val); + } + } + break; + } + case wrapup_par_code: + { + halfword par = tex_find_par_par(cur_list.head); + if (par) { + halfword eop = par_end_par_tokens(par); + int reverse = tex_scan_optional_keyword("reverse"); + do { + tex_get_x_token(); + } while (cur_cmd == spacer_cmd); + if (cur_cmd == left_brace_cmd) { + halfword source = tex_scan_toks_normal(1, NULL); + if (source) { + if (eop) { + if (reverse) { + halfword p = token_link(source); + if (p) { + while (token_link(p)) { + p = token_link(p); + } + token_link(p) = token_link(par_end_par_tokens(par)); + token_link(par_end_par_tokens(par)) = null; + tex_flush_token_list(par_end_par_tokens(par)); + par_end_par_tokens(par) = source; + } + } else { + halfword p = eop; + while (token_link(p)) { + p = token_link(p); + } + token_link(p) = token_link(source); + token_link(source) = null; + tex_flush_token_list(source); + } + } else { + par_end_par_tokens(par) = source; + } + } + } else { + tex_handle_error( + normal_error_type, + "I expected a {", + "The '\\wrapuppar' command only accepts an explicit token list." + ); + } + } + break; + } + } +} + +static void tex_aux_run_begin_paragraph_mmode(void) { + switch (cur_chr) { + case indent_par_code: + { + halfword p = tex_new_null_box_node(hlist_node, indent_list); + box_width(p) = par_indent_par; + p = tex_new_sub_box(p); + tex_tail_append(p); + break; + } + case snapshot_par_code: + /* silently ignore */ + tex_scan_int(0, NULL); + break; + case attribute_par_code: + /* silently ignore */ + tex_scan_attribute_register_number(); + tex_scan_int(1, NULL); + break; + case wrapup_par_code: + tex_you_cant_error(NULL); + break; + } +} + +static void tex_aux_run_new_paragraph(void) { + int context; + switch (cur_cmd) { + case char_given_cmd: + case other_char_cmd: + case letter_cmd: + case accent_cmd: + case char_number_cmd: + case discretionary_cmd: + context = char_par_begin; + break; + case boundary_cmd: + context = boundary_par_begin; + break; + case explicit_space_cmd: + context = space_par_begin; + break; + case math_shift_cmd: + case math_shift_cs_cmd: + context = math_par_begin; + break; + case hskip_cmd: + context = hskip_par_begin; + break; + case kern_cmd: + context = kern_par_begin; + break; + case un_hbox_cmd: + context = un_hbox_char_par_begin; + break; + case valign_cmd: + context = valign_char_par_begin; + break; + case vrule_cmd: + context = vrule_char_par_begin; + break; + default: + context = normal_par_begin; + break; + } + if (tracing_commands_par >= 4) { + tex_begin_diagnostic(); + tex_print_format("[text: pushing back %C]", cur_cmd, cur_chr); + tex_end_diagnostic(); + } + tex_back_input(cur_tok); + tex_begin_paragraph(1, context); +} + +/*tex + Append a |boundary_node|. The |page_boundary| case is kind of special. It adds a node node to + the list of contributions and triggers the page builder (that only kicks in when there is some + contribution). That itself can result in firing up the output routine if the page is filled up. + An alternative is to inject a penalty but we don't want anything to stay behind and using some + special penalty would be incompatible. + + In order to really trigger a check we change the boundary node into zero penalty in the builder + when it still present (as the callback can decide to wipe it). It's a bit weird mechanism but + it closely relates to triggering something that gets logged in the core engine. Anyway, we + basically have a zero penalty equivalent (but one that doesn't register as last node). +*/ + +void tex_page_boundary_message(const char *s, halfword n) +{ + if (tracing_pages_par >= 0) { + tex_begin_diagnostic(); + tex_print_format("[page: boundary, %s, trigger %i]", s, n); + tex_end_diagnostic(); + } +} + +static void tex_aux_run_par_boundary(void) { + switch (cur_chr) { + case page_boundary: + { + halfword n = tex_scan_int(0, NULL); + if (lmt_nest_state.nest_data.ptr == 0 && ! lmt_page_builder_state.output_active) { + halfword n = tex_new_node(boundary_node, (quarterword) cur_chr); + boundary_data(n) = n; + tex_tail_append(n); + if (cur_list.mode == vmode) { + if (! lmt_page_builder_state.output_active) { + tex_page_boundary_message("callback triggered", n); + lmt_page_filter_callback(boundary_page_context, n); + } + tex_page_boundary_message("build triggered", n); + tex_build_page(); + } else { + tex_page_boundary_message("appended", n); + } + } else { + tex_page_boundary_message("ignored", n); + } + break; + } + /*tex Not yet, first I need a proper use case. */ /* + case par_boundary: + { + halfword n = tex_new_node(boundary_node, (quarterword) cur_chr); + boundary_data(n) = tex_scan_int(0, NULL); + tex_tail_append(n); + break; + } + */ + default: + /*tex Go into horizontal mode and try again (was already the modus operandi). */ + tex_aux_run_new_paragraph(); + break; + } +} + +static void tex_aux_run_text_boundary(void) { + halfword n = tex_new_node(boundary_node, (quarterword) cur_chr); + switch (cur_chr) { + case user_boundary: + case protrusion_boundary: + boundary_data(n) = tex_scan_int(0, NULL); + break; + default: + break; + } + tex_tail_append(n); +} + +static void tex_aux_run_math_boundary(void) { + switch (cur_chr) { + case user_boundary: + { + halfword n = tex_new_node(boundary_node, user_boundary); + boundary_data(n) = tex_scan_int(0, NULL); + tex_tail_append(n); + break; + } + case protrusion_boundary: + tex_scan_int(0, NULL); + break; + } +} + +/*tex + + A paragraph ends when a |par_end| command is sensed, or when we are in horizontal mode when + reaching the right brace of vertical-mode routines like |\vbox|, |\insert|, or |\output|. + +*/ + +static void tex_aux_run_paragraph_end_vmode(void) { + // tex_normal_paragraph(normal_par_context); + tex_normal_paragraph(vmode_par_context); + if (cur_list.mode > nomode) { + if (! lmt_page_builder_state.output_active) { + lmt_page_filter_callback(vmode_par_page_context, 0); + } + tex_build_page(); + } +} + +/*tex We could pass the group and context here if needed and set some parameter. */ + +int tex_wrapped_up_paragraph(int context) { + halfword par = tex_find_par_par(cur_list.head); + lmt_main_control_state.last_par_context = context; + if (par) { + int done = 0; + if (par_end_par_tokens(par)) { + halfword eop = par_end_par_tokens(par); + par_end_par_tokens(par) = null; + tex_back_input(cur_tok); + /*tex We inject the tokens, which increments the ref count; this one has tracing. */ + tex_begin_token_list(eop, end_paragraph_text); + /*tex So we need to decrement the token ref here. */ + tex_delete_token_reference(eop); + done = 1; + } + // if (end_of_par_par) { + // if (! done) { + // back_input(cur_tok); + // } + // begin_token_list(end_of_par_par, end_paragraph_text); + // update_tex_end_of_par(null); + // done = 1; + // } + return done; + } else { + return 0; + } +} + +static void tex_aux_run_paragraph_end_hmode(void) { + if (! tex_wrapped_up_paragraph(normal_par_context)) { + if (lmt_input_state.align_state < 0) { + /*tex This tries to recover from an alignment that didn't end properly. */ + tex_off_save(); + } + /* This takes us to the enclosing mode, if |mode > 0|. */ + tex_end_paragraph(bottom_level_group, normal_par_context); + if (cur_list.mode == vmode) { + if (! lmt_page_builder_state.output_active) { + lmt_page_filter_callback(hmode_par_page_context, 0); + } + tex_build_page(); + } + } +} + +/* */ + +static void tex_aux_run_halign_mmode(void) { + if (tex_in_privileged_mode()) { + if (cur_group == math_shift_group) { + tex_run_alignment_initialize(); + } else { + tex_off_save(); + } + } +} + +/*tex + + The |\afterassignment| command puts a token into the global variable |after_token|. This global + variable is examined just after every assignment has been performed. It's value is zero, or a + saved token. + + Todo: combine code in helper. + +*/ + +static void tex_aux_run_after_something(void) { + switch (cur_chr) { + case after_group_code: + { + halfword t = tex_get_token(); /* avoid realloc issues */ + t = tex_get_available_token(t); + tex_save_for_after_group(t); + break; + } + case after_assignment_code: + { + lmt_main_control_state.after_token = tex_get_token(); + break; + } + case at_end_of_group_code: + { + halfword t = tex_get_token(); /* avoid realloc issues */ + halfword r = tex_get_available_token(t); + if (end_of_group_par) { + halfword p = end_of_group_par; + while (token_link(p)) { + p = token_link(p); + } + token_link(p) = r; + } else { + halfword p = tex_get_available_token(null); + token_link(p) = r; + update_tex_end_of_group(p); + } + break; + } + case after_grouped_code: + { + do { + tex_get_x_token(); + } while (cur_cmd == spacer_cmd); + if (cur_cmd == left_brace_cmd) { + halfword source = tex_scan_toks_normal(1, NULL); + if (source) { + tex_save_for_after_group(token_link(source)); + token_link(source) = null; + } + tex_flush_token_list(source); + } else { + tex_handle_error( + normal_error_type, + "I expected a {", + "The '\\aftergrouped' command only accepts an explicit token list." + ); + } + break; + } + case after_assigned_code: + { + do { + tex_get_x_token(); + } while (cur_cmd == spacer_cmd); + if (cur_cmd == left_brace_cmd) { + halfword source = tex_scan_toks_normal(1, NULL); + if (source) { + lmt_main_control_state.after_tokens = token_link(source); + token_link(source) = null; + } + tex_flush_token_list(source); + } else { + tex_handle_error( + normal_error_type, + "I expected a {", + "The '\\afterassigned' command only accepts an explicit token list." + ); + } + break; + } + case at_end_of_grouped_code: + { + do { + tex_get_x_token(); + } while (cur_cmd == spacer_cmd); + if (cur_cmd == left_brace_cmd) { + halfword source = tex_scan_toks_normal(1, NULL); + if (source) { + if (end_of_group_par) { + halfword p = end_of_group_par; + while (token_link(p)) { + p = token_link(p); + } + token_link(p) = token_link(source); + token_link(source) = null; + tex_flush_token_list(source); + } else { + update_tex_end_of_group(source); + } + } + } else { + tex_handle_error( + normal_error_type, + "I expected a {", + "The '\\endofgrouped' command only accepts an explicit token list." + ); + } + break; + } + } +} + +inline static void tex_aux_finish_after_assignment(void) +{ + if (lmt_main_control_state.after_token) { + tex_back_input(lmt_main_control_state.after_token); + lmt_main_control_state.after_token = null; + } + if (lmt_main_control_state.after_tokens) { + tex_begin_inserted_list(lmt_main_control_state.after_tokens); + lmt_main_control_state.after_tokens = null; + } +} + +static void tex_aux_invalid_catcode_table_error(void) { + tex_handle_error( + normal_error_type, + "Invalid \\catcode table", + "All \\catcode table ids must be between 0 and " LMT_TOSTRING(max_n_of_catcode_tables-1) + ); +} + +static void tex_aux_overwrite_catcode_table_error(void) { + tex_handle_error( + normal_error_type, + "Invalid \\catcode table", + "You cannot overwrite the current \\catcode table" + ); +} + +static void tex_aux_run_catcode_table(void) { + switch (cur_chr) { + case save_cat_code_table_code: + { + halfword v = tex_scan_int(0, NULL); + if ((v < 0) || (v >= max_n_of_catcode_tables)) { + tex_aux_invalid_catcode_table_error(); + } else if (v == cat_code_table_par) { + tex_aux_overwrite_catcode_table_error(); + } else { + tex_copy_cat_codes(cat_code_table_par, v); + } + break; + } + case init_cat_code_table_code: + { + halfword v = tex_scan_int(0, NULL); + if ((v < 0) || (v >= max_n_of_catcode_tables)) { + tex_aux_invalid_catcode_table_error(); + } else if (v == cat_code_table_par) { + tex_aux_overwrite_catcode_table_error(); + } else { + tex_initialize_cat_codes(v); + } + break; + } + /* + case dflt_cat_code_table_code: + { + halfword v = scan_int(1); + if ((v < 0) || (v > CATCODE_MAX)) { + invalid_catcode_table_error(); + } else { + set_cat_code_table_default(cat_code_table_par, v); + } + } + break; + */ + default: + break; + } +} + +static void tex_aux_run_end_local(void) +{ + if (tracing_nesting_par > 2) { + tex_local_control_message("leaving token scanner due to local end token"); + } + tex_end_local_control(); +} + +static void tex_aux_run_lua_function_call(void) +{ + switch (cur_chr) { + case lua_function_call_code: + { + halfword v = tex_scan_function_reference(0); + lmt_lua_run(v, 0); + break; + } + case lua_bytecode_call_code: + { + halfword v = tex_scan_bytecode_reference(0); + lmt_bytecode_run(v); + break; + } + default: + break; + } +} + +/*tex + + The |main_control| uses a jump table, and |init_main_control| sets that table up. We need to + assign an entry for {\em each} of the three modes! + + For mode-independent commands, the following macro is useful. Also, there is a list of cases + where the user has probably gotten into or out of math mode by mistake. \TEX\ will insert a + dollar sign and rescan the current token, and it makes sense to have a macro for that as well. + +*/ + +# if (main_control_mode == 0) + + typedef void (*main_control_function)(void); + + static main_control_function *jump_table; + +# endif + +/*tex + + Here is |main_control| itself. It is quite short nowadays. The initializer is at the end of + this file which saves a nunch of forward declarations. + + */ + +inline static void tex_aux_big_switch (int mode, int cmd); + +int tex_main_control(void) +{ + lmt_main_control_state.control_state = goto_next_state; + if (every_job_par) { + tex_begin_token_list(every_job_par, every_job_text); + } + while (1) { + if (lmt_main_control_state.control_state == goto_skip_token_state) { + lmt_main_control_state.control_state = goto_next_state; + } else { + tex_get_x_token(); + } + /*tex + Give diagnostic information, if requested When a new token has just been fetched at + |big_switch|, we have an ideal place to monitor \TEX's activity. + */ + if (tracing_commands_par > 0) { + tex_show_cmd_chr(cur_cmd, cur_chr); + } + /*tex Run the command: */ + tex_aux_big_switch(cur_mode, cur_cmd); + if (lmt_main_control_state.control_state == goto_return_state) { + return cur_chr == dump_code; + } + } + /*tex not reached */ + return 0; +} + +/*tex + + We assume a trailing |\relax|: |{...}\relax|, so we don't need a |back_input ()| here. + +*/ + +void tex_local_control_message(const char *s) +{ + tex_begin_diagnostic(); + tex_print_format("[local control: level %i, %s]", lmt_main_control_state.local_level, s); + tex_end_diagnostic(); +} + +/*tex + + We can save in two ways but when, for symmetry I want it to happen at the current level, we need + to use the save stack. It depends a bit on how this will evolve. + + This one is used in the runlocal \LUA\ helper. This local control is in fact like the main loop, + so it can result in stuff being injected in for instance the main vertical list. I played with + control over the mode but that gave weird side effects, so I dropped that immediately. + + The implementation of local control in \LUAMETATEX\ is a bit different from \LUATEX\ because we + use it in several ways. + +*/ + +void tex_local_control(int obeymode) +{ + full_scanner_status saved_full_status = tex_save_full_scanner_status(); + int old_mode = cur_list.mode; + int at_level = lmt_main_control_state.local_level; + lmt_main_control_state.local_level += 1; + lmt_main_control_state.control_state = goto_next_state; + if (! obeymode) { + cur_list.mode = -hmode; + } + while (1) { + if (lmt_main_control_state.control_state == goto_skip_token_state) { + lmt_main_control_state.control_state = goto_next_state; + } else { + tex_get_x_token(); + } + if (tracing_commands_par > 0) { + tex_show_cmd_chr(cur_cmd, cur_chr); + } + tex_aux_big_switch(cur_mode, cur_cmd); + if (lmt_main_control_state.local_level <= at_level) { + lmt_main_control_state.control_state = goto_next_state; + if (tracing_nesting_par > 2) { + /*tex This is a kind of duplicate message, which can be confusing */ + tex_local_control_message("leaving local control due to level change"); + } + break; + } else if (lmt_main_control_state.control_state == goto_return_state) { + if (tracing_nesting_par > 2) { + tex_local_control_message("leaving local control due to triggering"); + } + break; + } + } + if (! obeymode) { + cur_list.mode = old_mode; + } + tex_unsave_full_scanner_status(saved_full_status); +} + +inline int tex_aux_is_iterator_value(halfword tokeninfo) +{ + if (tokeninfo >= cs_token_flag) { + halfword cs = tokeninfo - cs_token_flag; + return eq_type(cs) == some_item_cmd && eq_value(cs) == last_loop_iterator_code; + } else { + return 0; + } +} + +void tex_begin_local_control(void) +{ + halfword code = cur_chr; + if (tracing_nesting_par > 2) { + tex_local_control_message("entering token scanner via primitive"); + } + switch (code) { + case local_control_list_code: + { + halfword t; + halfword h = tex_scan_toks_normal(0, &t); + halfword r = tex_get_available_token(token_val(end_local_cmd, 0)); + tex_begin_inserted_list(r); + tex_begin_token_list(h, local_text); + break; + } + case local_control_token_code: + { + halfword t = tex_get_token(); /* avoid realloc issues */ + halfword h = get_reference_token(); + halfword r = tex_get_available_token(token_val(end_local_cmd, 0)); + tex_store_new_token(h, t); + tex_begin_inserted_list(r); + tex_begin_token_list(h, local_text); + break; + } + /*tex + For the moment al three are here because they share some code. At some point I might + move the last two to the |convert_cmd| which is more natural spot but this is easier + for debugging. + + The align_state hack was tricky and took me a while to figure out because it only was + an issue with +10K loops (where 10K is this magic state number). + + We support a leading optional equal sign because that can help make robust macros that + get |\the \dimexpr 1pt| etc fed which can lead to \TEX\ seeing one huge number. + */ + case local_control_loop_code: + case expanded_loop_code: + case unexpanded_loop_code: + { + halfword tail; + halfword first = tex_scan_int(1, NULL); + halfword last = tex_scan_int(1, NULL); + halfword step = tex_scan_int(1, NULL); + halfword head = tex_scan_toks_normal(0, &tail); + if (token_link(head) && step) { + int savedloop = lmt_main_control_state.loop_iterator; + int savedquit = lmt_main_control_state.quit_loop; + ++lmt_main_control_state.loop_nesting; + switch (code) { + case local_control_loop_code: + { + /*tex: + Appending to tail gives issues at the outer level, for instance + |\dorecurse {3} {\startTEXpage \stopTEXpage}| without |\starttext + \stoptext| wrapping. So, no: + */ + /* tex_store_new_token(tail, token_val(end_local_cmd, 0)); */ + for (halfword i = first; step > 0 ? i <= last : i >= last; i += step) { + lmt_main_control_state.loop_iterator = i; + lmt_main_control_state.quit_loop = 0; + /*tex But this, so that we get a proper |\end message|: */ + tex_begin_inserted_list(tex_get_available_token(token_val(end_local_cmd, 0))); + /*tex ... maybe we need to enforce a level > 0 instead. */ + tex_begin_token_list(head, local_loop_text); + tex_local_control(1); + /*tex We need to avoid build-up. */ + tex_cleanup_input_state(); + if (lmt_main_control_state.quit_loop) { + break; + } + } + tex_flush_token_list(head); + break; + } + case expanded_loop_code: + { + halfword h = null; + halfword t = null; + full_scanner_status saved_full_status = tex_save_full_scanner_status(); + strnumber u = tex_save_cur_string(); + tex_store_new_token(tail, right_brace_token + '}'); + for (halfword i = first; step > 0 ? i <= last : i >= last; i += step) { + halfword lt = null; + halfword lh = null; + ++lmt_input_state.align_state; + lmt_main_control_state.loop_iterator = i; + tex_begin_token_list(head, loop_text); /* ref counted */ + lh = tex_scan_toks_expand(1, <, 0); + if (token_link(lh)) { + if (h) { + token_link(t) = token_link(lh); + } else { + h = token_link(lh); + } + t = lt; + } + tex_put_available_token(lh); + tex_cleanup_input_state(); + if (lmt_main_control_state.quit_loop) { + break; + } + } + tex_unsave_full_scanner_status(saved_full_status); + tex_restore_cur_string(u); + tex_flush_token_list(head); + tex_begin_inserted_list(h); + break; + } + case unexpanded_loop_code: + { + /* + A |\currentloopiterator| will not adapt itself in this kind of + loop so we can as well replace it by the current one value which + is what we do here. There is some overhead but I can live with + that. + */ + + halfword h = token_link(head); + halfword tt = null; + halfword t = h; + halfword b = 0; /* we can count and then break out */ + while (token_link(t)) { + t = token_link(t); + if (! b && tex_aux_is_iterator_value(token_info(t))) { + b = 1; + } + } + tt = t; + for (halfword i = first + step; step > 0 ? i <= last : i >= last; i += step) { + halfword hh = h; + while (1) { + t = tex_store_new_token(t, token_info(hh)); + if (b && tex_aux_is_iterator_value(token_info(t))) { + halfword v = (i < min_iterator_value) ? min_iterator_value : (i > max_iterator_value ? max_iterator_value : i); + token_info(t) = token_val(iterator_value_cmd, v < 0 ? 0x100000 - v : v); + } + if (hh == tt) { + break; + } else { + hh = token_link(hh); + } + } + } + if (b) { + halfword hh = h; + while (1) { + if (tex_aux_is_iterator_value(token_info(hh))) { + halfword v = (first < min_iterator_value) ? min_iterator_value : (first > max_iterator_value ? max_iterator_value : first); + token_info(hh) = token_val(iterator_value_cmd, v < 0 ? 0x100000 - v : v); + } + if (hh == tt) { + break; + } else { + hh = token_link(hh); + } + } + } + tex_put_available_token(head); + tex_begin_inserted_list(h); + break; + } + } + --lmt_main_control_state.loop_nesting; + lmt_main_control_state.quit_loop = savedquit; + lmt_main_control_state.loop_iterator = savedloop; + return; + } else { + tex_flush_token_list(head); + } + return; + } + } + tex_local_control(1); /*tex In this case nicer than 0. */ + // tex_cleanup_input_state(); /*tex Yes or no? */ +} + +void tex_end_local_control(void ) +{ + if (lmt_main_control_state.local_level > 0) { + lmt_main_control_state.local_level -= 1; + } else { + tex_local_control_message("redundant end local control"); + } +} + +/*tex + + We need to go back to the main loop. This is rather nasty and dirty and counterintuive code and + there might be a cleaner way. Basically we trigger the main control state from here. + + \starttyping + 0 0 \directlua{token.scan_box()}\hbox{!} + -1 0 \setbox0\hbox{x}\directlua{token.scan_box()}\box0 + 1 1 \toks0={\directlua{token.scan_box()}\hbox{x}}\directlua{tex.runtoks(0)} + 0 0 1 1 \directlua{tex.box[0]=token.scan_box()}\hbox{x\directlua{node.write(token.scan_box())}\hbox{x}} + 0 0 0 1 \setbox0\hbox{x}\directlua{tex.box[0]=token.scan_box()}\hbox{x\directlua{node.write(token.scan_box())}\box0} + \stoptyping + + It's rather fragile code so we added some tracing options. + +*/ + +halfword tex_local_scan_box(void) +{ + int old_mode = cur_list.mode; + int old_level = lmt_main_control_state.local_level; + cur_list.mode = -hmode; + tex_aux_scan_box(lua_scan_flag, 0, null_flag); + if (lmt_main_control_state.local_level == old_level) { + /*tex |\directlua{print(token.scan_list())}\hbox{!}| (n n) */ + if (tracing_nesting_par > 2) { + tex_local_control_message("entering at end of box scanning"); + } + tex_local_control(1); + } else { + /*tex |\directlua{print(token.scan_list())}\box0| (n-1 n) */ + /* + if (tracing_nesting_par > 2) { + local_control_message("setting level after box scanning"); + } + */ + lmt_main_control_state.local_level = old_level; + } + cur_list.mode = old_mode; + return cur_box; +} + +/*tex + + We have an issue with modes when we quit here because we're coming from and still staying at + the \LUA\ end. So, unless we're already nested, we trigger an end_local_level token (an + extension code). + +*/ + +static void tex_aux_wrapup_local_scan_box(void) +{ + /* + if (tracing_nesting_par > 2) { + local_control_message("leaving box scanner"); + } + */ + lmt_main_control_state.local_level -= 1; +} + +static void tex_aux_run_insert_dollar_sign(void) +{ + tex_back_input(cur_tok); + cur_tok = math_shift_token + '$'; + tex_handle_error( + insert_error_type, + "Missing $ inserted", + "I've inserted a begin-math/end-math symbol since I think you left one out.\n" + "Proceed, with fingers crossed." + ); +} + +/*tex + + The |you_cant| procedure prints a line saying that the current command is illegal in the current + mode; it identifies these things symbolically. + +*/ + +void tex_you_cant_error(const char *helpinfo) +{ + tex_handle_error( + normal_error_type, + "You can't use '%C' in %M", cur_cmd, cur_chr, cur_list.mode, + helpinfo + ); +} + +/*tex + + When erroneous situations arise, \TEX\ usually issues an error message specific to the particular + error. For example, |\noalign| should not appear in any mode, since it is recognized by the + |align_peek| routine in all of its legitimate appearances; a special error message is given when + |\noalign| occurs elsewhere. But sometimes the most appropriate error message is simply that the + user is not allowed to do what he or she has attempted. For example, |\moveleft| is allowed only + in vertical mode, and |\lower| only in non-vertical modes. + +*/ + +static void tex_aux_run_illegal_case(void) +{ + tex_you_cant_error( + "Sorry, but I'm not programmed to handle this case;\n" + "I'll just pretend that you didn''t ask for it.\n" + "If you're in the wrong mode, you might be able to\n" + "return to the right one by typing 'I}' or 'I$' or 'I\\par'." + ); +} + +/*tex + + Some operations are allowed only in privileged modes, i.e., in cases that |mode > 0|. The + |privileged| function is used to detect violations of this rule; it issues an error message and + returns |false| if the current |mode| is negative. + +*/ + +int tex_in_privileged_mode(void) +{ + if (cur_list.mode > nomode) { + return 1; + } else { + tex_aux_run_illegal_case(); + return 0; + } +} + +/*tex + + We don't want to leave |main_control| immediately when a |stop| command is sensed, because it + may be necessary to invoke an |\output| routine several times before things really grind to a + halt. (The output routine might even say |\gdef \end {...}|, to prolong the life of the job.) + Therefore |its_all_over| is |true| only when the current page and contribution list are empty, + and when the last output was not a \quote {dead cycle}. We do this when |\end| or |\dump| + occurs. This |stop| is a special case as we want |main_control| to return to its caller if there + is nothing left to do. + +*/ + +static void tex_aux_run_end_job(void) { + if (tex_in_privileged_mode()) { + if ((page_head == lmt_page_builder_state.page_tail) + && (cur_list.head == cur_list.tail) + && (lmt_page_builder_state.dead_cycles == 0)) { + /*tex this is the only way out */ + lmt_main_control_state.control_state = goto_return_state; + } else { + /*tex we will try to end again after ejecting residual material */ + tex_back_input(cur_tok); + tex_tail_append(tex_new_null_box_node(hlist_node, unknown_list)); + box_width(cur_list.tail) = hsize_par; + tex_tail_append(tex_new_glue_node(fill_glue, user_skip_glue)); /* todo: subtype, final_skip_glue? */ + tex_tail_append(tex_new_penalty_node(-010000000000, final_penalty_subtype)); /* -0x40000000 */ + lmt_page_filter_callback(end_page_context, 0); + /*tex append |\hbox to \hsize{}\vfill\penalty-'10000000000| */ + tex_build_page(); + } + } +} + +/*tex + + The |hskip| and |vskip| command codes are used for control sequences like |\hss| and |\vfil| as + well as for |\hskip| and |\vskip|. The difference is in the value of |cur_chr|. + + All the work relating to glue creation has been relegated to the following subroutine. It does + not call |build_page|, because it is used in at least one place where that would be a mistake. + +*/ + +static void tex_aux_run_glue(void) +{ + switch (cur_chr) { + case fil_code: + tex_tail_append(tex_new_glue_node(fil_glue, user_skip_glue)); + break; + case fill_code: + tex_tail_append(tex_new_glue_node(fill_glue, user_skip_glue)); + break; + case filll_code: /*tex aka |ss_code| */ + tex_tail_append(tex_new_glue_node(filll_glue, user_skip_glue)); + break; + case fil_neg_code: + tex_tail_append(tex_new_glue_node(fil_neg_glue, user_skip_glue)); + break; + case skip_code: + { + halfword v = tex_scan_glue(glue_val_level, 0); + halfword g = tex_new_glue_node(v, user_skip_glue); + /* glue_data(g) = glue_data_par; */ + tex_tail_append(g); + tex_flush_node(v); + break; + } + default: + break; + } +} + +static void tex_aux_run_mglue(void) +{ + switch (cur_chr) { + case normal_mskip_code: + { + halfword v = tex_scan_glue(mu_val_level, 0); + tex_tail_append(tex_new_glue_node(v, mu_glue)); + tex_flush_node(v); + break; + } + case atom_mskip_code: + { + halfword left = tex_scan_math_class_number(0); + halfword right = tex_scan_math_class_number(0); + halfword style = tex_scan_math_style_identifier(0, 0); + halfword node = tex_math_spacing_glue(left, right, style); + if (node) { + tex_tail_append(node); + } else { + /*tex This could be an option: */ + tex_tail_append(tex_new_glue_node(zero_glue, mu_glue)); + } + break; + } + } +} + +/*tex + + We have to deal with errors in which braces and such things are not properly nested. Sometimes + the user makes an error of commission by inserting an extra symbol, but sometimes the user makes + an error of omission. \TEX\ can't always tell one from the other, so it makes a guess and tries + to avoid getting into a loop. + + The |off_save| routine is called when the current group code is wrong. It tries to insert + something into the user's input that will help clean off the top level. + +*/ + +void tex_off_save(void) +{ + if (cur_group == bottom_level_group) { + /*tex Drop current token and complain that it was unmatched */ + tex_handle_error(normal_error_type, "Extra %C", cur_cmd, cur_chr, + "Things are pretty mixed up, but I think the worst is over." + ); + } else { + const char * helpinfo = + "I've inserted something that you may have forgotten. (See the <inserted text>\n" + "above.) With luck, this will get me unwedged."; + halfword h = tex_get_available_token(null); + tex_back_input(cur_tok); + /*tex + Prepare to insert a token that matches |cur_group|, and print what it is. At this point, + |link (temp_token_head) = p|, a pointer to an empty one-word node. + */ + switch (cur_group) { + case also_simple_group: + case semi_simple_group: + case math_simple_group: + { + set_token_info(h, deep_frozen_end_group_token); + tex_handle_error( + normal_error_type, + "Missing \\endgroup inserted", + helpinfo + ); + break; + } + case math_shift_group: + { + set_token_info(h, math_shift_token + '$'); + tex_handle_error( + normal_error_type, + "Missing $ inserted", + helpinfo + ); + break; + } + case math_fence_group: + { + /* maybe nicer is just a zero delimiter one */ + halfword q = tex_get_available_token(period_token); + halfword f = node_next(cur_list.head); + set_token_info(h, deep_frozen_right_token); + set_token_link(h, q); + if (! (f && node_type(f) == fence_noad && has_noad_option_nocheck(f))) { + tex_handle_error( + normal_error_type, + "Missing \\right. inserted", + helpinfo + ); + } + break; + } + default: + { + set_token_info(h, right_brace_token + '}'); + tex_handle_error( + normal_error_type, + "Missing } inserted", + helpinfo + ); + break; + } + } + tex_begin_inserted_list(h); + } +} + +/*tex + + Discretionary nodes are easy in the common case |\-|, but in the general case we must process + three braces full of items. + + The space factor does not change when we append a discretionary node, but it starts out as 1000 + in the subsidiary lists. + +*/ + +static void tex_aux_run_discretionary(void) +{ + switch (cur_chr) { + case normal_discretionary_code: + /*tex |\discretionary| */ + { + halfword d = tex_new_disc_node(normal_discretionary_code); + tex_tail_append(d); + while (1) { + switch (tex_scan_character("pocPOC", 0, 1, 0)) { + case 0: + goto DONE; + case 'p': case 'P': + switch (tex_scan_character("eorEOR", 0, 0, 0)) { + case 'e': case 'E': + if (tex_scan_mandate_keyword("penalty", 2)) { + set_disc_penalty(d, tex_scan_int(0, NULL)); + } + break; + case 'o': case 'O': + if (tex_scan_mandate_keyword("postword", 2)) { + set_disc_option(d, disc_option_post_word); + } + break; + case 'r': case 'R': + if (tex_scan_mandate_keyword("preword", 2)) { + set_disc_option(d, disc_option_pre_word); + } + break; + default: + tex_aux_show_keyword_error("penalty|postword|preword"); + goto DONE; + } + break; + case 'o': case 'O': + if (tex_scan_mandate_keyword("options", 1)) { + set_disc_options(d, tex_scan_int(0, NULL)); + } + break; + case 'c': case 'C': + if (tex_scan_mandate_keyword("class", 1)) { + set_disc_class(d, tex_scan_math_class_number(0)); + } + break; + default: + goto DONE; + } + } + DONE: + tex_set_saved_record(saved_discretionary_item_component, saved_discretionary_count, 0, 0); + lmt_save_state.save_stack_data.ptr += saved_discretionary_n_of_items; + tex_new_save_level(discretionary_group); + tex_scan_left_brace(); + tex_push_nest(); + cur_list.mode = -hmode; + cur_list.space_factor = default_space_factor; /* hm, quite hard coded */ + } + break; + case explicit_discretionary_code: + /*tex |\-| */ + if (hyphenation_permitted(hyphenation_mode_par, explicit_hyphenation_mode)) { + halfword d = tex_new_disc_node(explicit_discretionary_code); + tex_tail_append(d); + int c = tex_get_pre_hyphen_char(cur_lang_par); + if (c > 0) { + tex_set_disc_field(d, pre_break_code, tex_new_char_node(glyph_unset_subtype, cur_font_par, c, 1)); + } + c = tex_get_post_hyphen_char(cur_lang_par); + if (c > 0) { + tex_set_disc_field(d, post_break_code, tex_new_char_node(glyph_unset_subtype, cur_font_par, c, 1)); + } + disc_penalty(d) = tex_explicit_disc_penalty(hyphenation_mode_par); + } + break; + case automatic_discretionary_code: + case mathematics_discretionary_code: + /*tex |-| */ + if (hyphenation_permitted(hyphenation_mode_par, automatic_hyphenation_mode)) { + halfword d = tex_new_disc_node(automatic_discretionary_code); + tex_tail_append(d); + /*tex As done in hyphenator: */ + halfword c = tex_get_pre_exhyphen_char(cur_lang_par); + if (c <= 0) { + c = ex_hyphen_char_par; + } + if (c > 0) { + tex_set_disc_field(d, pre_break_code, tex_new_char_node(glyph_unset_subtype, cur_font_par, c, 1)); + } + c = tex_get_post_exhyphen_char(cur_lang_par); + if (c > 0) { + tex_set_disc_field(d, post_break_code, tex_new_char_node(glyph_unset_subtype, cur_font_par, c, 1)); + } + c = ex_hyphen_char_par; + if (c > 0) { + tex_set_disc_field(d, no_break_code, tex_new_char_node(glyph_unset_subtype, cur_font_par, c, 1)); + } + disc_penalty(d) = tex_automatic_disc_penalty(hyphenation_mode_par); + } else { + halfword c = ex_hyphen_char_par; + if (c > 0) { + c = tex_new_char_node(glyph_unset_subtype, cur_font_par, c, 1); + set_glyph_discpart(c, glyph_discpart_always); + tex_tail_append(c); + } + } + break; + } +} + +/*tex + + The three discretionary lists are constructed somewhat as if they were hboxes. A subroutine + called |finish_discretionary| handles the transitions. (This is sort of fun.) + +*/ + +static void tex_aux_finish_discretionary(void) +{ + halfword p, q, d; /* for link manipulation */ + int n = 0; /* length of discretionary list */ + tex_unsave(); + /*tex + Prune the current list, if necessary, until it contains only |char_node|, |kern_node|, + |hlist_node|, |vlist_node| and |rule_node| items; set |n| to the length of the list, and + set |q| to the lists tail. During this loop, |p = node_next(q)| and there are |n| items + preceding |p|. + */ + q = cur_list.head; + p = node_next(q); + while (p) { + switch (node_type(p)) { + case glyph_node: + case hlist_node: + case vlist_node: + case rule_node: + case kern_node: + break; + case glue_node: + if (hyphenation_permitted(hyphenation_mode_par, permit_glue_hyphenation_mode)) { + if (glue_stretch_order(p)) { + glue_stretch(p) = 0; + glue_stretch_order(p) = 0; + } + if (glue_shrink_order(p)) { + glue_shrink(p) = 0; + glue_shrink_order(p) = 0; + } + break; + } else { + // fall through + } + default: + if (hyphenation_permitted(hyphenation_mode_par, permit_all_hyphenation_mode)) { + break; + } else { + tex_handle_error( + normal_error_type, + "Improper discretionary list", + "Discretionary lists must contain only glyphs, boxes, rules and kerns." + ); + tex_begin_diagnostic(); + tex_print_str("The following discretionary sublist has been deleted:"); + tex_print_levels(); + tex_show_box(p); + tex_end_diagnostic(); + tex_flush_node_list(p); + node_next(q) = null; + goto DONE; + } + } + node_prev(p) = q; + q = p; + p = node_next(q); + ++n; + } + DONE: + p = node_next(cur_list.head); + tex_pop_nest(); + d = cur_list.tail; + if (saved_type(saved_discretionary_item_component - saved_discretionary_n_of_items) == saved_discretionary_count) { + switch (saved_value(saved_discretionary_item_component - saved_discretionary_n_of_items)) { + case 0: + if (n > 0) { + tex_set_disc_field(d, pre_break_code, p); + } + break; + case 1: + if (n > 0) { + tex_set_disc_field(d, post_break_code, p); + } + break; + case 2: + /*tex + Attach list |p| to the current list, and record its length; then finish up and + |return|. + */ + if (n > 0) { + if (cur_mode == mmode && ! hyphenation_permitted(hyphenation_mode_par, permit_math_replace_hyphenation_mode)) { + tex_handle_error( + normal_error_type, + "Illegal math \\discretionary", + "Sorry: The third part of a discretionary break must be empty, in math formulas. I\n" + "had to delete your third part." + ); + tex_flush_node_list(p); + } else { + tex_set_disc_field(d, no_break_code, p); + } + } + if (! hyphenation_permitted(hyphenation_mode_par, normal_hyphenation_mode)) { + halfword n = disc_no_break_head(d); + cur_list.tail = node_prev(cur_list.tail); + node_next(cur_list.tail) = null; + if (n) { + tex_tail_append(n); + cur_list.tail = disc_no_break_tail(d); + tex_set_disc_field(d, no_break_code, null); + tex_set_discpart(d, n, disc_no_break_tail(d), glyph_discpart_replace); + } + tex_flush_node(d); + } else if (cur_mode == mmode && disc_class(d) != unset_disc_class) { + halfword n = null; + cur_list.tail = node_prev(d); + node_prev(d) = null; + node_next(d) = null; + n = tex_math_make_disc(d); + tex_tail_append(n); + } + /*tex There are no other cases. */ + lmt_save_state.save_stack_data.ptr -= saved_discretionary_n_of_items; + return; + default: + break; + } + tex_set_saved_record(saved_discretionary_item_component - saved_discretionary_n_of_items, saved_discretionary_count, 0, saved_value(saved_discretionary_item_component - saved_discretionary_n_of_items) + 1); + tex_new_save_level(discretionary_group); + tex_scan_left_brace(); + tex_push_nest(); + cur_list.mode = -hmode; + cur_list.space_factor = default_space_factor; + } else { + tex_confusion("finish discretionary"); + } +} + +/*tex + + The routine for a |right_brace| character branches into many subcases, since a variety of things + may happen, depending on |cur_group|. Some types of groups are not supposed to be ended by a + right brace; error messages are given in hopes of pinpointing the problem. Most branches of this + routine will be filled in later, when we are ready to understand them; meanwhile, we must prepare + ourselves to deal with such errors. + + When the right brace occurs at the end of an |\hbox| or |\vbox| or |\vtop| construction, the + |package| routine comes into action. We might also have to finish a paragraph that hasn't ended. +*/ + +static void tex_aux_extra_right_brace_error(void) +{ + const char * helpinfo = + "I've deleted a group-closing symbol because it seems to be spurious, as in\n" + "'$x}$'. But perhaps the } is legitimate and you forgot something else, as in\n" + "'\\hbox{$x}'."; + switch (cur_group) { + case also_simple_group: + case semi_simple_group: + tex_handle_error( + normal_error_type, + "Extra }, or forgotten %eendgroup", + helpinfo + ); + break; + case math_simple_group: + tex_handle_error( + normal_error_type, + "Extra }, or forgotten %eendmathgroup", + helpinfo + ); + break; + case math_shift_group: + tex_handle_error( + normal_error_type, + "Extra }, or forgotten $", + helpinfo + ); + break; + case math_fence_group: + tex_handle_error( + normal_error_type, + "Extra }, or forgotten %eright", + helpinfo + ); + break; + } + ++lmt_input_state.align_state; +} + +inline static void tex_aux_finish_hbox(void) +{ + tex_aux_fixup_directions_only(); + tex_package(hpack_code); +} + +inline static void tex_aux_finish_adjusted_hbox(void) +{ + lmt_packaging_state.post_adjust_tail = post_adjust_head; + lmt_packaging_state.pre_adjust_tail = pre_adjust_head; + lmt_packaging_state.post_migrate_tail = post_migrate_head; + lmt_packaging_state.pre_migrate_tail = pre_migrate_head; + tex_package(hpack_code); +} + +inline static void tex_aux_finish_vbox(void) +{ + if (! tex_wrapped_up_paragraph(vbox_par_context)) { + tex_end_paragraph(vbox_group, vbox_par_context); + tex_package(vpack_code); + } +} + +inline static void tex_aux_finish_vtop(void) +{ + if (! tex_wrapped_up_paragraph(vtop_par_context)) { + tex_end_paragraph(vtop_group, vtop_par_context); + tex_package(vtop_code); + } +} + +inline static void tex_aux_finish_simple_group(void) +{ + tex_aux_fixup_directions_and_unsave(); +} + +static void tex_aux_finish_bottom_level_group(void) +{ + tex_handle_error( + normal_error_type, + "Too many }'s", + "You've closed more groups than you opened. Such booboos are generally harmless,\n" + "so keep going." + ); +} + +inline static void tex_aux_finish_output(void) +{ + tex_pop_text_dir_ptr(); + tex_resume_after_output(); +} + +static void tex_aux_run_right_brace(void) +{ + switch (cur_group) { + case bottom_level_group: + tex_aux_finish_bottom_level_group(); + break; + case simple_group: + tex_aux_finish_simple_group(); + break; + case hbox_group: + tex_aux_finish_hbox(); + break; + case adjusted_hbox_group: + tex_aux_finish_adjusted_hbox(); + break; + case vbox_group: + tex_aux_finish_vbox(); + break; + case vtop_group: + tex_aux_finish_vtop(); + break; + case align_group: + tex_finish_alignment_group(); + break; + case no_align_group: + tex_finish_no_alignment_group(); + break; + case output_group: + tex_aux_finish_output(); + break; + case math_group: + tex_finish_math_group(); + break; + case discretionary_group: + tex_aux_finish_discretionary(); + break; + case insert_group: + tex_finish_insert_group(); + break; + case vadjust_group: + tex_finish_vadjust_group(); + break; + case vcenter_group: + tex_finish_vcenter_group(); + break; + case math_fraction_group: + tex_finish_math_fraction(); + break; + case math_operator_group: + tex_finish_math_operator(); + break; + case math_choice_group: + tex_finish_math_choice(); + break; + case also_simple_group: + case math_simple_group: + // cur_group = semi_simple_group; /* probably not needed */ + tex_aux_run_end_group(); + break; + case semi_simple_group: + case math_shift_group: + case math_fence_group: /*tex See above, let's see when we are supposed to end up here. */ + tex_aux_extra_right_brace_error(); + break; + case local_box_group: + tex_aux_finish_local_box(); + break; + default: + tex_confusion("right brace"); + break; + } +} + +/*tex + + Here is where we clear the parameters that are supposed to revert to their default values after + every paragraph and when internal vertical mode is entered. + +*/ + +void tex_normal_paragraph(int context) +{ + int ignore = 0; + lmt_main_control_state.last_par_context = context; + lmt_paragraph_context_callback(context, &ignore); + if (! ignore) { + if (looseness_par) { + update_tex_looseness(0); + } + if (hang_indent_par) { + update_tex_hang_indent(0); + } + if (hang_after_par != 1) { + update_tex_hang_after(1); + } + if (par_shape_par) { + update_tex_par_shape(null); + } + if (inter_line_penalties_par) { + update_tex_inter_line_penalties(null); + } + } +} + +/*tex + + The global variable |cur_box| will point to a newly-made box. If the box is void, we will have + |cur_box = null|. Otherwise we will have |type(cur_box) = hlist_node| or |vlist_node| or + |rule_node|; the |rule_node| case can occur only with leaders. + + The |box_end| procedure does the right thing with |boxnode|, if |boxcontext| represents the + context as explained above. The |boxnode| variable is either a list node or a register index. + In some cases we communicate via a state variable. + +*/ + +static void tex_aux_wrapup_leader_box(halfword boxcontext, halfword boxnode) +{ + /*tex Append a new leader node that uses |box| and get the next non-blank non-relax. */ + do { + tex_get_x_token(); + } while (cur_cmd == spacer_cmd || cur_cmd == relax_cmd); + if ((cur_cmd == hskip_cmd && cur_mode != vmode) || (cur_cmd == vskip_cmd && cur_mode == vmode)) { + tex_aux_run_glue(); /* uses cur_chr */ + switch (boxcontext) { + case a_leaders_flag: + node_subtype(cur_list.tail) = a_leaders; + break; + case c_leaders_flag: + node_subtype(cur_list.tail) = c_leaders; + break; + case x_leaders_flag: + node_subtype(cur_list.tail) = x_leaders; + break; + case g_leaders_flag: + node_subtype(cur_list.tail) = g_leaders; + break; + case u_leaders_flag: + switch (node_type(boxnode)) { + case hlist_node: + if (cur_mode != vmode) { + node_subtype(cur_list.tail) = u_leaders; + glue_amount(cur_list.tail) += box_width(boxnode); + } else { + node_subtype(cur_list.tail) = a_leaders; + } + break; + case vlist_node: + if (cur_mode == vmode) { + node_subtype(cur_list.tail) = u_leaders; + glue_amount(cur_list.tail) += box_total(boxnode); + } else { + node_subtype(cur_list.tail) = a_leaders; + } + break; + default: + /* yet unsupported */ + node_subtype(cur_list.tail) = a_leaders; + break; + } + break; + } + glue_leader_ptr(cur_list.tail) = boxnode; + } else { + tex_handle_error( + back_error_type, + "Leaders not followed by proper glue", + "You should say '\\leaders <box or rule><hskip or vskip>'. I found the <box or\n" + "rule>, but there's no suitable <hskip or vskip>, so I'm ignoring these leaders." + ); + tex_flush_node_list(boxnode); + } +} + +void tex_box_end(int boxcontext, halfword boxnode, scaled shift, halfword mainclass) +{ + cur_box = boxnode; + if (boxcontext < box_flag) { + /*tex + + Append box |boxnode| to the current list, shifted by |boxcontext|. The global variable + |adjust_tail| will be non-null if and only if the current box might include adjustments + that should be appended to the current vertical list. + + Having shift in the box context is kind of strange but as long as we stay below maxdimen + it works. + + We now pass the shift directly, so no boxcontext trick here. + + */ + + if (boxnode) { + // box_shift_amount(boxnode) = boxcontext; + if (shift != null_flag) { + box_shift_amount(boxnode) = shift; + } + switch (cur_mode) { + case vmode: + if (lmt_packaging_state.pre_adjust_tail) { + if (pre_adjust_head != lmt_packaging_state.pre_adjust_tail) { + tex_inject_adjust_list(pre_adjust_head, 1, boxnode, NULL); + } + lmt_packaging_state.pre_adjust_tail = null; + } + if (lmt_packaging_state.pre_migrate_tail) { + if (pre_migrate_head != lmt_packaging_state.pre_migrate_tail) { + tex_append_list(pre_migrate_head, lmt_packaging_state.pre_migrate_tail); + } + lmt_packaging_state.pre_migrate_tail = null; + } + tex_append_to_vlist(boxnode, lua_key_index(box), NULL); + if (lmt_packaging_state.post_migrate_tail) { + if (post_migrate_head != lmt_packaging_state.post_migrate_tail) { + tex_append_list(post_migrate_head, lmt_packaging_state.post_migrate_tail); + } + lmt_packaging_state.post_migrate_tail = null; + } + if (lmt_packaging_state.post_adjust_tail) { + if (post_adjust_head != lmt_packaging_state.post_adjust_tail) { + tex_inject_adjust_list(post_adjust_head, 1, null, NULL); + } + lmt_packaging_state.post_adjust_tail = null; + } + if (cur_list.mode > nomode) { + if (! lmt_page_builder_state.output_active) { + lmt_page_filter_callback(box_page_context, 0); + } + tex_build_page(); + } + break; + case hmode: + cur_list.space_factor = default_space_factor; + tex_couple_nodes(cur_list.tail, boxnode); + cur_list.tail = boxnode; + break; + /* case mmode: */ + default: + boxnode = tex_new_sub_box(boxnode); + tex_couple_nodes(cur_list.tail, boxnode); + cur_list.tail = boxnode; + if (mainclass != unset_noad_class) { + set_noad_classes(boxnode, mainclass); + } + break; + } + } else { + /* just scanning */ + } + } else if (boxcontext < global_box_flag) { + /*tex Store |box| in a local box register */ + update_tex_box_local(boxcontext, boxnode); + } else if (boxcontext <= max_global_box_flag) { + /*tex Store |box| in a global box register */ + update_tex_box_global(boxcontext, boxnode); + } else { + switch (boxcontext) { + case shipout_flag: + /*tex This normally can't happen as some backend code needs to kick in. */ + if (boxnode) { + /*tex We just show the box ... */ + tex_begin_diagnostic(); + tex_show_node_list(boxnode, max_integer, max_integer); + tex_end_diagnostic(); + /*tex ... and wipe it when it's a register ... */ + if (box_register(boxnode)) { + tex_flush_node_list(boxnode); + box_register(boxnode) = null; + } + /*tex ... so there is at least an indication that we flushed. */ + } + break; + case left_box_flag: + case right_box_flag: + case middle_box_flag: + /*tex Actualy, this cannot happen ... will go away. */ + tex_aux_finish_local_box(); + break; + case lua_scan_flag: + /*tex We are done with scanning so let's return to the caller. */ + tex_aux_wrapup_local_scan_box(); + cur_box = boxnode; + break; + case a_leaders_flag: + case c_leaders_flag: + case x_leaders_flag: + case g_leaders_flag: + case u_leaders_flag: + tex_aux_wrapup_leader_box(boxcontext, boxnode); + break; + default: + /* fatal error */ + break; + } + } +} + +/*tex + + The canonical \TEX\ engine(s) inject an indentation box, so there is always something at the beginning that + also acts as a boundary. However, when snapshotting was introduced it made also sense to turn the parindent + related hlist into a glue. We might need to adapt the parbuilder but it looks liek that is not needed. Of + course, an |\unskip| will now also unskip the parindent but there are ways to prevent this. I'll test it for + a while, which is why we have a way to enable it. The glue is {\em always} injected, also when it's zero. + +*/ + +void tex_begin_paragraph(int doindent, int context) +{ + halfword q; + int indented = doindent; + int isvmode = cur_list.mode == vmode; + if (isvmode || cur_list.head != cur_list.tail) { + /*tex + Actually we could remove the callback and hook it into the |\everybeforepar| but that one + started out as a |tex.expandmacro| itself and we don't want the callback overhead every + time, so now we have both. However, in the end I decided to do this one {\em before} the + parskip is injected. + */ + if (every_before_par_par) { + tex_begin_inserted_list(tex_get_available_token(token_val(end_local_cmd, 0))); + tex_begin_token_list(every_before_par_par, every_before_par_text); + if (tracing_nesting_par > 2) { + tex_local_control_message("entering local control via \\everybeforepar"); + } + tex_local_control(1); + } + // if (type(cur_list.tail) == glue_node && subtype(cur_list.tail) == par_skip_glue) { + // /* ignore */ + // } else { + tex_tail_append(tex_new_param_glue_node(par_skip_code, par_skip_glue)); + // } + } + lmt_begin_paragraph_callback(isvmode, &indented, context); + /*tex We'd better not messed up things in the callback! */ + cur_list.prev_graf = 0; + tex_push_nest(); + cur_list.mode = hmode; + cur_list.space_factor = default_space_factor; + /*tex Add local paragraph node */ + tex_tail_append(tex_new_par_node(vmode_par_par_subtype)); + // if (end_of_par_par) { + // update_tex_end_of_par(null); /* option */ + // } + q = cur_list.tail; + /*tex We will move this to after the dir nodes have been dealt with. */ + tex_aux_insert_parindent(indented); + /*tex Dir nodes end up before the indent box. */ + { + halfword dir_rover = lmt_dir_state.text_dir_ptr; + while (dir_rover) { + if ((node_next(dir_rover)) || (dir_direction(dir_rover) != par_direction_par)) { + halfword dir_graf_tmp = tex_new_dir(normal_dir_subtype, dir_direction(dir_rover)); + tex_try_couple_nodes(dir_graf_tmp, node_next(q)); + tex_couple_nodes(q, dir_graf_tmp); + } + dir_rover = node_next(dir_rover); + } + } + /*tex We might need to go to the last injected dir and/or indent node. */ + while (node_next(q)) { + q = node_next(q); + } + cur_list.tail = q; + /*tex The |\everypar| tokens are injected after dir nodes have been added. */ + if (every_par_par) { + tex_begin_token_list(every_par_par, every_par_text); + } + if (lmt_nest_state.nest_data.ptr == 1) { + if (! lmt_page_builder_state.output_active) { + lmt_page_filter_callback(begin_paragraph_page_context, 0); + } + /*tex put |par_skip| glue on current page */ + tex_build_page(); + } +} + +void tex_insert_paragraph_token(void) +{ + if (auto_paragraph_mode_par > 0) { + cur_tok = token_val(end_paragraph_cmd, inserted_end_paragraph_code); + // cur_tok = token_val(end_paragraph_cmd, normal_end_paragraph_code); + // cur_cs = null; + } else { + cur_tok = lmt_token_state.par_token; + } +} + +static void tex_aux_run_head_for_vmode(void) +{ + if (cur_list.mode >= nomode) { + tex_back_input(cur_tok); + /*tex + We could have a callback here but on the other hand, we really need to be in vmode + afterwards! Also, a macro package can just test for the mode at that spot which is + less hassle than making a callback identify what is needed. A return value would + indicate to not inject a par when we're in vmode and only very dirty \LUA\ code can + change modes here by messing with the list so far. So, unless I find a real use case + we just continue. + */ + tex_insert_paragraph_token(); + tex_back_input(cur_tok); + lmt_input_state.cur_input.token_type = inserted_text; + } else if (cur_cmd != hrule_cmd) { + tex_off_save(); + } else { + tex_handle_error( + normal_error_type, + "You can't use '\\hrule' here except with leaders", + "To put a horizontal rule in an hbox or an alignment, you should use \\leaders or\n" + "\\hrulefill (see The TeXbook)." + ); + } +} + +/*tex + + We don't have |hkern_cmd| and |vkern_cmd| and it makes no sense to introduce them now so instead + of handling modes in the big switch we do it here. Because we need to be compatible we would end + up with three |cmd| codes anyway. The rationale for |\hkern| and |\vkern| is consistency of + primitives, while |\nonzerowidthkern| can make node lists smaller which is nice for \LUA\ based + juggling. + +*/ + +/* +static void tex_aux_run_kern(void) +{ + halfword val = tex_scan_dimen(0, 0, 0, 0, NULL); + tex_tail_append(tex_new_kern_node(val, explicit_kern)); +} +*/ + +static void tex_aux_run_kern(void) +{ + halfword code = cur_chr; + halfword val = tex_scan_dimen(0, 0, 0, 0, NULL); + switch (code) { + case normal_kern_code: + break; + case h_kern_code: + if (cur_mode == mmode) { + break; + } else { + cur_tok = token_val(kern_cmd, normal_kern_code); + tex_aux_run_new_paragraph(); + return; + } + break; + case v_kern_code: + if (cur_mode == mmode) { + break; + } else { + cur_tok = token_val(kern_cmd, normal_kern_code); + tex_aux_run_head_for_vmode(); + return; + } + case non_zero_width_kern_code: + if (val) { + break; + } else { + return; + } + } + tex_tail_append(tex_new_kern_node(val, explicit_kern_subtype)); +} + +static void tex_aux_run_mkern(void) +{ + halfword val = tex_scan_dimen(1, 0, 0, 0, NULL); + tex_tail_append(tex_new_kern_node(val, explicit_math_kern_subtype)); +} + +/*tex + + |cur_list.dirs| would have been set by |line_break| by means of |post_line_break|, but this is + not done right now, as it introduces pretty heavy memory leaks. This means the current code + might be wrong in some way that relates to in-paragraph displays. + +*/ + +static int tex_aux_only_dirs(halfword n) +{ + while (n) { + switch (node_type(n)) { + case par_node: + case dir_node: + n = node_next(n); + break; + /*tex + This can become an option if realy needed but it kind of violates the enforced + hmode, so we stay compatible. But contrary to \LUATEX\ a |\noindent| is seen as + content trigger. + */ + case glue_node: + if (tex_is_par_init_glue(n)) { + n = node_next(n); + break; + } + default: + return 0; + } + } + return 1; +} + +void tex_end_paragraph(int group, int context) +{ + if (cur_list.mode == hmode) { + if (cur_list.head == cur_list.tail) { + /*tex |null| paragraphs are ignored, all contain a |par| node */ + tex_pop_nest(); + } else if (tex_aux_only_dirs(node_next(cur_list.head))) { + tex_flush_node(node_next(cur_list.head)); + /* cur_list.tail = cur_list.head; */ /* probably needed */ + tex_pop_nest(); + // if (cur_list.head == cur_list.tail || node_next(cur_list.head) == cur_list.tail) { + // if (node_next(cur_list.head) == cur_list.tail) { + // tex_flush_node(node_next(cur_list.head)); + // // cur_list.tail = cur_list.head; + // } + // tex_pop_nest(); + } else { + tex_line_break(0, group); + } + if (cur_list.direction_stack) { + tex_flush_node_list(cur_list.direction_stack); + cur_list.direction_stack = null; + } + tex_normal_paragraph(context); + lmt_error_state.error_count = 0; + } +} + +static void tex_aux_run_penalty(void) +{ + halfword value = tex_scan_int(0, NULL); + tex_tail_append(tex_new_penalty_node(value, user_penalty_subtype)); + if (cur_list.mode == vmode) { + if (! lmt_page_builder_state.output_active) { + lmt_page_filter_callback(penalty_page_context, 0); + } + tex_build_page(); + } +} + +/*tex + + When |delete_last| is called, |cur_chr| is the |type| of node that will be deleted, if present. + The |remove_item| command removes a penalty, kern, or glue node if it appears at the tail of + the current list, using a brute-force linear scan. Like |\lastbox|, this command is not allowed + in vertical mode (except internal vertical mode), since the current list in vertical mode is + sent to the page builder. But if we happen to be able to implement it in vertical mode, we do. + +*/ + +static void tex_aux_run_remove_item(void) +{ + halfword code = cur_chr; + halfword head = cur_list.head; + halfword tail = cur_list.tail; + if (cur_list.mode == vmode && tail == head) { + /*tex + Apologize for inability to do the operation now, unless |\unskip| + follows non-glue. It's a bit weird test. + */ + if ((code != skip_item_code) || (lmt_page_builder_state.last_glue != max_halfword)) { + switch (code) { + case kern_item_code: + tex_you_cant_error( + "Sorry...I usually can't take things from the current page.\n" + "Try '\\kern-\\lastkern' instead." + ); + break; + case penalty_item_code: + case boundary_item_code: + tex_you_cant_error( + "Sorry...I usually can't take things from the current page.\n" + "Perhaps you can make the output routine do it." + ); + break; + case skip_item_code: + tex_you_cant_error( + "Sorry...I usually can't take things from the current page.\n" + "Try '\\vskip-\\lastskip' instead." + ); + break; + } + } +// } else if (node_type(tail) != glyph_node) { +// /*tex +// Officially we don't need to check what we remove because it can be only one of +// three, unless one creates one indendently (in \LUA). So, we just do check and +// silently ignore bad code. +// */ +// halfword p; +// switch (code) { +// case kern_item_code : if (node_type(tail) != kern_node ) { return; } else { break; } +// case penalty_item_code : if (node_type(tail) != penalty_node) { return; } else { break; } +// case skip_item_code : if (node_type(tail) != glue_node ) { return; } else { break; } +// } +// /*tex +// There is some magic testing here that makes sure we don't mess up any discretionary +// nodes. But why do we care? +// */ +// do { +// p = head; +// if (p == tail && node_type(head) == disc_node) { +// return; +// } else { +// head = node_next(p); +// } +// } while (head != tail); +// node_next(p) = null; +// tex_flush_node_list(tail); +// cur_list.tail = p; +// } + } else { + /*tex + Officially we don't need to check what we remove because it can be only one of + three, unless one creates one indendently (in \LUA). So, we just do check and + silently ignore bad code. + */ + switch (node_type(tail)) { + case kern_node : + if (code == kern_item_code) { + break; + } else { + return; + } + case penalty_node : + if (code == penalty_item_code) { + break; + } else { + return; + } + case glue_node : + if (code == skip_item_code) { + break; + } else { + return; + } + case boundary_node : + if (node_subtype(tail) == user_boundary && code == boundary_item_code) { + break; + } else { + return; + } + default: + return; + } + { + /*tex + There is some magic testing here that makes sure we don't mess up any discretionary + nodes. But why do we care? + */ + halfword p; + do { + p = head; + if (p == tail && node_type(head) == disc_node) { + return; + } else { + head = node_next(p); + } + } while (head != tail); + node_next(p) = null; + tex_flush_node_list(tail); + cur_list.tail = p; + } + } + +} + +/*tex + + Italic corrections are converted to kern nodes when the |italic_correction| command follows a + character. In math mode the same effect is achieved by appending a kern of zero here, since + italic corrections are supplied later. + +*/ + +static void tex_aux_run_text_italic_correction(void) +{ + halfword tail = cur_list.tail; + if (tail != cur_list.head && node_type(tail) == glyph_node) { + // tex_tail_append(tex_new_kern_node(tex_char_italic_from_font(glyph_font(tail), glyph_character(tail)), italic_kern)); + tex_tail_append(tex_new_kern_node(tex_char_italic_from_glyph(tail), italic_kern_subtype)); /* scaled */ + } +} + +/*tex + + The positioning of accents is straightforward but tedious. Given an accent of width |a|, + designed for characters of height |x| and slant |s|; and given a character of width |w|, + height |h|, and slant |t|: We will shift the accent down by |x - h|, and we will insert kern + nodes that have the effect of centering the accent over the character and shifting the accent + to the right by $\delta = {1 \over 2} (w-a) + h \cdot t - x \cdot s$. If either character is + absent from the font, we will simply use the other, without shifting. + + While much is delegated to builders this is one of the few places where the action happens + directly. Of course, in a \UNICODE\ engine this command is not really relevant but here we + even extended it with optional offsets! + +*/ + +static void tex_aux_run_text_accent(void) +{ + halfword fnt = cur_font_par; + halfword accent = null; + halfword base = null; + scaled xoffset = 0; + scaled yoffset = 0; + while (1) { + switch (tex_scan_character("xyXY", 0, 1, 0)) { + case 'x': case 'X': + if (tex_scan_mandate_keyword("xoffset", 1)) { + xoffset = tex_scan_dimen(0, 0, 0, 0, NULL); + } + break; + case 'y': case 'Y': + if (tex_scan_mandate_keyword("yoffset", 1)) { + yoffset = tex_scan_dimen(0, 0, 0, 0, NULL); + } + break; + default: + goto DONE; + } + } + DONE: + accent = tex_new_char_node(glyph_unset_subtype, fnt, tex_scan_char_number(0), 1); + if (accent) { + /*tex + Create a character node |q| for the next character, but set |q := null| if problems + arise. + */ + scaled x = tex_get_scaled_ex_height(fnt); + double s = (double) (tex_get_font_slant(fnt)) / (double) (65536); + scaled a = tex_glyph_width(accent); + /*tex + Here we had |handle_assignments| which is a bit confusing one so we inlined it, probably + at the cost of some error recovery compatibility, which we don't worry too much about. + It looks like skipping spaces and relax is okay. The (original \TEX\ idea is that one + can change a font in between which is why the |fnt| variable gets set again. Because in + practice switching a font can involve more than assignments wd could be more tolerant + and often wrapping in |\localcontrolled| is more robust then. + */ + /* handle_assignments(); */ + fnt = cur_font_par; + PICKUP: + switch (cur_cmd) { + case spacer_cmd: + case relax_cmd: + tex_get_x_token(); + goto PICKUP; + case letter_cmd: + case other_char_cmd: + case char_given_cmd: + base = tex_new_glyph_node(glyph_unset_subtype, fnt, cur_chr, accent); + break; + case char_number_cmd: + /* We don't accept keywords for |\glyph|. */ + base = tex_new_glyph_node(glyph_unset_subtype, fnt, tex_scan_char_number(0), accent); + break; + default: + /* compatibility hack, not that useful nowadays */ + if (cur_cmd <= max_non_prefixed_cmd) { + tex_back_input(cur_tok); + break; + } else { + lmt_error_state.set_box_allowed = 0; + tex_run_prefixed_command(); + lmt_error_state.set_box_allowed = 0; + goto PICKUP; + } + } + if (base) { + /*tex + Append the accent with appropriate kerns, then set |p := q|. The kern nodes + appended here must be distinguished from other kerns, lest they be wiped away by + the hyphenation algorithm or by a previous line break. The two kerns are computed + with (machine dependent) |real| arithmetic, but their sum is machine independent; + the net effect is machine independent, because the user cannot remove these nodes + nor access them via |\lastkern|. + + This goes away: not listening to scaled yet. + + */ + double t = (double) (tex_get_font_slant(fnt)) / (double) (65536); /* amount of slant */ + scaled w = tex_glyph_width(base); + scaled h = tex_glyph_height(base); + scaled delta = glueround((double) (w - a) / (double) (2) + h * t - x * s); + halfword left = tex_new_kern_node(delta, accent_kern_subtype); + halfword right = tex_new_kern_node(- a - delta, accent_kern_subtype); + glyph_x_offset(accent) = xoffset; + glyph_y_offset(accent) = yoffset; + if (h != x) { + /*tex the accent must be shifted up or down */ + // accent = hpack(accent, 0, packing_additional, direction_unknown); + // box_shift_amount(accent) = x - h; + glyph_y_offset(accent) += x - h; + } + tex_couple_nodes(cur_list.tail, left); + tex_couple_nodes(left, accent); + tex_couple_nodes(accent, right); + tex_couple_nodes(right,base); + cur_list.tail = base; + } else { + tex_couple_nodes(cur_list.tail, accent); + cur_list.tail = accent; + } + cur_list.space_factor = default_space_factor; + } +} + +/*tex Finally, |\endcsname| is not supposed to get through to |main_control|. */ + +static void tex_aux_run_cs_error(void) +{ + tex_handle_error( + normal_error_type, + "Extra \\endcsname", + "I'm ignoring this, since I wasn't doing a \\csname." + ); +} + +/*tex + + Assignments to values in |eqtb| can be global or local. Furthermore, a control sequence can + be defined to be |\long|, |\protected|, or |\outer|, and it might or might not be expanded. + The prefixes |\global|, |\long|, |\protected|, and |\outer| can occur in any order. Therefore + we assign binary numeric codes, making it possible to accumulate the union of all specified + prefixes by adding the corresponding codes. (\PASCAL's |set| operations could also have been + used.) + + Every prefix, and every command code that might or might not be prefixed, calls the action + procedure |prefixed_command|. This routine accumulates a sequence of prefixes until coming to + a non-prefix, then it carries out the command. + +*/ + +void tex_inject_text_or_line_dir(int val, int check_glue) +{ + if (cur_mode == hmode && internal_dir_state_par > 0) { + /*tex |tail| is non zero but we test anyway. */ + halfword dirn = tex_new_dir(cancel_dir_subtype, text_direction_par); + halfword tail = cur_list.tail; + if (check_glue && tail && node_type(tail) == glue_node) { + halfword prev = node_prev(tail); + tex_couple_nodes(prev, dirn); + tex_couple_nodes(dirn, tail); + } else { + tex_tail_append(dirn); + } + } + tex_push_text_dir_ptr(val); + if (cur_mode == hmode) { + halfword dir = tex_new_dir(normal_dir_subtype, val); + dir_level(dir) = cur_level; + tex_tail_append(dir); + } +} + +static void tex_aux_show_frozen_error(halfword cs) +{ + if (cs) { + tex_handle_error( + normal_error_type, + "You can't redefine the frozen macro %S.", cs, + NULL + ); + } else { + tex_handle_error( + normal_error_type, + "You can't redefine a frozen macro.", + NULL + ); + + } +} + +/*tex + + We use the fact that |register| $<$ |advance| $<$ |multiply| $<$ |divide| We compute the + register location |l| and its type |p| but |return| if invalid. Here we use the fact that + the consecutive codes |int_val .. mu_val| and |assign_int .. assign_mu_glue| correspond + to each other nicely. + +*/ + +inline static halfword tex_aux_get_register_index(int level) +{ + switch (level) { + case int_val_level: + { + halfword index = tex_scan_int_register_number(); + return register_int_location(index); + } + case dimen_val_level: + { + halfword index = tex_scan_dimen_register_number(); + return register_dimen_location(index); + } + case attr_val_level: + { + halfword index = tex_scan_attribute_register_number(); + return register_attribute_location(index); + } + case glue_val_level: + { + halfword index = tex_scan_glue_register_number(); + return register_glue_location(index); + } + case mu_val_level: + { + halfword index = tex_scan_mu_glue_register_number(); + return register_mu_glue_location(index); + } + case tok_val_level: + { + halfword index = tex_scan_toks_register_number(); + return register_toks_location(index); + } + default: + return 0; + } +} + +inline static halfword tex_aux_get_register_value(int level, int optionalequal) +{ + switch (level) { + case int_val_level: + case attr_val_level: + return tex_scan_int(optionalequal, NULL); + case dimen_val_level: + return tex_scan_dimen(0, 0, 0, optionalequal, NULL); + default: + return tex_scan_glue(level, optionalequal); + } +} + +static int tex_aux_valid_arithmic(int cmd, int *index, int *level, int *varcmd) +{ + /*tex So: |\multiply|, |\divide| or |\advance|. */ + tex_get_x_token(); + *varcmd = cur_cmd; + switch (cur_cmd) { + case register_int_cmd: + case internal_int_cmd: + *index = cur_chr; + *level = int_val_level; + return 1; + case register_attribute_cmd: + case internal_attribute_cmd: + *index = cur_chr; + *level = attr_val_level; + return 1; + case register_dimen_cmd: + case internal_dimen_cmd: + *index = cur_chr; + *level = dimen_val_level; + return 1; + case register_glue_cmd: + case internal_glue_cmd: + *index = cur_chr; + *level = glue_val_level; + return 1; + case register_mu_glue_cmd: + case internal_mu_glue_cmd: + *index = cur_chr; + *level = mu_val_level; + return 1; + case register_cmd: + *level = cur_chr; + *index = tex_aux_get_register_index(*level); + return 1; + default: + tex_handle_error( + normal_error_type, + "You can't use '%C' after %C", + cur_cmd, cur_chr, cmd, 0, + "I'm forgetting what you said and not changing anything." + ); + return 0; + } +} + +static void tex_aux_arithmic_overflow_error(int level, halfword value) +{ + if (level >= glue_val_level) { + tex_flush_node(value); + } + tex_handle_error( + normal_error_type, + "Arithmetic overflow", + "I can't carry out that multiplication or division, since the result is out of\n" + "range." + ); +} + +inline static void tex_aux_update_register(int a, int level, halfword index, halfword value, halfword cmd) +{ + switch (level) { + case int_val_level: + tex_word_define(a, index, value); + if (is_frozen(a) && cmd == internal_int_cmd && cur_mode == hmode) { + tex_update_par_par(internal_int_cmd, index - lmt_primitive_state.prim_data[cmd].offset); + } + break; + case attr_val_level: + if ((register_attribute_number(index)) > lmt_node_memory_state.max_used_attribute) { + lmt_node_memory_state.max_used_attribute = register_attribute_number(index); + } + change_attribute_register(a, index, value); + tex_word_define(a, index, value); + break; + case dimen_val_level: + tex_word_define(a, index, value); + if (is_frozen(a) && cmd == internal_dimen_cmd && cur_mode == hmode) { + tex_update_par_par(internal_dimen_cmd, index - lmt_primitive_state.prim_data[cmd].offset); + } + break; + case glue_val_level: +// tex_define(a, index, register_glue_reference_cmd, value); + tex_define(a, index, cmd == internal_glue_cmd ? internal_glue_reference_cmd : register_glue_reference_cmd, value); + if (is_frozen(a) && cmd == internal_glue_cmd && cur_mode == hmode) { + tex_update_par_par(internal_glue_cmd, index - lmt_primitive_state.prim_data[cmd].offset); + } + break; + case mu_val_level: +// tex_define(a, index, register_glue_reference_cmd, value); + tex_define(a, index, cmd == internal_glue_cmd ? internal_mu_glue_reference_cmd : register_mu_glue_reference_cmd, value); + break; + default: + /* can't happen */ + tex_word_define(a, index, value); + break; + } +} + +static void tex_aux_set_register(int a) +{ + halfword level = cur_chr; + halfword varcmd = cur_cmd; + halfword index = tex_aux_get_register_index(level); + halfword value = tex_aux_get_register_value(level, 1); + tex_aux_update_register(a, level, index, value, varcmd); +} + +static void tex_aux_arithmic_register(int a, int code) +{ + halfword cmd = cur_cmd; + halfword level = cur_chr; + halfword index = 0; + halfword varcmd = 0; + if (tex_aux_valid_arithmic(cmd, &index, &level, &varcmd)) { + halfword value = null; + tex_scan_optional_keyword("by"); + lmt_scanner_state.arithmic_error = 0; + switch (code) { + case advance_code: + { + value = tex_aux_get_register_value(level, 0); + switch (level) { + case int_val_level: + case attr_val_level: + case dimen_val_level: + value += eq_value(index); + break; + case glue_val_level: + case mu_val_level: + { + /* Compute the sum of two glue specs */ + halfword oldvalue = eq_value(index); + halfword newvalue = tex_new_glue_spec_node(value); + tex_flush_node(value); + glue_amount(newvalue) += glue_amount(oldvalue); + if (glue_stretch(newvalue) == 0) { + glue_stretch_order(newvalue) = normal_glue_order; + } + if (glue_stretch_order(newvalue) == glue_stretch_order(oldvalue)) { + glue_stretch(newvalue) += glue_stretch(oldvalue); + } else if ((glue_stretch_order(newvalue) < glue_stretch_order(oldvalue)) && (glue_stretch(oldvalue))) { + glue_stretch(newvalue) = glue_stretch(oldvalue); + glue_stretch_order(newvalue) = glue_stretch_order(oldvalue); + } + if (glue_shrink(newvalue) == 0) { + glue_shrink_order(newvalue) = normal_glue_order; + } + if (glue_shrink_order(newvalue) == glue_shrink_order(oldvalue)) { + glue_shrink(newvalue) += glue_shrink(oldvalue); + } else if ((glue_shrink_order(newvalue) < glue_shrink_order(oldvalue)) && (glue_shrink(oldvalue))) { + glue_shrink(newvalue) = glue_shrink(oldvalue); + glue_shrink_order(newvalue) = glue_shrink_order(oldvalue); + } + value = newvalue; + break; + } + default: + /* error */ + break; + } + /*tex There is no overflow detection for addition, just wraparound. */ + tex_aux_update_register(a, level, index, value, varcmd); + break; + } + case multiply_code: + { + halfword amount = tex_scan_int(0, NULL); + switch (level) { + case int_val_level: + case attr_val_level: + value = tex_multiply_integers(eq_value(index), amount); + break; + case dimen_val_level: + value = tex_nx_plus_y(eq_value(index), amount, 0); + break; + case glue_val_level: + case mu_val_level: + { + halfword s = eq_value(index); + halfword r = tex_new_glue_spec_node(s); + glue_amount(r) = tex_nx_plus_y(glue_amount(s), amount, 0); + glue_stretch(r) = tex_nx_plus_y(glue_stretch(s), amount, 0); + glue_shrink(r) = tex_nx_plus_y(glue_shrink(s), amount, 0); + value = r; + break; + } + default: + /* error */ + break; + } + if (lmt_scanner_state.arithmic_error) { + tex_aux_arithmic_overflow_error(level, value); + } else { + tex_aux_update_register(a, level, index, value, varcmd); + } + break; + } + case divide_code: + { + halfword amount = tex_scan_int(0, NULL); + switch (level) { + case int_val_level: + case attr_val_level: + case dimen_val_level: + value = tex_x_over_n(eq_value(index), amount); + break; + case glue_val_level: + case mu_val_level: + { + halfword s = eq_value(index); + halfword r = tex_new_glue_spec_node(s); + glue_amount(r) = tex_x_over_n(glue_amount(s), amount); + glue_stretch(r) = tex_x_over_n(glue_stretch(s), amount); + glue_shrink(r) = tex_x_over_n(glue_shrink(s), amount); + value = r; + break; + } + default: + /* error */ + break; + } + if (lmt_scanner_state.arithmic_error) { + tex_aux_arithmic_overflow_error(level, value); + } else { + tex_aux_update_register(a, level, index, value, varcmd); + } + break; + } + } + } +} + +/*tex + The value of |c| is 0 for |\deadcycles|, 1 for |\insertpenalties|, etc. In traditional \TEX\ + the interaction mode is set by primitives so no checking is needed. However, in \ETEX\ the + value can be set. As a consequence there is an error message for wrong values but here we + just clip the values. After all, we can also set values from \LUA\ so either we bark or we + just recover. So, gone is: + + \starttyping + handle_error_int( + normal_error_type, + "Bad interaction mode (", val, ")", + "Modes are 0=batch, 1=nonstop, 2=scroll, and 3=errorstop. Proceed, and I'll ignore\n" + "this case." + ); + \stoptyping + + I could have decided to ignore bad values but clipping is probably better. + +*/ + +inline static void tex_aux_set_interaction(halfword mode) +{ + tex_print_ln(); + if (mode < batch_mode) { + lmt_error_state.interaction = batch_mode; + } else if (mode > error_stop_mode) { + lmt_error_state.interaction = error_stop_mode; + } else { + lmt_error_state.interaction = mode; + } + tex_fixup_selector(lmt_fileio_state.log_opened); +} + +static void tex_aux_set_page_property(void) +{ + switch (cur_chr) { + case page_goal_code: + lmt_page_builder_state.goal = tex_scan_dimen(0, 0, 0, 1, NULL); + break; + case page_vsize_code: + lmt_page_builder_state.vsize = tex_scan_dimen(0, 0, 0, 1, NULL); + break; + case page_total_code: + lmt_page_builder_state.total = tex_scan_dimen(0, 0, 0, 1, NULL); + break; + case page_depth_code: + lmt_page_builder_state.depth = tex_scan_dimen(0, 0, 0, 1, NULL); + break; + case dead_cycles_code: + lmt_page_builder_state.dead_cycles = tex_scan_int(1, NULL); + break; + case insert_penalties_code: + lmt_page_builder_state.insert_penalties = tex_scan_int(1, NULL); + break; + case insert_heights_code: + lmt_page_builder_state.insert_heights = tex_scan_dimen(0, 0, 0, 1, NULL); + break; + case insert_storing_code: + lmt_insert_state.storing = tex_scan_int(1, NULL); + break; + case insert_distance_code: + { + /*tex + We need to scan the index first because when we do that in the call we somehow + get an out-of-order issue (index too large). The same is true for teh rest. + */ + int index = tex_scan_int(0, NULL); + tex_set_insert_distance(index, tex_scan_glue(glue_val_level, 1)); + } + break; + case insert_multiplier_code: + { + int index = tex_scan_int(0, NULL); + tex_set_insert_multiplier(index, tex_scan_int(1, NULL)); + } + break; + case insert_limit_code: + { + int index = tex_scan_int(0, NULL); + tex_set_insert_limit(index, tex_scan_dimen(0, 0, 0, 1, NULL)); + } + break; + case insert_storage_code: + { + int index = tex_scan_int(0, NULL); + tex_set_insert_storage(index, tex_scan_int(1, NULL)); + } + break; + case insert_penalty_code: + { + int index = tex_scan_int(0, NULL); + tex_set_insert_penalty(index, tex_scan_int(1, NULL)); + } + break; + case insert_maxdepth_code: + { + int index = tex_scan_int(0, NULL); + tex_set_insert_maxdepth(index, tex_scan_dimen(0, 0, 0, 1, NULL)); + } + break; + case insert_height_code: + { + int index = tex_scan_int(0, NULL); + tex_set_insert_height(index, tex_scan_dimen(0, 0, 0, 1, NULL)); + } + break; + case insert_depth_code: + { + int index = tex_scan_int(0, NULL); + tex_set_insert_depth(index, tex_scan_dimen(0, 0, 0, 1, NULL)); + } + break; + case insert_width_code: + { + int index = tex_scan_int(0, NULL); + tex_set_insert_width(index, tex_scan_dimen(0, 0, 0, 1, NULL)); + } + break; + default: + lmt_page_builder_state.page_so_far[page_state_offset(cur_chr)] = tex_scan_dimen(0, 0, 0, 1, NULL); + break; + } +} + +/*tex + The |space_factor| or |prev_depth| settings are changed when a |set_aux| command is sensed. + Similarly, |prev_graf| is changed in the presence of |set_prev_graf|, and |dead_cycles| or + |insert_penalties| in the presence of |set_page_int|. These definitions are always global. +*/ + +static void tex_aux_set_auxiliary(int a) +{ + (void) a; + switch (cur_chr) { + case space_factor_code: + if (cur_mode == hmode) { + halfword v = tex_scan_int(1, NULL); + if ((v <= min_space_factor) || (v > max_space_factor)) { + tex_handle_error( + normal_error_type, + "Bad space factor (%i). I allow only values in the range %i..%i here.", + v, min_space_factor + 1, max_space_factor, + NULL + ); + } else { + cur_list.space_factor = v; + } + } else { + tex_aux_run_illegal_case(); + } + break; + case prev_depth_code: + if (cur_mode == vmode) { + cur_list.prev_depth = tex_scan_dimen(0, 0, 0, 1, NULL); + } else { + tex_aux_run_illegal_case(); + } + break; + case prev_graf_code: + { + halfword v = tex_scan_int(1, NULL); + if (v >= 0) { + lmt_nest_state.nest[tex_vmode_nest_index()].prev_graf = v; + } else { + tex_handle_error( + normal_error_type, + "Bad \\prevgraf (%i)", + v, + "I allow only nonnegative values here." + ); + } + break; + } + case interaction_mode_code: + { + tex_aux_set_interaction(tex_scan_int(1, NULL)); + break; + } + case insert_mode_code: + { + tex_set_insert_mode(tex_scan_int(1, NULL)); + break; + } + } +} + +/*tex + When some dimension of a box register is changed, the change isn't exactly global; but \TEX\ + does not look at the |\global| switch. +*/ + +static void tex_aux_set_box_property(void) +{ + halfword code = cur_chr; + halfword n = tex_scan_box_register_number(); + halfword b = box_register(n); + switch (code) { + case box_width_code: + { + scaled v = tex_scan_dimen(0, 0, 0, 1, NULL); + if (b) { + box_width(b) = v; + } + break; + } + case box_height_code: + { + scaled v = tex_scan_dimen(0, 0, 0, 1, NULL); + if (b) { + box_height(b) = v; + } + break; + } + case box_depth_code: + { + scaled v = tex_scan_dimen(0, 0, 0, 1, NULL); + if (b) { + box_depth(b) = v; + } + break; + } + case box_direction_code: + { + halfword v = tex_scan_direction(1); + if (b) { + tex_set_box_direction(b, v); + } + break; + } + case box_geometry_code: + { + halfword v = tex_scan_geometry(1); + if (b) { + box_geometry(b) = (singleword) v; + } + break; + } + case box_orientation_code: + { + halfword v = tex_scan_orientation(1); + if (b) { + box_orientation(b) = v; + tex_set_box_geometry(b, orientation_geometry); + } + break; + } + case box_anchor_code: + case box_anchors_code: + { + halfword v = code == box_anchor_code ? tex_scan_anchor(1) : tex_scan_anchors(1); + if (b) { + box_anchor(b) = v; + tex_set_box_geometry(b, anchor_geometry); + } + break; + } + case box_source_code: + { + halfword v = tex_scan_int(1, NULL); + if (b) { + box_source_anchor(b) = v; + tex_set_box_geometry(b, anchor_geometry); + } + break; + } + case box_target_code: + { + halfword v = tex_scan_int(1, NULL); + if (b) { + box_target_anchor(b) = v; + tex_set_box_geometry(b, anchor_geometry); + } + break; + } + case box_xoffset_code: + { + scaled v = tex_scan_dimen(0, 0, 0, 1, NULL); + if (b) { + box_x_offset(b) = v; + tex_set_box_geometry(b, offset_geometry); + } + break; + } + case box_yoffset_code: + { + scaled v = tex_scan_dimen(0, 0, 0, 1, NULL); + if (b) { + box_y_offset(b) = v; + tex_set_box_geometry(b, offset_geometry); + } + break; + } + case box_xmove_code: + { + scaled v = tex_scan_dimen(0, 0, 0, 1, NULL); + if (b) { + box_x_offset(b) = tex_aux_checked_dimen1(box_x_offset(b) + v); + box_width(b) = tex_aux_checked_dimen2(box_width(b) + v); + tex_set_box_geometry(b, offset_geometry); + } + break; + } + case box_ymove_code: + { + scaled v = tex_scan_dimen(0, 0, 0, 1, NULL); + if (b) { + box_y_offset(b) = tex_aux_checked_dimen1(box_y_offset(b) + v); + box_height(b) = tex_aux_checked_dimen2(box_height(b) + v); + box_depth(b) = tex_aux_checked_dimen2(box_depth(b) - v); + tex_set_box_geometry(b, offset_geometry); + } + break; + } + case box_total_code: + { + scaled v = tex_scan_dimen(0, 0, 0, 1, NULL); + if (b) { + box_height(b) = v / 2; + box_depth(b) = v - (v / 2); + } + } + break; + case box_shift_code: + { + scaled v = tex_scan_dimen(0, 0, 0, 1, NULL); + if (b) { + box_shift_amount(b) = v; + } + } + break; + case box_adapt_code: + { + scaled v = tex_scan_limited_scale(1); + if (b) { + tex_repack(b, v, packing_adapted); + } + } + break; + case box_repack_code: + { + scaled v = tex_scan_dimen(0, 0, 0, 1, NULL); + if (b) { + tex_repack(b, v, packing_additional); + } + } + break; + case box_freeze_code: + { + scaled v = tex_scan_int(1, NULL); + if (b) { + tex_freeze(b, v); + } + } + break; + case box_attribute_code: + { + halfword att = tex_scan_box_register_number(); + halfword val = tex_scan_int(1, NULL); + if (b) { + if (val == unused_attribute_value) { + tex_unset_attribute(b, att, val); + } else { + tex_set_attribute(b, att, val); + } + } + } + break; + default: + break; + } +} + +/*tex + The processing of boxes is somewhat different, because we may need to scan and create an entire + box before we actually change the value of the old one. +*/ + +static void tex_aux_set_box(int a) +{ + halfword n = tex_scan_box_register_number() + (is_global(a) ? global_box_flag : box_flag); + if (lmt_error_state.set_box_allowed) { + tex_aux_scan_box(n, 1, null_flag); + } else { + tex_handle_error( + normal_error_type, + "Improper \\setbox", + "Sorry, \\setbox is not allowed after \\halign in a display, between \\accent and\n" + "an accented character, or in immediate assignments." + ); + } +} + +/*tex + We temporarily define |p| to be |relax|, so that an occurrence of |p| while scanning the + definition will simply stop the scanning instead of producing an \quote {undefined control + sequence} error or expanding the previous meaning. This allows, for instance, |\chardef + \foo = 123\foo|. +*/ + +static void tex_aux_set_shorthand_def(int a, int force) +{ + halfword code = cur_chr; + tex_get_r_token(); + if (force || tex_define_permitted(cur_cs, a)) { + halfword p = cur_cs; + tex_define(a, p, relax_cmd, relax_code); + tex_scan_optional_equals(); + switch (code) { + case char_def_code: + { + halfword chr = tex_scan_char_number(0); /* maybe 1 */ + tex_define(a, p, char_given_cmd, chr); + break; + } + case math_char_def_code: + { + mathcodeval mval = tex_scan_mathchar(tex_mathcode); + tex_define(a, p, mathspec_cmd, tex_new_math_spec(mval, tex_mathcode)); + // tex_define(a, p, math_char_given_cmd, math_old_packed_character(mval.class_value,mval.family_value,mval.character_value)); + break; + } + case math_dchar_def_code: + { + mathdictval dval = tex_scan_mathdict(); + mathcodeval mval = tex_scan_mathchar(umath_mathcode); + tex_define(a, p, mathspec_cmd, tex_new_math_dict_spec(dval, mval, umath_mathcode)); + // tex_define(a, p, math_char_xgiven_cmd, math_packed_character(mval.class_value,mval.family_value,mval.character_value)); + break; + } + case math_xchar_def_code: + { + mathcodeval mval = tex_scan_mathchar(umath_mathcode); + tex_define(a, p, mathspec_cmd, tex_new_math_spec(mval, umath_mathcode)); + // tex_define(a, p, math_char_xgiven_cmd, math_packed_character(mval.class_value,mval.family_value,mval.character_value)); + break; + } + /* + case math_uchar_def_code: + { + mathcodeval mval = tex_scan_mathchar(umathnum_mathcode); + tex_define(a, p, mathspec_cmd, tex_new_math_spec(mval, umathnum_mathcode)); + // tex_define(a, p, math_char_xgiven_cmd, math_packed_character(mval.class_value,mval.family_value,mval.character_value)); + break; + } + */ + case count_def_code: + { + halfword n = tex_scan_int_register_number(); + tex_define(a, p, register_int_cmd, register_int_location(n)); + break; + } + case attribute_def_code: + { + halfword n = tex_scan_attribute_register_number(); + tex_define(a, p, register_attribute_cmd, register_attribute_location(n)); + break; + } + case dimen_def_code: + { + scaled n = tex_scan_dimen_register_number(); + tex_define(a, p, register_dimen_cmd, register_dimen_location(n)); + break; + } + case skip_def_code: + { + halfword n = tex_scan_glue_register_number(); + tex_define(a, p, register_glue_cmd, register_glue_location(n)); + break; + } + case mu_skip_def_code: + { + halfword n = tex_scan_mu_glue_register_number(); + tex_define(a, p, register_mu_glue_cmd, register_mu_glue_location(n)); + break; + } + case toks_def_code: + { + halfword n = tex_scan_toks_register_number(); + tex_define(a, p, register_toks_cmd, register_toks_location(n)); + break; + } + case lua_def_code: + { + halfword v = tex_scan_function_reference(1); + tex_define(a, p, is_protected(a) ? lua_protected_call_cmd : lua_call_cmd, v); + } + break; + case integer_def_code: + { + halfword v = tex_scan_int(1, NULL); + tex_define(a, p, integer_cmd, v); + } + break; + case dimension_def_code: + { + scaled v = tex_scan_dimen(0, 0, 0, 1, NULL); + tex_define(a, p, dimension_cmd, v); + } + break; + case gluespec_def_code: + { + halfword v = tex_scan_glue(glue_val_level, 1); + tex_define(a, p, gluespec_cmd, v); + } + break; + case mugluespec_def_code: + { + halfword v = tex_scan_glue(mu_val_level, 1); + tex_define(a, p, mugluespec_cmd, v); + } + break; + /* + case mathspec_def_code: + { + halfword v = tex_scan_math_spec(1); + tex_define(a, p, mathspec_cmd, v); + } + break; + */ + case fontspec_def_code: + { + halfword v = tex_scan_font(1); + tex_define(a, p, fontspec_cmd, v); + } + break; + /* + case string_def_code: + { + halfword t = scan_toks_expand(0, NULL); + halfword s = tokens_to_string(t); + define(a, p, string_cmd, s - cs_offset_value); + flush_list(t); + break; + } + */ + default: + tex_confusion("shorthand definition"); + break; + } + } +} + +/*tex This deals with the shapes and penalty lists: */ + +static void tex_aux_set_specification(int a) +{ + halfword loc = cur_chr; + quarterword num = (quarterword) internal_specification_number(loc); + halfword p = null; + halfword options = 0; + halfword count = tex_scan_int(1, NULL); + if (tex_scan_keyword("options")) { + options = tex_scan_int(0, NULL); + } + if (count > 0) { + p = tex_new_specification_node(count, num, options); + if (num == par_shape_code) { + for (int j = 1; j <= count; j++) { + tex_set_specification_indent(p, j, tex_scan_dimen(0, 0, 0, 0, NULL)); /*tex indentation */ + tex_set_specification_width(p, j, tex_scan_dimen(0, 0, 0, 0, NULL)); /*tex width */ + } + } else { + for (int j = 1; j <= count; j++) { + tex_set_specification_penalty(p, j, tex_scan_int(0, NULL)); /*tex penalty values */ + } + } + } + tex_define(a, loc, specification_reference_cmd, p); + if (is_frozen(a) && cur_mode == hmode) { + tex_update_par_par(specification_reference_cmd, num); + } +} + +/*tex + All of \TEX's parameters are kept in |eqtb| except the font and language information, including + the hyphenation tables; these are strictly global. +*/ + +static void tex_aux_set_hyph_data(void) +{ + switch (cur_chr) { + case hyphenation_code: + tex_scan_toks_expand(0, NULL, 0); + tex_load_tex_hyphenation(language_par, lmt_input_state.def_ref); /* hm, why not use return value */ + tex_flush_token_list(lmt_input_state.def_ref); + break; + case patterns_code: + tex_scan_toks_expand(0, NULL, 0); + tex_load_tex_patterns(language_par, lmt_input_state.def_ref); /* hm, why not use return value */ + tex_flush_token_list(lmt_input_state.def_ref); + break; + case prehyphenchar_code: + tex_set_pre_hyphen_char(language_par, tex_scan_int(1, NULL)); + break; + case posthyphenchar_code: + tex_set_post_hyphen_char(language_par, tex_scan_int(1, NULL)); + break; + case preexhyphenchar_code: + tex_set_pre_exhyphen_char(language_par, tex_scan_int(1, NULL)); + break; + case postexhyphenchar_code: + tex_set_post_exhyphen_char(language_par, tex_scan_int(1, NULL)); + break; + case hyphenationmin_code: + tex_set_hyphenation_min(language_par, tex_scan_int(1, NULL)); + break; + case hjcode_code: + { + halfword lan = tex_scan_int(0, NULL); + halfword val = tex_scan_int(1, NULL); + tex_set_hj_code(language_par, lan, val, -1); + } + break; + default: + break; + } +} + +/*tex move to font */ + +static void tex_aux_set_font_property(void) +{ + halfword code = cur_chr; + switch (code) { + case font_hyphen_code: + { + halfword fnt = tex_scan_font_identifier(NULL); + halfword val = tex_scan_int(1, NULL); + set_font_hyphen_char(fnt, val); + break; + } + case font_skew_code: + { + halfword fnt = tex_scan_font_identifier(NULL); + halfword val = tex_scan_int(1, NULL); + set_font_skew_char(fnt, val); + break; + } + case font_lp_code: + { + halfword fnt = tex_scan_font_identifier(NULL); + halfword chr = tex_scan_char_number(0); + halfword val = tex_scan_dimen(0, 0, 0, 1, NULL); + tex_set_lpcode_in_font(fnt, chr, val); + break; + } + case font_rp_code: + { + halfword fnt = tex_scan_font_identifier(NULL); + halfword chr = tex_scan_char_number(0); + halfword val = tex_scan_dimen(0, 0, 0, 1, NULL); + tex_set_rpcode_in_font(fnt, chr, val); + break; + } + case font_ef_code: + { + halfword fnt = tex_scan_font_identifier(NULL); + halfword chr = tex_scan_char_number(0); + halfword val = tex_scan_int(1, NULL); + tex_set_efcode_in_font(fnt, chr, val); + break; + } + case font_dimen_code: + { + tex_set_font_dimen(); + break; + } + case scaled_font_dimen_code: + { + tex_set_scaled_font_dimen(); + break; + } + default: + break; + } +} + +/*tex + Here is where the information for a new font gets loaded. We start with fonts. Unfortunately, + they aren't all as simple as this. +*/ + +static void tex_aux_set_font(int a) +{ + tex_set_cur_font(a, cur_chr); +} + +static void tex_aux_set_define_font(int a) +{ + if (! tex_tex_def_font(a)) { + tex_aux_show_frozen_error(cur_cs); + } +} + +/*tex + When a |def| command has been scanned, |cur_chr| is odd if the definition is supposed to be + global, and |cur_chr >= 2| if the definition is supposed to be expanded. Remark: this is + different in \LUAMETATEX. +*/ + +static void tex_aux_set_def(int a, int force) +{ + halfword expand = 0; + switch (cur_chr) { + case expanded_def_code: + expand = 1; + break; + case def_code: + break; + case global_expanded_def_code: + expand = 1; + // fall through + case global_def_code: + a = add_global_flag(a); + break; + case expanded_def_csname_code: + expand = 1; + // fall through + case def_csname_code: + cur_cs = tex_create_csname(); + goto DONE; + case global_expanded_def_csname_code: + expand = 1; + // fall through + case global_def_csname_code: + cur_cs = tex_create_csname(); + a = add_global_flag(a); + goto DONE; + } + tex_get_r_token(); + DONE: + if (global_defs_par > 0) { + a = add_global_flag(a); + } + if (force || tex_define_permitted(cur_cs, a)) { + halfword p = cur_cs; + halfword t = expand ? tex_scan_macro_expand() : tex_scan_macro_normal(); + tex_define(a, p, tex_flags_to_cmd(a), t); + } +} + +static void tex_aux_set_let(int a, int force) +{ + halfword code = cur_chr; + halfword p = null; + halfword q = null; + switch (code) { + case global_let_code: + /*tex |\glet| */ + if (global_defs_par >= 0) { + a = add_global_flag(a); + } + // fall through + case let_code: + /*tex |\let| */ + // LET: + tex_get_r_token(); + LETINDEED: + if (force || tex_define_permitted(cur_cs, a)) { + p = cur_cs; + do { + tex_get_token(); + } while (cur_cmd == spacer_cmd); + if (cur_tok == equal_token) { + tex_get_token(); + if (cur_cmd == spacer_cmd) { + tex_get_token(); + } + } + } + break; + case future_let_code: + case future_def_code: + /*tex |\futurelet| */ + tex_get_r_token(); + /*tex + Checking for a frozen macro here is tricky but not doing it would be kind of weird. + */ + if (force || tex_define_permitted(cur_cs, a)) { + p = cur_cs; + q = tex_get_token(); + tex_back_input(tex_get_token()); + /*tex + We look ahead and then back up. Note that |back_input| doesn't affect |cur_cmd|, + |cur_chr|. + */ + tex_back_input(q); + if (code == future_def_code) { + halfword result = get_reference_token(); + halfword r = result; + r = tex_store_new_token(r, cur_tok); + cur_cmd = tex_flags_to_cmd(a); + cur_chr = result; + } + } + break; + case let_charcode_code: + /*tex |\letcharcode| (todo: protection) */ + { + halfword v = tex_scan_int(0, NULL); + if (v > 0) { + p = tex_active_to_cs(v, 1); + do { + tex_get_token(); + } while (cur_cmd == spacer_cmd); + if (cur_tok == equal_token) { + tex_get_token(); + if (cur_cmd == spacer_cmd) { + tex_get_token(); + } + } + } else { + p = null; + tex_handle_error( + normal_error_type, + "invalid number for \\letcharcode", + NULL + ); + } + break; + } + case swap_cs_values_code: + { + /*tex + There is no real gain in performance but it looks nicer when tracing when we + just swap natively (like no save and restore of a temporary variable and + such). Maybe we should be more restrictive but it's a cheap experiment anyway. + + Flags should match and should not contain permanent, primitive or immutable. + */ + halfword s1, s2; + tex_get_r_token(); + s1 = cur_cs; + tex_get_r_token(); + s2 = cur_cs; + tex_define_swapped(a, s1, s2, force); + return; + } + case let_protected_code: + tex_get_r_token(); + if (force || tex_define_permitted(cur_cs, a)) { + switch (cur_cmd) { + case call_cmd: + case semi_protected_call_cmd: + set_eq_type(cur_cs, protected_call_cmd); + break; + case tolerant_call_cmd: + case tolerant_semi_protected_call_cmd: + set_eq_type(cur_cs, tolerant_protected_call_cmd); + break; + } + } + return; + case unlet_protected_code: + tex_get_r_token(); + if (force || tex_define_permitted(cur_cs, a)) { + switch (cur_cmd) { + case protected_call_cmd: + case semi_protected_call_cmd: + set_eq_type(cur_cs, call_cmd); + break; + case tolerant_call_cmd: + case tolerant_semi_protected_call_cmd: + set_eq_type(cur_cs, tolerant_call_cmd); + break; + } + } + return; + case let_frozen_code: + tex_get_r_token(); + if (is_call_cmd(cur_cmd) && (force || tex_define_permitted(cur_cs, a))) { + set_eq_flag(cur_cs, add_frozen_flag(eq_flag(cur_cs))); + } + return; + case unlet_frozen_code: + tex_get_r_token(); + if (is_call_cmd(cur_cmd) && (force || tex_define_permitted(cur_cs, a))) { + set_eq_flag(cur_cs, remove_frozen_flag(eq_flag(cur_cs))); + } + return; + case global_let_csname_code: + if (global_defs_par >= 0) { + a = add_global_flag(a); + } + // fall through + case let_csname_code: + cur_cs = tex_create_csname(); + goto LETINDEED; + case global_let_to_nothing_code: + a = add_global_flag(a); + // fall through + case let_to_nothing_code: + tex_get_r_token(); + if (global_defs_par > 0) { + a = add_global_flag(a); + } + if (force || tex_define_permitted(cur_cs, a)) { + tex_define(a, cur_cs, tex_flags_to_cmd(a), get_reference_token()); + } + return; + default: + /*tex We please the compiler. */ + p = null; + tex_confusion("let"); + break; + } + if (is_referenced_cmd(cur_cmd)) { + tex_add_token_reference(cur_chr); + } else if (is_nodebased_cmd(cur_cmd)) { + cur_chr = tex_copy_node(cur_chr); + } + // if (p && cur_cmd >= relax_cmd) { + if (p && cur_cmd >= 0) { + singleword oldf = eq_flag(cur_cs); + singleword newf = 0; + singleword cmd = (singleword) cur_cmd; + if (is_aliased(a)) { + newf = oldf; + } else { + oldf = remove_overload_flags(oldf); + newf = oldf | make_eq_flag_bits(a); + } + if (is_protected(a)) { + switch (cmd) { + case call_cmd: + cmd = protected_call_cmd; + break; + case tolerant_call_cmd: + cmd = tolerant_protected_call_cmd; + break; + } + } + tex_define_inherit(a, p, (singleword) newf, (singleword) cmd, cur_chr); + } else { + tex_define(a, p, (singleword) cur_cmd, cur_chr); + } +} + +/*tex + The token-list parameters, |\output| and |\everypar|, etc., receive their values in the + following way. (For safety's sake, we place an enclosing pair of braces around an |\output| + list.) +*/ + +static void tex_aux_set_assign_toks(int a) // better just pass cmd and chr +{ + halfword cs = cur_cs; + halfword cmd = cur_cmd; + halfword chr; + halfword loc; + halfword tail; + if (cmd == register_cmd) { + loc = register_toks_location(tex_scan_toks_register_number()); + } else { + /*tex |every_par_loc| or |output_routine_loc| or \dots */ + loc = cur_chr; + } + /*tex + Skip an optional equal sign and get the next non-blank non-relax non-call token. + */ + { + int n = 1 ; + while (1) { + tex_get_x_token(); + if (cur_cmd == spacer_cmd) { + /*tex Go on! */ + } else if (cur_cmd == relax_cmd) { + n = 0; + } else if (n && cur_tok == equal_token) { + n = 0; + } else { + break; + } + } + } + if (cur_cmd != left_brace_cmd) { + /*tex + If the right-hand side is a token parameter or token register, finish + the assignment and |goto done| + */ + if (cur_cmd == register_cmd && cur_chr == tok_val_level) { + chr = eq_value(register_toks_location(tex_scan_toks_register_number())); + if (chr) { + tex_add_token_reference(chr); + } + goto DEFINE; + } else if (cur_cmd == register_toks_cmd || cur_cmd == internal_toks_cmd) { + chr = eq_value(cur_chr); + if (chr) { + tex_add_token_reference(chr); + } + goto DEFINE; + } else { + /*tex Recover possibly with error message. */ + tex_back_input(cur_tok); + cur_cs = cs; + chr = tex_scan_toks_normal(0, &tail); + } + } else { + cur_cs = cs; + chr = tex_scan_toks_normal(1, &tail); + } + if (! token_link(chr)) { + tex_put_available_token(chr); + chr = null; + } else if (loc == internal_toks_location(output_routine_code)) { + halfword head = token_link(chr); + halfword list = tex_store_new_token(null, left_brace_token + '{'); + tex_store_new_token(tail, right_brace_token + '}'); + set_token_link(list, head); + set_token_link(chr, list); + } + DEFINE: + tex_define(a, loc, cmd == internal_toks_cmd ? internal_toks_reference_cmd : register_toks_reference_cmd, chr); +} + +/*tex Let |n| be the largest legal code value, based on |cur_chr| */ + +static void tex_aux_set_define_char_code(int a) /* maybe make |a| already a boolean */ +{ + switch (cur_chr) { + case catcode_charcode: + { + halfword chr = tex_scan_char_number(0); + halfword val = tex_scan_int(1, NULL); + if (val < 0 || val > max_char_code) { + tex_aux_out_of_range_error(val, max_char_code); + } + tex_set_cat_code(cat_code_table_par, chr, val, global_or_local(a)); + } + break; + case lccode_charcode: + { + halfword chr = tex_scan_char_number(0); + halfword val = tex_scan_int(1, NULL); + if (val < 0 || val > max_character_code) { + tex_aux_out_of_range_error(val, max_character_code); + } + tex_set_lc_code(chr, val, global_or_local(a)); + } + break; + case uccode_charcode: + { + halfword chr = tex_scan_char_number(0); + halfword val = tex_scan_int(1, NULL); + if (val < 0 || val > max_character_code) { + tex_aux_out_of_range_error(val, max_character_code); + } + tex_set_uc_code(chr, val, global_or_local(a)); + } + break; + case sfcode_charcode: + { + halfword chr = tex_scan_char_number(0); + halfword val = tex_scan_int(1, NULL); + if (val < min_space_factor || val > max_space_factor) { + tex_aux_out_of_range_error(val, max_space_factor); + } + tex_set_sf_code(chr, val, global_or_local(a)); + } + break; + case hccode_charcode: + { + halfword chr = tex_scan_char_number(0); + halfword val = tex_scan_char_number(1); + tex_set_hc_code(chr, val, global_or_local(a)); + } + break; + case hmcode_charcode: + { + halfword chr = tex_scan_char_number(0); + halfword val = tex_scan_math_discretionary_number(1); + tex_set_hm_code(chr, val, global_or_local(a)); + } + break; + case mathcode_charcode: + tex_scan_extdef_math_code((is_global(a)) ? level_one: cur_level, tex_mathcode); + break; + case extmathcode_charcode: + tex_scan_extdef_math_code((is_global(a)) ? level_one : cur_level, umath_mathcode); + break; + /* + case extmathcodenum_charcode: + tex_scan_extdef_math_code((is_global(a)) ? level_one : cur_level, umathnum_mathcode); + break; + */ + case delcode_charcode: + tex_scan_extdef_del_code((is_global(a)) ? level_one : cur_level, tex_mathcode); + break; + case extdelcode_charcode: + tex_scan_extdef_del_code((is_global(a)) ? level_one : cur_level, umath_mathcode); + break; + /* + case extdelcodenum_charcode: + tex_scan_extdef_del_code((is_global(a)) ? level_one : cur_level, umathnum_mathcode); + break; + */ + default: + break; + } +} + +static void tex_aux_skip_optional_equal(void) +{ + do { + tex_get_x_token(); + } while (cur_cmd == spacer_cmd); + if (cur_tok == equal_token) { + tex_get_x_token(); + } +} + +static void tex_aux_set_math_parameter(int a) +{ + halfword code = cur_chr; + halfword value = null; /* can also be scaled */ + switch (code) { + case math_parameter_reset_spacing: + { + tex_reset_all_styles(global_or_local(a)); + return; + } + case math_parameter_set_spacing: + case math_parameter_set_atom_rule: + { + halfword left = tex_scan_math_class_number(0); + halfword right = tex_scan_math_class_number(0); + switch (code) { + case math_parameter_set_spacing: + code = tex_to_math_spacing_parameter(left, right); + break; + case math_parameter_set_atom_rule: + code = tex_to_math_rules_parameter(left, right); + break; + } + if (code < 0) { + tex_handle_error( + normal_error_type, + "Invalid math class pair", + "I'm going to assume ordinary atoms." + ); + switch (code) { + case math_parameter_set_spacing: + code = tex_to_math_spacing_parameter(ordinary_noad_subtype, ordinary_noad_subtype); + break; + case math_parameter_set_atom_rule: + code = tex_to_math_rules_parameter(ordinary_noad_subtype, ordinary_noad_subtype); + break; + } + } + break; + } + case math_parameter_let_spacing: + case math_parameter_let_atom_rule: + { + halfword class = tex_scan_math_class_number(0); + halfword display = tex_scan_math_class_number(1); + halfword text = tex_scan_math_class_number(0); + halfword script = tex_scan_math_class_number(0); + halfword scriptscript = tex_scan_math_class_number(0); + if (valid_math_class_code(class)) { + switch (code) { + case math_parameter_let_spacing: + code = internal_int_location(first_math_class_code + class); + break; + case math_parameter_let_atom_rule: + code = internal_int_location(first_math_atom_code + class); + break; + } + value = (display << 24) + (text << 16) + (script << 8) + scriptscript; + // tex_assign_internal_int_value(a, code, value); + tex_word_define(a, code, value); + } else { + tex_handle_error( + normal_error_type, + "Invalid math class", + "I'm going to ignore this alias." + ); + } + return; + } + case math_parameter_copy_spacing: + case math_parameter_copy_atom_rule: + case math_parameter_copy_parent: + { + halfword class = tex_scan_math_class_number(0); + halfword parent = tex_scan_math_class_number(1); + if (valid_math_class_code(class) && valid_math_class_code(parent)) { + switch (code) { + case math_parameter_copy_spacing: + code = internal_int_location(first_math_class_code + class); + value = count_parameter(first_math_class_code + parent); + break; + case math_parameter_copy_atom_rule: + code = internal_int_location(first_math_atom_code + class); + value = count_parameter(first_math_atom_code + parent); + break; + case math_parameter_copy_parent: + code = internal_int_location(first_math_parent_code + class); + value = count_parameter(first_math_parent_code + parent); + break; + } + tex_word_define(a, code, value); + } else { + tex_handle_error( + normal_error_type, + "Invalid math class", + "I'm going to ignore this alias." + ); + } + return; + } + case math_parameter_set_pre_penalty: + case math_parameter_set_post_penalty: + case math_parameter_set_display_pre_penalty: + case math_parameter_set_display_post_penalty: + { + halfword class = tex_scan_math_class_number(0); + halfword penalty = tex_scan_int(1, NULL); + if (valid_math_class_code(class)) { + switch (code) { + case math_parameter_set_pre_penalty: + code = internal_int_location(first_math_pre_penalty_code + class); + break; + case math_parameter_set_post_penalty: + code = internal_int_location(first_math_post_penalty_code + class); + break; + case math_parameter_set_display_pre_penalty: + code = internal_int_location(first_math_display_pre_penalty_code + class); + break; + case math_parameter_set_display_post_penalty: + code = internal_int_location(first_math_display_post_penalty_code + class); + break; + } + tex_word_define(a, code, penalty); + // tex_assign_internal_int_value(a, code, penalty); + } else { + tex_handle_error( + normal_error_type, + "Invalid math class", + "I'm going to ignore this atom penalty." + ); + } + return; + } + case math_parameter_let_parent: + { + halfword class = tex_scan_math_class_number(0); + halfword pre = tex_scan_math_class_number(1); + halfword post = tex_scan_math_class_number(0); + halfword options = tex_scan_math_class_number(0); + halfword reserved = tex_scan_math_class_number(0); + if (valid_math_class_code(class)) { + code = internal_int_location(first_math_parent_code + class); + value = (reserved << 24) + (options << 16) + (pre << 8) + post; + tex_word_define(a, code, value); + // tex_assign_internal_int_value(a, code, value); + } else { + tex_handle_error( + normal_error_type, + "Invalid math class", + "I'm going to ignore this penalty alias." + ); + } + return; + } + case math_parameter_ignore: + { + halfword param = tex_scan_math_parameter(); + if (param >= 0) { + code = internal_int_location(first_math_ignore_code + param); + value = tex_scan_int(1, NULL); + tex_word_define(a, code, value); + } + return; + } + case math_parameter_options: + { + halfword class = tex_scan_math_class_number(0); + if (valid_math_class_code(class)) { + code = internal_int_location(first_math_options_code + class); + value = tex_scan_int(1, NULL); + tex_word_define(a, code, value); + // tex_assign_internal_int_value(a, code, value); + } else { + tex_handle_error( + normal_error_type, + "Invalid math class", + "I'm going to ignore these options." + ); + } + return; + } + case math_parameter_set_defaults: + tex_set_default_math_codes(); + return; + } + { + halfword style = tex_scan_math_style_identifier(0, 1); + halfword indirect = indirect_math_regular; + int freeze = is_frozen(a) && cur_mode == mmode; + if (! freeze && is_inherited(a)) { + tex_aux_skip_optional_equal(); + /* maybe also let inherit from another mathparam but that can become circular */ + switch (math_parameter_value_type(code)) { + case math_int_parameter: + switch (cur_cmd) { + case integer_cmd: + value = cur_cs; + indirect = indirect_math_integer; + break; + case register_int_cmd: + value = cur_chr; + indirect = indirect_math_register_integer; + break; + } + break; + case math_dimen_parameter: + switch (cur_cmd) { + case dimension_cmd: + value = cur_cs; + indirect = indirect_math_dimension; + break; + case register_dimen_cmd: + value = cur_chr; + indirect = indirect_math_register_dimension; + break; + } + break; + case math_muglue_parameter: + switch (cur_cmd) { + case mugluespec_cmd: + value = cur_cs; + indirect = indirect_math_mugluespec; + break; + case register_mu_glue_cmd: + value = cur_chr; + indirect = indirect_math_register_mugluespec; + break; + case internal_mu_glue_cmd: + value = cur_chr; + indirect = indirect_math_internal_mugluespec; + break; + case dimension_cmd: + value = cur_cs; + indirect = indirect_math_dimension; + break; + case register_dimen_cmd: + value = cur_chr; + indirect = indirect_math_register_dimension; + break; + case gluespec_cmd: + value = cur_cs; + indirect = indirect_math_gluespec; + break; + case register_glue_cmd: + value = cur_chr; + indirect = indirect_math_register_gluespec; + break; + case internal_glue_cmd: + value = cur_chr; + indirect = indirect_math_internal_gluespec; + break; + } + break; + case math_pair_parameter: + { + halfword left = tex_scan_math_class_number(0); + halfword right = tex_scan_math_class_number(0); + value = (left << 16) + right; + } + break; + } + if (indirect == indirect_math_regular) { + tex_handle_error( + normal_error_type, + "Invalid inherited math parameter type", + "The inheritance type should match the math parameter type" + ); + return; + } + } else { + switch (math_parameter_value_type(code)) { + case math_int_parameter: + value = tex_scan_int(1, NULL); + break; + case math_dimen_parameter: + value = tex_scan_dimen(0, 0, 0, 1, NULL); + break; + case math_muglue_parameter: + value = tex_scan_glue(mu_val_level, 1); + break; + case math_style_parameter: + value = tex_scan_int(1, NULL); + if (value < 0 || value > last_math_style_variant) { + /* maybe a warning */ + value = math_normal_style_variant; + } + break; + case math_pair_parameter: + { + halfword left = tex_scan_math_class_number(0); + halfword right = tex_scan_math_class_number(0); + value = (left << 16) + right; + } + break; + default: + tex_confusion("math parameter type"); + return; + } + } + if (freeze) { + halfword n = tex_new_node(parameter_node, (quarterword) style); + parameter_name(n) = code; + parameter_value(n) = value; + attach_current_attribute_list(n); + tex_tail_append(n); + } else { + switch (style) { + case all_display_styles: + tex_set_display_styles(code, value, global_or_local(a), indirect); + break; + case all_text_styles: + tex_set_text_styles(code, value, global_or_local(a), indirect); + break; + case all_script_styles: + tex_set_script_styles(code, value, global_or_local(a), indirect); + break; + case all_script_script_styles: + tex_set_script_script_styles(code, value, global_or_local(a), indirect); + break; + case all_math_styles: + tex_set_all_styles(code, value, global_or_local(a), indirect); + break; + case all_split_styles: + tex_set_split_styles(code, value, global_or_local(a), indirect); + break; + case all_uncramped_styles: + tex_set_uncramped_styles(code, value, global_or_local(a), indirect); + break; + case all_cramped_styles: + tex_set_cramped_styles(code, value, global_or_local(a), indirect); + break; + default: + tex_def_math_parameter(style, code, value, global_or_local(a), indirect); + break; + } + + } + } +} + +/* */ + +static void tex_aux_set_define_family(int a) +{ + halfword p = cur_chr; + halfword fnt; + halfword fam = tex_scan_math_family_number(); + tex_scan_optional_equals(); + fnt = tex_scan_font_identifier(NULL); + tex_def_fam_fnt(fam, p, fnt, global_or_local(a)); +} + +/*tex Similar routines are used to assign values to the numeric parameters. */ + +static void tex_aux_set_internal_int(int a) +{ + halfword p = cur_chr; + halfword v = tex_scan_int(1, NULL); + tex_assign_internal_int_value(a, p, v); +} + +static void tex_aux_set_register_int(int a) +{ + halfword p = cur_chr; + halfword v = tex_scan_int(1, NULL); + tex_word_define(a, p, v); +} + +static void tex_aux_set_internal_attr(int a) +{ + halfword p = cur_chr; + halfword v = tex_scan_int(1, NULL); + if (internal_attribute_number(p) > lmt_node_memory_state.max_used_attribute) { + lmt_node_memory_state.max_used_attribute = internal_attribute_number(p); + } + change_attribute_register(a, p, v); + tex_word_define(a, p, v); +} + +static void tex_aux_set_register_attr(int a) +{ + halfword p = cur_chr; + halfword v = tex_scan_int(1, NULL); + if (register_attribute_number(p) > lmt_node_memory_state.max_used_attribute) { + lmt_node_memory_state.max_used_attribute = register_attribute_number(p); + } + change_attribute_register(a, p, v); + tex_word_define(a, p, v); +} + +static void tex_aux_set_internal_dimen(int a) +{ + halfword p = cur_chr; + scaled v = tex_scan_dimen(0, 0, 0, 1, NULL); + tex_assign_internal_dimen_value(a, p, v); +} + +static void tex_aux_set_register_dimen(int a) +{ + halfword p = cur_chr; + scaled v = tex_scan_dimen(0, 0, 0, 1, NULL); + tex_word_define(a, p, v); +} + +static void tex_aux_set_internal_glue(int a) +{ + halfword p = cur_chr; + halfword v = tex_scan_glue(glue_val_level, 1); + // define(a, p, internal_glue_ref_cmd, v); + tex_assign_internal_skip_value(a, p, v); +} + +static void tex_aux_set_register_glue(int a) +{ + halfword p = cur_chr; + halfword v = tex_scan_glue(glue_val_level, 1); + tex_define(a, p, register_glue_reference_cmd, v); +} + +static void tex_aux_set_internal_mu_glue(int a) +{ + halfword p = cur_chr; + halfword v = tex_scan_glue(mu_val_level, 1); + tex_define(a, p, internal_mu_glue_reference_cmd, v); +} + +static void tex_aux_set_register_mu_glue(int a) +{ + halfword p = cur_chr; + halfword v = tex_scan_glue(mu_val_level, 1); + tex_define(a, p, register_mu_glue_reference_cmd, v); +} + +/*tex + We ignore prefixes that don't apply as we might apply then in the future: just like |\immediate| + so it's not that alien. And maybe frozen can be applied some day in other cases as well. As + reference we keep the old code (long and outer code has been removed elsewhere.) Most of the + calls are the only call so the functions are likely to be inlined. + +*/ + +static void tex_aux_set_combine_toks(halfword a) +{ + if (is_global(a)) { + switch (cur_chr) { + case expanded_toks_code: cur_chr = global_expanded_toks_code; break; + case append_toks_code: cur_chr = global_append_toks_code; break; + case append_expanded_toks_code: cur_chr = global_append_expanded_toks_code; break; + case prepend_toks_code: cur_chr = global_prepend_toks_code; break; + case prepend_expanded_toks_code: cur_chr = global_prepend_expanded_toks_code; break; + } + } + tex_run_combine_the_toks(); +} + +static int tex_aux_set_some_item(halfword a) +{ + (void) a; + switch (cur_chr) { + case lastpenalty_code: + lmt_page_builder_state.last_penalty = tex_scan_int(1, NULL); + return 1; + case lastkern_code: + lmt_page_builder_state.last_kern = tex_scan_int(1, NULL); + return 1; + case lastskip_code: + lmt_page_builder_state.last_glue = tex_scan_glue(glue_val_level, 1); + return 1; + case lastboundary_code: + lmt_page_builder_state.last_penalty = tex_scan_int(1, NULL); + return 1; + case last_node_type_code: + lmt_page_builder_state.last_node_type = tex_scan_int(1, NULL); + return 1; + case last_node_subtype_code: + lmt_page_builder_state.last_node_subtype = tex_scan_int(1, NULL); + return 1; + case last_left_class_code: + lmt_math_state.last_left = tex_scan_math_class_number(1); + return 1; + case last_right_class_code: + lmt_math_state.last_right = tex_scan_math_class_number(1); + return 1; + case last_atom_class_code: + lmt_math_state.last_atom = tex_scan_math_class_number(1); + return 1; + default: + return 0; + } +} + +void tex_run_prefixed_command(void) +{ + /*tex accumulated prefix codes so far */ + int flags = 0; + int force = 0; + halfword lastprefix = -1; + while (cur_cmd == prefix_cmd) { + switch (cur_chr) { + case frozen_code: flags = add_frozen_flag (flags); break; + case tolerant_code: flags = add_tolerant_flag (flags); break; + case protected_code: flags = add_protected_flag (flags); break; + case permanent_code: flags = add_permanent_flag (flags); break; + case immutable_code: flags = add_immutable_flag (flags); break; + case mutable_code: flags = add_mutable_flag (flags); break; + case noaligned_code: flags = add_noaligned_flag (flags); break; + case instance_code: flags = add_instance_flag (flags); break; + case untraced_code: flags = add_untraced_flag (flags); break; + case global_code: flags = add_global_flag (flags); break; + case overloaded_code: flags = add_overloaded_flag (flags); break; + case aliased_code: flags = add_aliased_flag (flags); break; + case immediate_code: flags = add_immediate_flag (flags); break; + case semiprotected_code: flags = add_semiprotected_flag(flags); break; + /*tex This one is bound. */ + case always_code: flags = add_aliased_flag (flags); force = 1; break; + /*tex This one is special */ + case inherited_code: flags = add_inherited_flag (flags); break; + default: + goto PICKUP; + } + lastprefix = cur_chr; + PICKUP: + /*tex We no longer report prefixes. */ + do { + tex_get_x_token(); + } while (cur_cmd == spacer_cmd || cur_cmd == relax_cmd); + if (tracing_commands_par > 2) { + tex_show_cmd_chr(cur_cmd, cur_chr); + } + } + + /*tex: Here we can quit when we have a constant! */ + + /*tex + Adjust for the setting of |\globaldefs|. + */ + if (global_defs_par) { + flags = global_defs_par > 0 ? add_global_flag(flags) : remove_global_flag(flags); + } + /*tex + Now we arrived at all the def variants. We only apply the prefixes that make sense (for + now). + */ + switch (cur_cmd) { + case set_font_cmd: + tex_aux_set_font(flags); + break; + case def_cmd: + tex_aux_set_def(flags, force); + break; + case let_cmd: + tex_aux_set_let(flags, force); + break; + case shorthand_def_cmd: + tex_aux_set_shorthand_def(flags, force); + break; + case internal_toks_cmd: + case register_toks_cmd: + tex_aux_set_assign_toks(flags); + break; + case internal_int_cmd: + tex_aux_set_internal_int(flags); + break; + case register_int_cmd: + tex_aux_set_register_int(flags); + break; + case internal_attribute_cmd: + tex_aux_set_internal_attr(flags); + break; + case register_attribute_cmd: + tex_aux_set_register_attr(flags); + break; + case internal_dimen_cmd: + tex_aux_set_internal_dimen(flags); + break; + case register_dimen_cmd: + tex_aux_set_register_dimen(flags); + break; + case internal_glue_cmd: + tex_aux_set_internal_glue(flags); + break; + case register_glue_cmd: + tex_aux_set_register_glue(flags); + break; + case internal_mu_glue_cmd: + tex_aux_set_internal_mu_glue(flags); + break; + case register_mu_glue_cmd: + tex_aux_set_register_mu_glue(flags); + break; + case lua_value_cmd: + tex_aux_set_lua_value(flags); + break; + case define_char_code_cmd: + tex_aux_set_define_char_code(flags); + break; + case define_family_cmd: + tex_aux_set_define_family(flags); + break; + case set_math_parameter_cmd: + tex_aux_set_math_parameter(flags); + break; + case register_cmd: + if (cur_chr == tok_val_level) { + tex_aux_set_assign_toks(flags); + } else { + tex_aux_set_register(flags); + } + break; + case arithmic_cmd: + tex_aux_arithmic_register(flags, cur_chr); + break; + case set_box_cmd: + tex_aux_set_box(flags); + break; + case set_auxiliary_cmd: + tex_aux_set_auxiliary(flags); + break; + case set_page_property_cmd: + tex_aux_set_page_property(); + break; + case set_box_property_cmd: + tex_aux_set_box_property(); + break; + case set_specification_cmd: + tex_aux_set_specification(flags); + break; + case hyphenation_cmd: + tex_aux_set_hyph_data(); + break; + case set_font_property_cmd: + tex_aux_set_font_property(); + break; + case define_font_cmd: + tex_aux_set_define_font(flags); + break; + case set_interaction_cmd: + tex_aux_set_interaction(cur_chr); + break; + case combine_toks_cmd: + tex_aux_set_combine_toks(flags); + break; + case some_item_cmd: + if (! tex_aux_set_some_item(flags)) { + tex_aux_run_illegal_case(); + } + break; + default: + if (lastprefix < 0) { + tex_confusion("prefixed command"); + } else { + tex_handle_error( + normal_error_type, + "You can't use a prefix %C with %C", + prefix_cmd, lastprefix, cur_cmd, cur_chr, + "A prefix should be followed by a quantity that can be assigned to. Intermediate\n" + "spaces and \\relax tokens are gobbled in the process.\n" + ); + break; + } + } + /*tex + End of assignments cases. We insert a token saved by |\afterassignment|, if any. + */ + tex_aux_finish_after_assignment(); +} + +/*tex + + When a control sequence is to be defined, by |\def| or |\let| or something similar, the + |get_r_token| routine will substitute a special control sequence for a token that is not + redefinable. + +*/ + +void tex_get_r_token(void) +{ + RESTART: + do { + tex_get_token(); + } while (cur_tok == space_token); + if (eqtb_valid_cs(cur_cs)) { + if (cur_cs == 0) { + tex_back_input(cur_tok); + } + cur_tok = deep_frozen_protection_token; + /* moved down but this might interfere with input on the console */ + tex_handle_error( + insert_error_type, + "Missing control sequence inserted", + "Please don't say '\\def cs{...}', say '\\def\\cs{...}'. I've inserted an\n" + "inaccessible control sequence so that your definition will be completed without\n" + "mixing me up too badly.\n" + ); + goto RESTART; + } +} + +/*tex + Some of the internal int values need a special treatment. This used to be a more complex + function, also dealing with other registers than didn't really need a check, also because we + now split into internals and registers. + + Beware: the post binary and relation penalties are not synchronzed here because we assume a + proper overload of the primitive. They can still be set and their setting is reflected in the + atom panalties but that's all. No need for more code. +*/ + +void tex_assign_internal_int_value(int a, halfword p, int val) +{ + switch (internal_int_number(p)) { + case par_direction_code: + { + check_direction_value(val); + tex_word_define(a, p, val); + } + break; + case math_direction_code: + { + check_direction_value(val); + tex_word_define(a, p, val); + } + break; + case text_direction_code: + { + check_direction_value(val); + tex_inject_text_or_line_dir(val, 0); + tex_word_define(a, p, val); + /*tex Plus: */ + update_tex_internal_dir_state(internal_dir_state_par + 1); + } + break; + case line_direction_code: + { + check_direction_value(val); + tex_inject_text_or_line_dir(val, 1); + p = internal_int_location(text_direction_code); + tex_word_define(a, p, val); + /*tex Plus: */ + update_tex_internal_dir_state(internal_dir_state_par + 1); + } + break; + case cat_code_table_code: + if (tex_valid_catcode_table(val)) { + if (val != cat_code_table_par) { + tex_word_define(a, p, val); + } + } else { + tex_handle_error( + normal_error_type, + "Invalid \\catcode table", + "You can only switch to a \\catcode table that is initialized using\n" + "\\savecatcodetable or \\initcatcodetable, or to table 0" + ); + } + break; + case glyph_scale_code: + case glyph_x_scale_code: + case glyph_y_scale_code: + if (! val) { + /* maybe an error message */ + return; + } else { + /* todo: check for reasonable */ + goto DEFINE; + } + case glyph_text_scale_code: + case glyph_script_scale_code: + case glyph_scriptscript_scale_code: + /* here zero is a signal */ + if (val < min_limited_scale || val > max_limited_scale) { + tex_handle_error( + normal_error_type, + "Invalid \\glyph..scale", + "The value for \\glyph..scale has to be between 0 and 1000 where\n" + "a value of zero forces font percentage scaling to be used." + ); + val = max_limited_scale; + } + goto DEFINE; + case math_begin_class_code: + case math_end_class_code: + case math_left_class_code: + case math_right_class_code: + if (! valid_math_class_code(val)) { + val = unset_noad_class; + } + tex_word_define(a, p, val); + break; + case output_box_code: + if (val < 0 || val > max_box_index) { + tex_handle_error( + normal_error_type, + "Invalid \\outputbox", + "The value for \\outputbox has to be between 0 and " LMT_TOSTRING(max_box_index) "." + ); + } else { + tex_word_define(a, p, val); + } + break; + case new_line_char_code: + if (val > max_newline_character) { + tex_handle_error( + normal_error_type, + "Invalid \\newlinechar", + "The value for \\newlinechar has to be no higher than " LMT_TOSTRING(max_newline_character) ".\n" + "Your invalid assignment will be ignored." + ); + } + else { + tex_word_define(a, p, val); + } + break; + case end_line_char_code: + if (val > 127) { + tex_handle_error( + normal_error_type, + "Invalid \\endlinechar", + "The value for \\endlinechar has to be no higher than 127." + ); + } + else { + tex_word_define(a, p, val); + } + break; + case language_code: + /* this is |\language| */ + if (val < 0) { + val = 0; + } + if (tex_is_valid_language(val)) { + update_tex_language(a, val); + } + else { + tex_handle_error( + normal_error_type, + "Invalid \\language", + "The value for \\language has to be defined and in the range 0 .. " LMT_TOSTRING(max_n_of_languages) "." + ); + } + break; + case font_code: + if (val < 0) { + val = 0; + } + if (tex_is_valid_font(val)) { + tex_set_cur_font(a, val); + } + else { + tex_handle_error( + normal_error_type, + "Invalid \\fontid", + "The value for \\fontid has to be defined and in the range 0 .. " LMT_TOSTRING(max_n_of_fonts) "." + ); + } + break; + case hyphenation_mode_code: + if (val < 0) { + val = 0; + } + /* We don't update |\uchyph| here. */ + tex_word_define(a, p, val); + break; + case uc_hyph_code: + /*tex For old times sake. */ + tex_word_define(a, p, val); + /*tex But we do use this instead. */ + val = val ? set_hyphenation_mode(hyphenation_mode_par, uppercase_hyphenation_mode) : unset_hyphenation_mode(hyphenation_mode_par, uppercase_hyphenation_mode); + tex_word_define(a, internal_int_location(hyphenation_mode_code), val); + break; + case local_interline_penalty_code: + case local_broken_penalty_code: + /*tex + If we are defining subparagraph penalty levels while we are in hmode, then we + put out a whatsit immediately, otherwise we leave it alone. This mechanism might + not be sufficiently powerful, and some other algorithm, searching down the stack, + might be necessary. Good first step. + */ + tex_word_define(a, p, val); + if (cur_mode == hmode) { + /*tex Add local paragraph node */ + tex_tail_append(tex_new_par_node(penalty_par_subtype)); + update_tex_internal_par_state(internal_par_state_par + 1); + } + break; + case adjust_spacing_code: + if (val < adjust_spacing_off) { + val = adjust_spacing_off; + } + else if (val > adjust_spacing_font) { + val = adjust_spacing_font; + } + goto DEFINE; + case protrude_chars_code: + if (val < protrude_chars_off) { + val = protrude_chars_off; + } + else if (val > protrude_chars_advanced) { + val = protrude_chars_advanced; + } + goto DEFINE; + case glyph_options_code: + if (val < glyph_option_normal_glyph) { + val = glyph_option_normal_glyph; + } else if (val > glyph_option_all) { + val = glyph_option_all; + } + goto DEFINE; + case overload_mode_code: + if (overload_mode_par == 255) { + return; + } else { + goto DEFINE; + } + /* We only synchronize these four one way. */ + case post_binary_penalty_code: + tex_word_define(a, internal_int_location(first_math_post_penalty_code + binary_noad_subtype), val); + tex_word_define(a, internal_int_location(first_math_display_post_penalty_code + binary_noad_subtype), val); + break; + case post_relation_penalty_code: + tex_word_define(a, internal_int_location(first_math_post_penalty_code + relation_noad_subtype), val); + tex_word_define(a, internal_int_location(first_math_display_post_penalty_code + relation_noad_subtype), val); + break; + case pre_binary_penalty_code: + tex_word_define(a, internal_int_location(first_math_pre_penalty_code + binary_noad_subtype), val); + tex_word_define(a, internal_int_location(first_math_display_pre_penalty_code + binary_noad_subtype), val); + break; + case pre_relation_penalty_code: + tex_word_define(a, internal_int_location(first_math_pre_penalty_code + relation_noad_subtype), val); + tex_word_define(a, internal_int_location(first_math_display_pre_penalty_code + relation_noad_subtype), val); + break; + /* We could do this, but then we also need to do day and check it per month. */ /* + case month_code: + if (val < 1) { + val = 1; + } else if (val > 12) { + val = 12; + } + goto DEFINE; + */ + default: + DEFINE: + tex_word_define(a, p, val); + if (is_frozen(a) && cur_mode == hmode) { + tex_update_par_par(internal_int_cmd, internal_int_number(p)); + } + } +} + +void tex_assign_internal_attribute_value(int a, halfword p, int val) +{ + if (register_attribute_number(p) > lmt_node_memory_state.max_used_attribute) { + lmt_node_memory_state.max_used_attribute = register_attribute_number(p); + } + change_attribute_register(a, p, val); + tex_word_define(a, p, val); +} + +void tex_assign_internal_dimen_value(int a, halfword p, int val) +{ + tex_word_define(a, p, val); + if (is_frozen(a) && cur_mode == hmode) { + tex_update_par_par(internal_dimen_cmd, internal_dimen_number(p)); + } +} + +void tex_assign_internal_skip_value(int a, halfword p, int val) +{ + tex_define(a, p, internal_glue_reference_cmd, val); + if (is_frozen(a) && cur_mode == hmode) { + tex_update_par_par(internal_glue_cmd, internal_glue_number(p)); + } +} + +/*tex + + Here is a procedure that might be called \quotation {Get the next non-blank non-relax non-call + non-assignment token}. It is a runner used in text accents and math alignments. It probably + has to be adapted to the additional command codes that we have. + +*/ + +void tex_handle_assignments(void) +{ + while (1) { + do { + tex_get_x_token(); + } while (cur_cmd == spacer_cmd || cur_cmd == relax_cmd); + if (cur_cmd <= max_non_prefixed_cmd) { + return; + } else { + lmt_error_state.set_box_allowed = 0; + tex_run_prefixed_command(); + lmt_error_state.set_box_allowed = 1; + } + } +} + +/*tex Has the long |\errmessage| help been used? */ + +static strnumber tex_aux_scan_string(void) +{ + int saved_selector = lmt_print_state.selector; /*tex holds |selector| setting */ + halfword result = tex_scan_toks_expand(0, NULL, 0); + // saved_selector = lmt_print_state.selector; + lmt_print_state.selector = new_string_selector_code; + tex_token_show(result, extreme_token_show_max); + tex_flush_token_list(result); + lmt_print_state.selector = saved_selector; + return tex_make_string(); /* todo: we can use take_string instead but happens only @ error */ +} + +static void tex_aux_run_message(void) +{ + switch (cur_chr) { + case message_code: + { + /*tex Print string |s| on the terminal */ + strnumber s = tex_aux_scan_string(); + if ((lmt_print_state.terminal_offset > 0) || (lmt_print_state.logfile_offset > 0)) { + tex_print_char(' '); + } + tex_print_tex_str(s); + tex_terminal_update(); + tex_flush_str(s); + break; + } + case error_message_code: + { + /*tex + Print string |s| as an error message. If |\errmessage| occurs often in + |scroll_mode|, without user-defined |\errhelp|, we don't want to give a long + help message each time. So we give a verbose explanation only once. These + help messages are not expanded because that could itself generate an error. + */ + strnumber s = tex_aux_scan_string(); + if (error_help_par) { + strnumber helpinfo = tex_tokens_to_string(error_help_par); + char *h = tex_makecstring(helpinfo); + tex_handle_error( + normal_error_type, + "%T", + s, + h + ); + lmt_memory_free(h); + tex_flush_str(helpinfo); + } else if (lmt_error_state.long_help_seen) { + tex_handle_error( + normal_error_type, + "%T", + s, + "(That was another \\errmessage.)" + ); + } else { + if (lmt_error_state.interaction < error_stop_mode) { + lmt_error_state.long_help_seen = 1; + } + tex_handle_error( + normal_error_type, + "%T", + s, + "This error message was generated by an \\errmessage command, so I can't give any\n" + "explicit help. Pretend that you're Hercule Poirot: Examine all clues, and deduce\n" + "the truth by order and method." + ); + } + tex_flush_str(s); + break; + } + } +} + +/*tex + + The |\uppercase| and |\lowercase| commands are implemented by building a token list and then + changing the cases of the letters in it. + + Change the case of the token in |p|, if a change is appropriate. When the case of a |chr_code| + changes, we don't change the |cmd|. We also change active characters. (The last fact permits + trickery.) + +*/ + +static void tex_aux_run_shift_case(void) +{ + int upper = cur_chr == upper_case_code; + halfword l = tex_scan_toks_normal(0, NULL); + halfword p = token_link(l); + while (p) { + halfword t = token_info(p); + if (t < cs_token_flag) { + halfword c = t % cs_offset_value; + halfword i = upper ? tex_get_uc_code(c) : tex_get_lc_code(c); + if (i) { + set_token_info(p, t - c + i); + } + } else if (tex_is_active_cs(cs_text(t - cs_token_flag))) { + halfword c = active_cs_value(cs_text(t - cs_token_flag)); + halfword i = upper ? tex_get_uc_code(c) : tex_get_lc_code(c); + if (i) { + set_token_info(p, tex_active_to_cs(i, 1) + cs_token_flag); + } + } + p = token_link(p); + } + tex_begin_backed_up_list(token_link(l)); + tex_put_available_token(l); +} + +/*tex + + We come finally to the last pieces missing from |main_control|, namely the |\show| commands that + are useful when debugging. + +*/ + +static void tex_aux_run_show_whatever(void) +{ + int justshow = 1; + switch (cur_chr) { + case show_code: + /*tex Show the current meaning of a token, then |goto common_ending|. */ + { + tex_get_token(); + tex_print_nlp(); + tex_print_str("> "); + if (cur_cs != 0) { + tex_print_cs(cur_cs); + tex_print_char('='); + } + tex_print_meaning(meaning_full_code); + goto COMMON_ENDING; + } + case show_box_code: + /*tex Show the current contents of a box. */ + { + int nolevels = 0; + int diagnose = 0; + int content = 0; + int online = 0; + int max = 0; + while (1) { + switch (tex_scan_character("ocdnaOCDNA", 0, 0, 0)) { + case 'a': case 'A': + if (tex_scan_mandate_keyword("all", 1)) { + max = 1; + } + break; + case 'c': case 'C': + if (tex_scan_mandate_keyword("content", 1)) { + content = 1; + } + break; + case 'd': case 'D': + if (tex_scan_mandate_keyword("diagnose", 1)) { + diagnose = 1; + } + break; + case 'n': case 'N': + if (tex_scan_mandate_keyword("nolevels", 1)) { + nolevels = 1; + } + break; + case 'o': case 'O': + if (tex_scan_mandate_keyword("online", 1)) { + online = 1; + } + break; + default: + goto DONE; + } + } + DONE: + /*tex This can become a general helper. */ + { + halfword n = tex_scan_box_register_number(); + halfword r = box_register(n); + halfword l = tracing_levels_par; + halfword o = tracing_online_par; + halfword d = show_box_depth_par; + halfword b = show_box_breadth_par; + if (nolevels) { + tracing_levels_par = 0; + } + if (online) { + tracing_online_par = 2; + } + if (max) { + show_box_depth_par = max_integer; + show_box_breadth_par = max_integer; + } + if (diagnose) { + tex_begin_diagnostic(); + } + if (! content) { + tex_print_str("> \\box"); + tex_print_int(n); + tex_print_char('='); + } + if (r) { + tex_show_box(r); + } else { + tex_print_str("void"); + } + if (diagnose) { + tex_end_diagnostic(); + } + tracing_levels_par = l; + tracing_online_par = o; + show_box_depth_par = d; + show_box_breadth_par = b; + } + break; + } + case show_the_code: + { + halfword head = tex_the_value_toks(1, NULL, 0); + tex_print_nlp(); + tex_print_str("> "); + tex_show_token_list(head, null, default_token_show_max, 0); + tex_flush_token_list(head); + goto COMMON_ENDING; + } + case show_lists_code: + { + tex_begin_diagnostic(); + tex_show_activities(); + tex_end_diagnostic(); + break; + } + case show_groups_code: + { + tex_begin_diagnostic(); + tex_show_save_groups(); + tex_end_diagnostic(); + break; + } + case show_tokens_code: + { + halfword head = tex_the_detokenized_toks(NULL); + tex_print_nlp(); + tex_print_str("> "); + tex_show_token_list(head, null, default_token_show_max, 0); + tex_flush_token_list(head); + goto COMMON_ENDING; + } + case show_ifs_code: + { + // if (! justshow) { + tex_begin_diagnostic(); + // } + tex_show_ifs(); + // if (! justshow) { + tex_end_diagnostic(); + // } + break; + } + default: + /* can't happen */ + break; + } + if (justshow) { + return; + } else { + /*tex By default we |justshow| now so the next is dead code. */ + } + /*tex Complete a potentially long |\show| command: */ + tex_handle_error_message_only("OK"); + if (lmt_print_state.selector == terminal_and_logfile_selector_code && tracing_online_par <= 0) { + lmt_print_state.selector = terminal_selector_code; + tex_print_str(" (see the transcript file)"); /*tex Here |transcript| means |log|.*/ + lmt_print_state.selector = terminal_and_logfile_selector_code; + } + COMMON_ENDING: + if (justshow) { + return; + } else if (lmt_error_state.interaction < error_stop_mode) { + tex_handle_error( + normal_error_type, + NULL, /* no message */ + NULL /* no help */ + ); + --lmt_error_state.error_count; + /* } else if (tracing_online_par > 0) { */ + } else { + tex_handle_error( + normal_error_type, + NULL, /* no message */ + "This isn't an error message; I'm just \\showing something.\n" + ); + } +} + +/*tex + + These procedures get things started properly. The initializer sets up the function table. We + have a few aliases to run_functions that are also used otherwise. + + We actually only have some 50 cases where there is a difference between the modes and it makes + sense now to combine the handling and move the mode checking to those combined functions. That + way we get a switch no longer a jump. Actually, some already share a function and check for the + mode. On the other hand, this is how \TEX\ does it. + + When we have version 2.10 released I might move the mode tests to the runners so that we get a + smaller case cq. jump table and we might also go for mode 1 permanently. A side effect will be + that some commands codes will be collapsed (move and such). + +*/ + +# if (main_control_mode == 0) + +# define register_runner(A,B,C,D) \ + jump_table[vmode+(A)] = B; \ + jump_table[hmode+(A)] = C; \ + jump_table[mmode+(A)] = D + +# define register_simple(A,B) \ + jump_table[vmode+(A)] = B; \ + jump_table[hmode+(A)] = B; \ + jump_table[mmode+(A)] = B + +# define register_asmath(A,B,C) \ + jump_table[vmode+(A)] = B; \ + jump_table[hmode+(A)] = B; \ + jump_table[mmode+(A)] = C + +inline static void init_main_control(void) +{ + + jump_table = lmt_memory_malloc((mmode + max_command_cmd + 1) * sizeof(main_control_function)) ; + + if (jump_table) { + +# elif (main_control_mode == 1) + +# define register_runner(A,B,C,D) \ + case A: \ + switch (mode) { \ + case vmode: B(); break; \ + case hmode: C(); break; \ + case mmode: D(); break; \ + } \ + break + +# define register_simple(A,B) \ + case A: B(); break + +# define register_asmath(A,B,C) \ + case A: if (mode == mmode) { C(); } else { B(); } break + +inline static void tex_aux_big_switch(int mode, int cmd) +{ + + switch (cmd) { + +# else + +# define register_runner(A,B,C,D) \ + case (vmode + A): B(); break; \ + case (hmode + A): C(); break; \ + case (mmode + A): D(); break; + +# define register_simple(A,B) \ + case (vmode + A): B(); break; \ + case (hmode + A): B(); break; \ + case (mmode + A): B(); break; + +# define register_asmath(A,B,C) \ + case (vmode + A): B(); break; \ + case (hmode + A): B(); break; \ + case (mmode + A): C(); break; + +inline static void tex_aux_big_switch(int mode, int cmd) +{ + + switch (mode + cmd) { + +# endif + + /*tex These have the same handler for each mode: */ + + register_simple(arithmic_cmd, tex_run_prefixed_command); + register_simple(register_attribute_cmd, tex_run_prefixed_command); + register_simple(internal_attribute_cmd, tex_run_prefixed_command); + register_simple(register_dimen_cmd, tex_run_prefixed_command); + register_simple(internal_dimen_cmd, tex_run_prefixed_command); + register_simple(set_font_property_cmd, tex_run_prefixed_command); + register_simple(register_glue_cmd, tex_run_prefixed_command); + register_simple(internal_glue_cmd, tex_run_prefixed_command); + register_simple(register_int_cmd, tex_run_prefixed_command); + register_simple(internal_int_cmd, tex_run_prefixed_command); + register_simple(register_mu_glue_cmd, tex_run_prefixed_command); + register_simple(internal_mu_glue_cmd, tex_run_prefixed_command); + register_simple(register_toks_cmd, tex_run_prefixed_command); + register_simple(internal_toks_cmd, tex_run_prefixed_command); + register_simple(define_char_code_cmd, tex_run_prefixed_command); + register_simple(def_cmd, tex_run_prefixed_command); + register_simple(define_family_cmd, tex_run_prefixed_command); + register_simple(define_font_cmd, tex_run_prefixed_command); + register_simple(hyphenation_cmd, tex_run_prefixed_command); + register_simple(let_cmd, tex_run_prefixed_command); + register_simple(prefix_cmd, tex_run_prefixed_command); + register_simple(register_cmd, tex_run_prefixed_command); + register_simple(set_auxiliary_cmd, tex_run_prefixed_command); + register_simple(set_box_cmd, tex_run_prefixed_command); + register_simple(set_box_property_cmd, tex_run_prefixed_command); + register_simple(set_font_cmd, tex_run_prefixed_command); + register_simple(set_interaction_cmd, tex_run_prefixed_command); + register_simple(set_math_parameter_cmd, tex_run_prefixed_command); + register_simple(set_page_property_cmd, tex_run_prefixed_command); + register_simple(set_specification_cmd, tex_run_prefixed_command); + register_simple(shorthand_def_cmd, tex_run_prefixed_command); + register_simple(lua_value_cmd, tex_run_prefixed_command); + + register_simple(integer_cmd, tex_aux_run_illegal_case); /*tex This is better than |run_relax|. */ + register_simple(dimension_cmd, tex_aux_run_illegal_case); /*tex This is better than |run_relax|. */ + register_simple(gluespec_cmd, tex_aux_run_illegal_case); /*tex This is better than |run_relax|. */ + register_simple(mugluespec_cmd, tex_aux_run_illegal_case); /*tex This is better than |run_relax|. */ + + register_simple(fontspec_cmd, tex_run_font_spec); + + // register_simple(some_item_cmd, tex_aux_run_illegal_case); + register_simple(some_item_cmd, tex_run_prefixed_command); + register_simple(iterator_value_cmd, tex_aux_run_illegal_case); + register_simple(parameter_cmd, tex_aux_run_illegal_case); + + register_simple(after_something_cmd, tex_aux_run_after_something); + register_simple(begin_group_cmd, tex_aux_run_begin_group); + register_simple(penalty_cmd, tex_aux_run_penalty); + register_simple(case_shift_cmd, tex_aux_run_shift_case); + register_simple(catcode_table_cmd, tex_aux_run_catcode_table); + register_simple(combine_toks_cmd, tex_run_prefixed_command); + // register_simple(combine_toks_cmd, tex_run_combine_the_toks); + register_simple(end_cs_name_cmd, tex_aux_run_cs_error); + register_simple(end_group_cmd, tex_aux_run_end_group); + register_simple(end_local_cmd, tex_aux_run_end_local); + register_simple(ignore_something_cmd, tex_aux_run_ignore_something); + register_simple(insert_cmd, tex_run_insert); + register_simple(kern_cmd, tex_aux_run_kern); + register_simple(leader_cmd, tex_aux_run_leader); + register_simple(legacy_cmd, tex_aux_run_legacy); + register_simple(local_box_cmd, tex_aux_run_local_box); + register_simple(lua_protected_call_cmd, tex_aux_run_lua_protected_call); + register_simple(lua_function_call_cmd, tex_aux_run_lua_function_call); + register_simple(make_box_cmd, tex_aux_run_make_box); + register_simple(set_mark_cmd, tex_run_mark); + register_simple(message_cmd, tex_aux_run_message); + register_simple(node_cmd, tex_aux_run_node); + register_simple(relax_cmd, tex_aux_run_relax); + register_simple(remove_item_cmd, tex_aux_run_remove_item); + register_simple(right_brace_cmd, tex_aux_run_right_brace); + register_simple(vcenter_cmd, tex_run_vcenter); + register_simple(xray_cmd, tex_aux_run_show_whatever); + + register_simple(alignment_cmd, tex_run_alignment_error); + register_simple(end_template_cmd, tex_run_alignment_end_template); + register_simple(alignment_tab_cmd, tex_run_alignment_error); + + /*tex These have different handlers but a common h/v mode: */ + + register_asmath(math_fraction_cmd, tex_aux_run_insert_dollar_sign, tex_run_math_fraction); + register_asmath(delimiter_number_cmd, tex_aux_run_insert_dollar_sign, tex_run_math_delimiter_number); + register_asmath(math_fence_cmd, tex_aux_run_insert_dollar_sign, tex_run_math_fence); + register_asmath(math_modifier_cmd, tex_aux_run_insert_dollar_sign, tex_run_math_modifier); + register_asmath(math_accent_cmd, tex_aux_run_insert_dollar_sign, tex_run_math_accent); + register_asmath(math_choice_cmd, tex_aux_run_insert_dollar_sign, tex_run_math_choice); + register_asmath(math_component_cmd, tex_aux_run_insert_dollar_sign, tex_run_math_math_component); + register_asmath(math_style_cmd, tex_aux_run_insert_dollar_sign, tex_run_math_style); + register_asmath(mkern_cmd, tex_aux_run_insert_dollar_sign, tex_aux_run_mkern); + register_asmath(mskip_cmd, tex_aux_run_insert_dollar_sign, tex_aux_run_mglue); + register_asmath(math_radical_cmd, tex_aux_run_insert_dollar_sign, tex_run_math_radical); + register_asmath(subscript_cmd, tex_aux_run_insert_dollar_sign, tex_run_math_script); + register_asmath(superscript_cmd, tex_aux_run_insert_dollar_sign, tex_run_math_script); + register_asmath(math_script_cmd, tex_aux_run_insert_dollar_sign, tex_run_math_script); + + register_asmath(equation_number_cmd, tex_aux_run_illegal_case, tex_run_math_equation_number); + + register_asmath(left_brace_cmd, tex_aux_run_left_brace, tex_run_math_left_brace); + + /*tex These have different handlers: */ + + register_runner(italic_correction_cmd, tex_aux_run_illegal_case, tex_aux_run_text_italic_correction, tex_run_math_italic_correction); + register_runner(math_char_number_cmd, tex_aux_run_math_non_math, tex_run_text_math_char_number, tex_run_math_math_char_number); + // register_runner(math_char_given_cmd, tex_aux_run_math_non_math, tex_run_text_math_char_given, tex_run_math_math_char_given); + // register_runner(math_char_xgiven_cmd, tex_aux_run_math_non_math, tex_run_text_math_char_xgiven, tex_run_math_math_char_xgiven); + register_runner(mathspec_cmd, tex_aux_run_math_non_math, tex_run_text_math_spec, tex_run_math_math_spec); + register_runner(vadjust_cmd, tex_aux_run_illegal_case, tex_run_vadjust, tex_run_vadjust); + + register_runner(char_given_cmd, tex_aux_run_new_paragraph, tex_aux_run_text_letter, tex_run_math_letter); + register_runner(other_char_cmd, tex_aux_run_new_paragraph, tex_aux_run_text_letter, tex_run_math_letter); + register_runner(letter_cmd, tex_aux_run_new_paragraph, tex_aux_run_text_letter, tex_run_math_letter); + + register_runner(accent_cmd, tex_aux_run_new_paragraph, tex_aux_run_text_accent, tex_run_math_accent); + register_runner(boundary_cmd, tex_aux_run_par_boundary, tex_aux_run_text_boundary, tex_aux_run_math_boundary); + register_runner(char_number_cmd, tex_aux_run_new_paragraph, tex_aux_run_text_char_number, tex_run_math_char_number); + register_runner(discretionary_cmd, tex_aux_run_new_paragraph, tex_aux_run_discretionary, tex_aux_run_discretionary); + register_runner(explicit_space_cmd, tex_aux_run_new_paragraph, tex_aux_run_space, tex_aux_run_space); + register_runner(math_shift_cmd, tex_aux_run_new_paragraph, tex_run_math_initialize, tex_run_math_shift); + register_runner(math_shift_cs_cmd, tex_aux_run_new_paragraph, tex_run_math_initialize, tex_run_math_shift); + + register_runner(end_paragraph_cmd, tex_aux_run_paragraph_end_vmode, tex_aux_run_paragraph_end_hmode, tex_aux_run_relax); + register_runner(spacer_cmd, tex_aux_run_relax, tex_aux_run_space, tex_aux_run_math_space); + register_runner(begin_paragraph_cmd, tex_aux_run_begin_paragraph_vmode, tex_aux_run_begin_paragraph_hmode, tex_aux_run_begin_paragraph_mmode); + register_runner(end_job_cmd, tex_aux_run_end_job, tex_aux_run_head_for_vmode, tex_aux_run_insert_dollar_sign); + + /*tex + These can share a handler if we move the mode test (we then also have 5 command codes + less) but it becomes less pretty for rules and so. When in the wrong more, a mode change + is enforced and the token is pushed back and ready for a new inspection. + */ + + register_runner(hmove_cmd, tex_aux_run_move, tex_aux_run_illegal_case, tex_aux_run_illegal_case); + register_runner(vmove_cmd, tex_aux_run_illegal_case, tex_aux_run_move, tex_aux_run_move); + + register_runner(hskip_cmd, tex_aux_run_new_paragraph, tex_aux_run_glue, tex_aux_run_glue); + register_runner(vskip_cmd, tex_aux_run_glue, tex_aux_run_head_for_vmode, tex_aux_run_insert_dollar_sign); + + register_runner(un_hbox_cmd, tex_aux_run_new_paragraph, tex_run_unpackage, tex_run_unpackage); + register_runner(un_vbox_cmd, tex_run_unpackage, tex_aux_run_head_for_vmode, tex_aux_run_insert_dollar_sign); + + register_runner(halign_cmd, tex_run_alignment_initialize, tex_aux_run_head_for_vmode, tex_aux_run_halign_mmode); + register_runner(valign_cmd, tex_aux_run_new_paragraph, tex_run_alignment_initialize, tex_aux_run_insert_dollar_sign); + + register_runner(hrule_cmd, tex_aux_run_hrule, tex_aux_run_head_for_vmode, tex_aux_run_insert_dollar_sign); + register_runner(vrule_cmd, tex_aux_run_new_paragraph, tex_aux_run_vrule, tex_aux_run_mrule); + + /* Just in case: */ + + register_runner(ignore_cmd, tex_aux_run_relax, tex_aux_run_relax, tex_aux_run_relax); + + /*tex The next is unlikely to happen but compilers like the check. */ + +# if (main_control_mode == 0) + } else { +# else + default: + printf("cmd code %i", cmd); + tex_confusion("unknown cmd code"); + break; +# endif + } + +} + +# if (main_control_mode == 0) + +inline static void tex_aux_big_switch(int mode, int cmd) +{ + (jump_table[mode + cmd])(); +} + +# endif + +/*tex + Some preset values no longer make sense, like family 1 for some math symbols but we keep them + for compatibility reasons. All settings are moved to the relevant modules. + +*/ + +void tex_initialize_variables(void) +{ + if (lmt_main_state.run_state == initializing_state) { + /* mag_par = 1000; */ + tolerance_par = default_tolerance; + hang_after_par = default_hangafter; + max_dead_cycles_par = default_deadcycles; + math_pre_display_gap_factor_par = default_pre_display_gap; + /* pre_binary_penalty_par = infinite_penalty; */ + /* pre_relation_penalty_par = infinite_penalty; */ + /* math_script_box_mode_par = 1; */ + /* math_script_char_mode_par = 1; */ + /* math_flatten_mode_par = 1; */ /*tex We default to ord */ /* obsolete */ + math_font_control_par = assumed_math_control; + math_eqno_gap_step_par = default_eqno_gap_step; + px_dimen_par = one_bp; + show_node_details_par = 2; /*tex $>1$: |[subtype]| $>2$: |[attributes]| */ + ex_hyphen_char_par = '-'; + escape_char_par = '\\'; + end_line_char_par = '\r'; + output_box_par = default_output_box; + adjust_spacing_step_par = -1; + adjust_spacing_stretch_par = -1; + adjust_spacing_shrink_par = -1; + math_double_script_mode_par = -1, + math_glue_mode_par = default_math_glue_mode; + hyphenation_mode_par = default_hyphenation_mode; + glyph_scale_par = 1000; + glyph_x_scale_par = 1000; + glyph_y_scale_par = 1000; + glyph_x_offset_par = 0; + glyph_y_offset_par = 0; + math_begin_class_par = math_begin_class; + math_end_class_par = math_end_class; + math_left_class_par = unset_noad_class; + math_right_class_par = unset_noad_class; + aux_get_date_and_time(&time_par, &day_par, &month_par, &year_par, &lmt_engine_state.utc_time); + } +} |