summaryrefslogtreecommitdiff
path: root/source/luametatex/source/tex/texmaincontrol.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/luametatex/source/tex/texmaincontrol.c')
-rw-r--r--source/luametatex/source/tex/texmaincontrol.c6412
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, &lt, 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);
+ }
+}