/* maincontrol.c

   Copyright 2009 Taco Hoekwater <taco@luatex.org>

   This file is part of LuaTeX.

   LuaTeX is free software; you can redistribute it and/or modify it under
   the terms of the GNU General Public License as published by the Free
   Software Foundation; either version 2 of the License, or (at your
   option) any later version.

   LuaTeX is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
   License for more details.

   You should have received a copy of the GNU General Public License along
   with LuaTeX; if not, see <http://www.gnu.org/licenses/>. */

static const char _svn_version[] =
    "$Id: maincontrol.c 3013 2009-08-17 19:01:31Z hhenkel $"
    "$URL: http://foundry.supelec.fr/svn/luatex/tags/beta-0.43.0/source/texk/web2c/luatexdir/tex/maincontrol.c $";

#include <ptexlib.h>

#define explicit 1
#define acc_kern 2
#define lp_code_base 2
#define rp_code_base 3
#define ef_code_base 4
#define tag_code 5
#define auto_kern explicit
#define no_lig_code 6


#define prev_depth cur_list.aux_field.cint
#define space_factor cur_list.aux_field.hh.lhfield
#define par_shape_ptr  equiv(par_shape_loc)

#define cur_lang int_par(cur_lang_code)
#define global_defs int_par(global_defs_code)
#define output_box int_par(output_box_code)
#define end_line_char int_par(end_line_char_code)
#define new_line_char int_par(new_line_char_code)
#define tracing_online int_par(tracing_online_code)
#define no_local_whatsits int_par(no_local_whatsits_code)
#define no_local_dirs int_par(no_local_dirs_code)
#define err_help equiv(err_help_loc)
#define text_direction int_par(text_direction_code)
#define every_par equiv(every_par_loc)
#define pdf_ignored_dimen dimen_par(pdf_ignored_dimen_code)
#define par_direction int_par(par_direction_code)

#define pdf_first_line_height  dimen_par(pdf_first_line_height_code)
#define pdf_last_line_depth    dimen_par(pdf_last_line_depth_code)
#define pdf_each_line_height   dimen_par(pdf_each_line_height_code)
#define pdf_each_line_depth    dimen_par(pdf_each_line_depth_code)
#define page_left_offset dimen_par(page_left_offset_code)
#define page_top_offset dimen_par(page_top_offset_code)
#define page_right_offset dimen_par(page_right_offset_code)
#define page_bottom_offset dimen_par(page_bottom_offset_code)
#define pdf_h_origin       dimen_par(pdf_h_origin_code)
#define pdf_v_origin       dimen_par(pdf_v_origin_code)
#define pdf_px_dimen       dimen_par(pdf_px_dimen_code)

#define pdf_image_resolution   int_par(pdf_image_resolution_code)
#define escape_char int_par(escape_char_code)
#define max_dead_cycles int_par(max_dead_cycles_code)
#define tolerance int_par(tolerance_code)
#define mag int_par(mag_code)
#define cat_code_table int_par(cat_code_table_code)

#define par_indent dimen_par(par_indent_code)
#define looseness int_par(looseness_code)
#define space_skip glue_par(space_skip_code)
#define xspace_skip glue_par(xspace_skip_code)
#define every_vbox equiv(every_vbox_loc)

#define split_top_skip glue_par(split_top_skip_code)
#define split_max_depth dimen_par(split_max_depth_code)

#define hang_indent dimen_par(hang_indent_code)
#define hang_after int_par(hang_after_code)
#define inter_line_penalties_ptr equiv(inter_line_penalties_loc)

#define box(A) eqtb[box_base+(A)].hh.rh
#define cur_font equiv(cur_font_loc)
#define hsize dimen_par(hsize_code)
#define ex_hyphen_char int_par(ex_hyphen_char_code)
#define floating_penalty int_par(floating_penalty_code)

#define mode          cur_list.mode_field
#define tail          cur_list.tail_field
#define head          cur_list.head_field
#define prev_graf     cur_list.pg_field
#define dir_save      cur_list.dirs_field

#define check_filter(A) if (!output_active) lua_node_filter_s(buildpage_filter_callback,(A))

#define var_code 7              /* math code meaning ``use the current family'' */

/*
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.
@^brain@>

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.

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 ``action procedure'' that does the work for that case, and then they
either |goto reswitch| or they ``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.
@!@^action procedure@>

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 ``inner loop''; the whole program runs
efficiently when its inner loop is fast, so this part has been written
with particular care.

*/

static halfword main_p;         /* temporary register for list manipulation */
static halfword main_s;         /* space factor value */

/*
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.
*/

void adjust_space_factor(void)
{
    main_s = get_sf_code(cur_chr);
    if (main_s == 1000) {
        space_factor = 1000;
    } else if (main_s < 1000) {
        if (main_s > 0)
            space_factor = main_s;
    } else if (space_factor < 1000) {
        space_factor = 1000;
    } else {
        space_factor = main_s;
    }
}

/*
From Knuth: ``Having |font_glue| allocated for each text font saves
both time and memory.''  That may be true, but it also punches through
the API wall for fonts, so I removed that -- Taco. But a bit of caching
is very welcome, which is why I need to have the next two globals:
*/

internal_font_number space_spec_font;
halfword space_spec_cache;

/* for mode-independent commands, the following macros is useful: */

#define any_mode(A) vmode+(A): case hmode+(A): case mmode+(A)

/*
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.
*/

#define non_math(A) vmode+(A): case hmode+(A)


void main_control(void)
{                               /* governs \TeX's activities */
    integer t;                  /* general-purpose temporary variable */
    halfword p;                 /* for whatsit nodes and testing whether an auto kern should be inserted */
    integer chr_stack;          /* to temporarily save an |cur_chr| to be appended */
    mathcodeval mval;           /* to build up an argument to |set_math_char| */
    t = 0;                      /* for -Wall */
    chr_stack = -1;
    if (equiv(every_job_loc) != null)
        begin_token_list(equiv(every_job_loc), every_job_text);

  BIG_SWITCH:
    get_x_token();

  RESWITCH:
    /* 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 (interrupt != 0) {
        if (OK_to_interrupt) {
            back_input();
            check_interrupt();
            goto BIG_SWITCH;
        }
    }
    if (int_par(tracing_commands_code) > 0)
        show_cur_cmd_chr();

    switch (abs(mode) + cur_cmd) {
    case hmode + letter_cmd:
    case hmode + other_char_cmd:
    case hmode + char_given_cmd:
    case hmode + char_num_cmd:
        if (abs(mode) + cur_cmd == hmode + char_num_cmd) {
            scan_char_num();
            cur_chr = cur_val;
        }
        if (is_last_ocp(current_ocp_lstack, current_ocp_no)) {
            /* Append character |cur_chr| and the following characters (if~any)
               to the current hlist in the current font; |goto reswitch| when
               a non-character has been fetched */
          CONTINUE:
            adjust_space_factor();
            chr_stack = cur_chr;
            tail_append(new_char(cur_font, chr_stack));
            get_x_token();
            if ((cur_cmd == letter_cmd) || (cur_cmd == other_char_cmd) ||
                (cur_cmd == char_given_cmd) || (cur_cmd == char_num_cmd)) {
                if (cur_cmd == char_num_cmd) {
                    scan_char_num();
                    cur_chr = cur_val;
                }
                if ((chr_stack == ex_hyphen_char) && (cur_chr != ex_hyphen_char)
                    && (mode > 0)) {
                    tail = compound_word_break(tail, cur_lang);
                    subtype(tail) = automatic_disc;
                }
                goto CONTINUE;
            } else {
                if ((chr_stack == ex_hyphen_char) && (mode > 0)) {
                    tail = compound_word_break(tail, cur_lang);
                    subtype(tail) = automatic_disc;
                }
                chr_stack = -1;
                goto RESWITCH;
            }

        } else {
            /* Create a buffer with character |cur_chr| and the following
               characters (if~any) and then apply the current active OCP filter
               to this buffer */
            run_ocp();
            goto BIG_SWITCH;

        }
        break;
    case hmode + spacer_cmd:
    case hmode + ex_space_cmd:
    case mmode + ex_space_cmd:
        if ((abs(mode) + cur_cmd == hmode + spacer_cmd)
            && (!(space_factor == 1000))) {
            app_space();
        } else {
            /* Append a normal inter-word space to the current list */
            /* The occurrence of blank spaces is almost part of \TeX's inner loop,
               @^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.
             */
            if (space_skip == zero_glue) {
                /* Find the glue specification, |main_p|, for
                   text spaces in the current font */
                if (cur_font != space_spec_font) {
                    if (space_spec_cache != zero_glue)
                        delete_glue_ref(space_spec_cache);
                    space_spec_cache = new_spec(zero_glue);
                    width(space_spec_cache) = space(cur_font);
                    stretch(space_spec_cache) = space_stretch(cur_font);
                    shrink(space_spec_cache) = space_shrink(cur_font);
                    space_spec_font = cur_font;
                }
                main_p = space_spec_cache;

                temp_ptr = new_glue(main_p);
            } else {
                temp_ptr = new_param_glue(space_skip_code);
            }
            vlink(tail) = temp_ptr;
            tail = temp_ptr;

        }
        break;

    case hmode + no_boundary_cmd:
        /* Append a |cancel_boundary_node| */
        new_whatsit(cancel_boundary_node);
        break;
    case hmode + char_ghost_cmd:
        t = cur_chr;
        get_x_token();
        if ((cur_cmd == letter_cmd) || (cur_cmd == other_char_cmd)
            || (cur_cmd == char_given_cmd) || (cur_cmd == char_num_cmd)) {
            p = new_glyph(get_cur_font(), cur_chr);
            if (t == 0) {
                set_is_leftghost(p);
            } else {
                set_is_rightghost(p);
            }
            tail_append(p);
        }
        break;
        /* Cases of |main_control| that are not part of the inner loop */
    case any_mode(relax_cmd):
    case vmode + spacer_cmd:
    case mmode + spacer_cmd:
    case mmode + no_boundary_cmd:
        break;
    case any_mode(ignore_spaces_cmd):
        if (cur_chr == 0) {
            /* Get the next non-blank non-call... */
            do {
                get_x_token();
            } while (cur_cmd == spacer_cmd);

            goto RESWITCH;
        } else {
            t = scanner_status;
            scanner_status = normal;
            get_token_lua();
            scanner_status = t;
            cur_cs = prim_lookup(cs_text(cur_cs));
            if (cur_cs != undefined_primitive) {
                cur_cmd = get_prim_eq_type(cur_cs);
                cur_chr = get_prim_equiv(cur_cs);
                cur_tok = (cur_cmd * STRING_OFFSET) + cur_chr;
                goto RESWITCH;
            }
        }
        break;
    case vmode + stop_cmd:
        if (its_all_over())
            return;             /* this is the only way out */
        break;
        /* Math cases in non-math modes */
    case vmode + math_char_num_cmd:
    case vmode + math_given_cmd:
    case vmode + omath_given_cmd:
    case vmode + xmath_given_cmd:
        back_input();
        new_graf(true);
        break;
    case hmode + math_char_num_cmd:
        if (cur_chr == 0)
            mval = scan_mathchar(tex_mathcode);
        else if (cur_chr == 1)
            mval = scan_mathchar(aleph_mathcode);
        else if (cur_chr == 2)
            mval = scan_mathchar(xetex_mathcode);
        else
            mval = scan_mathchar(xetexnum_mathcode);
        math_char_in_text(mval);
        break;
    case hmode + math_given_cmd:
        mval = mathchar_from_integer(cur_chr, tex_mathcode);
        math_char_in_text(mval);
        break;
    case hmode + omath_given_cmd:
        mval = mathchar_from_integer(cur_chr, aleph_mathcode);
        math_char_in_text(mval);
        break;
    case hmode + xmath_given_cmd:
        mval = mathchar_from_integer(cur_chr, xetex_mathcode);
        math_char_in_text(mval);
        break;

        /* Forbidden cases detected in |main_control|@> */
    case vmode + vmove_cmd:
    case hmode + hmove_cmd:
    case mmode + hmove_cmd:
    case any_mode(last_item_cmd):
    case vmode + vadjust_cmd:
    case vmode + ital_corr_cmd:
    case non_math(eq_no_cmd):
    case any_mode(mac_param_cmd):
        /* 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.  Such cases are enumerated
           here and in the other sections referred to under `See also \dots.'
         */
        report_illegal_case();
        break;

        /* Math-only cases in non-math modes, or vice versa */
    case non_math(sup_mark_cmd):
    case non_math(sub_mark_cmd):
    case non_math(super_sub_script_cmd):
    case non_math(math_comp_cmd):
    case non_math(delim_num_cmd):
    case non_math(left_right_cmd):
    case non_math(above_cmd):
    case non_math(radical_cmd):
    case non_math(math_style_cmd):
    case non_math(math_choice_cmd):
    case non_math(vcenter_cmd):
    case non_math(non_script_cmd):
    case non_math(mkern_cmd):
    case non_math(limit_switch_cmd):
    case non_math(mskip_cmd):
    case non_math(math_accent_cmd):
    case mmode + endv_cmd:
    case mmode + par_end_cmd:
    case mmode + stop_cmd:
    case mmode + vskip_cmd:
    case mmode + un_vbox_cmd:
    case mmode + valign_cmd:
    case mmode + hrule_cmd:
        insert_dollar_sign();
        break;
        /* Cases of |main_control| that build boxes and lists */
    case vmode + hrule_cmd:
    case hmode + vrule_cmd:
    case mmode + vrule_cmd:
        /* 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 ``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:=pdf_ignored_dimen|.
         */
        tail_append(scan_rule_spec());
        if (abs(mode) == vmode)
            prev_depth = pdf_ignored_dimen;
        else if (abs(mode) == hmode)
            space_factor = 1000;
        break;

    case vmode + vskip_cmd:
    case hmode + hskip_cmd:
    case mmode + hskip_cmd:
    case mmode + mskip_cmd:
        /* The processing of things like \.{\\hskip} and \.{\\vskip} is slightly
           more complicated. But the code in |main_control| is very short, since
           it simply calls on the action routine |append_glue|. Similarly, \.{\\kern}
           activates |append_kern|. */
        append_glue();
        break;
    case any_mode(kern_cmd):
    case mmode + mkern_cmd:
        append_kern();
        break;

    case non_math(left_brace_cmd):
        /* 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\{$\langle\,\hbox{hlist}\,\rangle$\}}' 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. */
        new_save_level(simple_group);
        eq_word_define(int_base + no_local_whatsits_code, 0);
        eq_word_define(int_base + no_local_dirs_code, 0);
        break;
    case any_mode(begin_group_cmd):
        new_save_level(semi_simple_group);
        eq_word_define(int_base + no_local_whatsits_code, 0);
        eq_word_define(int_base + no_local_dirs_code, 0);
        break;
    case any_mode(end_group_cmd):
        if (cur_group == semi_simple_group) {
            fixup_directions();
        } else {
            off_save();
        }
        break;
    case any_mode(right_brace_cmd):
        handle_right_brace();
        break;

    case vmode + hmove_cmd:
    case hmode + vmove_cmd:
    case mmode + vmove_cmd:
        /* 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|.
         */
        t = cur_chr;
        scan_normal_dimen();
        if (t == 0)
            scan_box(cur_val);
        else
            scan_box(-cur_val);
        break;
    case any_mode(leader_ship_cmd):
        scan_box(leader_flag - a_leaders + cur_chr);
        break;
    case any_mode(make_box_cmd):
        begin_box(0);
        break;
    case any_mode(assign_box_dir_cmd):
        scan_register_num();
        cur_box = box(cur_val);
        scan_optional_equals();
        scan_direction();
        if (cur_box != null)
            box_dir(cur_box) = cur_val;
        break;

    case vmode + start_par_cmd:
        /* 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}'.
         */
        new_graf((cur_chr > 0));
        break;
    case hmode + start_par_cmd:
    case mmode + start_par_cmd:
        if (cur_chr != 2)
            indent_in_hmode();
        break;
    case vmode + letter_cmd:
    case vmode + other_char_cmd:
    case vmode + char_num_cmd:
    case vmode + char_given_cmd:
    case vmode + char_ghost_cmd:
    case vmode + math_shift_cmd:
    case vmode + math_shift_cs_cmd:
    case vmode + un_hbox_cmd:
    case vmode + vrule_cmd:
    case vmode + accent_cmd:
    case vmode + discretionary_cmd:
    case vmode + hskip_cmd:
    case vmode + valign_cmd:
    case vmode + ex_space_cmd:
    case vmode + no_boundary_cmd:
        back_input();
        new_graf(true);
        break;

    case vmode + par_end_cmd:
        /* 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}. */
        normal_paragraph();
        if (mode > 0) {
            check_filter("vmode_par");
            build_page();
        }
        break;
    case hmode + par_end_cmd:
        if (align_state < 0)
            off_save();         /* this tries to  recover from an alignment that didn't end properly */
        end_graf(bottom_level); /* this takes us to the enclosing mode, if |mode>0| */
        if (mode == vmode) {
            check_filter("hmode_par");
            build_page();
        }
        break;
    case hmode + stop_cmd:
    case hmode + vskip_cmd:
    case hmode + hrule_cmd:
    case hmode + un_vbox_cmd:
    case hmode + halign_cmd:
        head_for_vmode();
        break;
    case any_mode(insert_cmd):
    case hmode + vadjust_cmd:
    case mmode + vadjust_cmd:
        /* Insertion and adjustment and mark nodes are constructed by the following
           pieces of the program. */
        begin_insert_or_adjust();
        break;
    case any_mode(mark_cmd):
        handle_mark();
        break;
    case any_mode(break_penalty_cmd):
        /* Penalty nodes get into a list via the |break_penalty| command. */
        append_penalty();
        break;
    case any_mode(remove_item_cmd):
        /* 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. */
        delete_last();
        break;
    case vmode + un_vbox_cmd:
    case hmode + un_hbox_cmd:
    case mmode + un_hbox_cmd:
        /* The |un_hbox| and |un_vbox| commands unwrap one of the |number_regs|
           current boxes. */
        unpackage();
        break;
    case hmode + ital_corr_cmd:
        /* Italic corrections are converted to kern nodes when the |ital_corr| 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. */
        append_italic_correction();
        break;
    case mmode + ital_corr_cmd:
        tail_append(new_kern(0));
        break;
    case hmode + discretionary_cmd:
    case mmode + discretionary_cmd:
        append_discretionary();
        break;
    case any_mode(assign_local_box_cmd):
        append_local_box(cur_chr);
        break;
    case hmode + accent_cmd:
        /* We need only one more thing to complete the horizontal mode routines, namely
           the \.{\\accent} primitive. */
        make_accent();
        break;
    case any_mode(car_ret_cmd):
    case any_mode(tab_mark_cmd):
        align_error();
        break;
    case any_mode(no_align_cmd):
        no_align_error();
        break;
    case any_mode(omit_cmd):
        omit_error();
        break;
    case vmode + halign_cmd:
    case hmode + valign_cmd:
        init_align();
        break;
    case mmode + halign_cmd:
        if (privileged()) {
            if (cur_group == math_shift_group)
                init_align();
            else
                off_save();
        }
        break;
    case vmode + endv_cmd:
    case hmode + endv_cmd:
        do_endv();
        break;
    case any_mode(end_cs_name_cmd):
        cs_error();
        break;
    case hmode + math_shift_cmd:
    case hmode + math_shift_cs_cmd:
        init_math();
        break;
    case mmode + eq_no_cmd:
        if (privileged()) {
            if (cur_group == math_shift_group)
                start_eq_no();
            else
                off_save();
        }
        break;
    case mmode + left_brace_cmd:
        math_left_brace();
        break;
    case mmode + letter_cmd:
    case mmode + other_char_cmd:
    case mmode + char_given_cmd:
        set_math_char(get_math_code(cur_chr));
        break;
    case mmode + char_num_cmd:
        scan_char_num();
        cur_chr = cur_val;
        set_math_char(get_math_code(cur_chr));
        break;
    case mmode + math_char_num_cmd:
        if (cur_chr == 0)
            mval = scan_mathchar(tex_mathcode);
        else if (cur_chr == 1)
            mval = scan_mathchar(aleph_mathcode);
        else if (cur_chr == 2)
            mval = scan_mathchar(xetex_mathcode);
        else
            mval = scan_mathchar(xetexnum_mathcode);
        set_math_char(mval);
        break;
    case mmode + math_given_cmd:
        mval = mathchar_from_integer(cur_chr, tex_mathcode);
        set_math_char(mval);
        break;
    case mmode + omath_given_cmd:
        mval = mathchar_from_integer(cur_chr, aleph_mathcode);
        set_math_char(mval);
        break;
    case mmode + xmath_given_cmd:
        mval = mathchar_from_integer(cur_chr, xetex_mathcode);
        set_math_char(mval);
        break;
    case mmode + delim_num_cmd:
        if (cur_chr == 0)
            mval = scan_delimiter_as_mathchar(tex_mathcode);
        else if (cur_chr == 1)
            mval = scan_delimiter_as_mathchar(aleph_mathcode);
        else
            mval = scan_delimiter_as_mathchar(xetex_mathcode);
        set_math_char(mval);
        break;
    case mmode + math_comp_cmd:
        math_math_comp();
        break;
    case mmode + limit_switch_cmd:
        math_limit_switch();
        break;
    case mmode + radical_cmd:
        math_radical();
        break;
    case mmode + accent_cmd:
    case mmode + math_accent_cmd:
        math_ac();
        break;
    case mmode + vcenter_cmd:
        scan_spec(vcenter_group, false);
        normal_paragraph();
        push_nest();
        mode = -vmode;
        prev_depth = pdf_ignored_dimen;
        if (every_vbox != null)
            begin_token_list(every_vbox, every_vbox_text);
        break;
    case mmode + math_style_cmd:
        tail_append(new_style(cur_chr));
        break;
    case mmode + non_script_cmd:
        tail_append(new_glue(zero_glue));
        subtype(tail) = cond_math_glue;
        break;
    case mmode + math_choice_cmd:
        if (cur_chr == 0)
            append_choices();
        else
            setup_math_style();
        break;
    case mmode + above_cmd:
        math_fraction();
        break;
    case mmode + sub_mark_cmd:
    case mmode + sup_mark_cmd:
    case mmode + super_sub_script_cmd:
        sub_sup();
        break;
    case mmode + left_right_cmd:
        math_left_right();
        break;
    case mmode + math_shift_cmd:
    case mmode + math_shift_cs_cmd:
        if (cur_group == math_shift_group)
            after_math();
        else
            off_save();
        break;

        /* Cases of |main_control| that do not depend on |mode| */
        /*
           The long |main_control| procedure has now been fully specified, except for
           certain activities that are independent of the current mode. These activities
           do not change the current vlist or hlist or mlist; if they change anything,
           it is the value of a parameter or the meaning of a control sequence.
         */
    case any_mode(toks_register_cmd):
    case any_mode(assign_toks_cmd):
    case any_mode(assign_int_cmd):
    case any_mode(assign_attr_cmd):
    case any_mode(assign_dir_cmd):
    case any_mode(assign_dimen_cmd):
    case any_mode(assign_glue_cmd):
    case any_mode(assign_mu_glue_cmd):
    case any_mode(assign_font_dimen_cmd):
    case any_mode(assign_font_int_cmd):
    case any_mode(set_aux_cmd):
    case any_mode(set_prev_graf_cmd):
    case any_mode(set_page_dimen_cmd):
    case any_mode(set_page_int_cmd):
    case any_mode(set_box_dimen_cmd):
    case any_mode(set_tex_shape_cmd):
    case any_mode(set_etex_shape_cmd):
    case any_mode(def_char_code_cmd):
    case any_mode(def_del_code_cmd):
    case any_mode(extdef_math_code_cmd):
    case any_mode(extdef_del_code_cmd):
    case any_mode(def_family_cmd):
    case any_mode(set_math_param_cmd):
    case any_mode(set_font_cmd):
    case any_mode(def_font_cmd):
    case any_mode(letterspace_font_cmd):
    case any_mode(pdf_copy_font_cmd):
    case any_mode(register_cmd):
    case any_mode(advance_cmd):
    case any_mode(multiply_cmd):
    case any_mode(divide_cmd):
    case any_mode(prefix_cmd):
    case any_mode(let_cmd):
    case any_mode(shorthand_def_cmd):
    case any_mode(read_to_cs_cmd):
    case any_mode(def_cmd):
    case any_mode(set_box_cmd):
    case any_mode(hyph_data_cmd):
    case any_mode(set_interaction_cmd):
    case any_mode(set_ocp_cmd):
    case any_mode(def_ocp_cmd):
    case any_mode(set_ocp_list_cmd):
    case any_mode(def_ocp_list_cmd):
    case any_mode(clear_ocp_lists_cmd):
    case any_mode(push_ocp_list_cmd):
    case any_mode(pop_ocp_list_cmd):
    case any_mode(ocp_list_op_cmd):
    case any_mode(ocp_trace_level_cmd):
        prefixed_command();
        break;
    case any_mode(after_assignment_cmd):
        get_token();
        after_token = cur_tok;
        break;
    case any_mode(after_group_cmd):
        get_token();
        save_for_after(cur_tok);
        break;
    case any_mode(in_stream_cmd):
        open_or_close_in();
        break;
    case any_mode(message_cmd):
        issue_message();
        break;
    case any_mode(case_shift_cmd):
        shift_case();
        break;
    case any_mode(xray_cmd):
        show_whatever();
        break;

        /* Cases of |main_control| that are for extensions to \TeX */
    case any_mode(extension_cmd):
        do_extension(static_pdf);
        break;
    }                           /* end of the big |switch| statement */

    goto BIG_SWITCH;            /* restart */
}

void app_space(void)
{                               /* handle spaces when |space_factor<>1000| */
    halfword q;                 /* glue node */
    if ((space_factor >= 2000) && (xspace_skip != zero_glue)) {
        q = new_param_glue(xspace_skip_code);
    } else {
        if (space_skip != zero_glue) {
            main_p = new_spec(space_skip);
        } else {
            main_p = new_spec(zero_glue);
            width(main_p) = space(cur_font);
            stretch(main_p) = space_stretch(cur_font);
            shrink(main_p) = space_shrink(cur_font);
        }
        /* Modify the glue specification in |main_p| according to the space factor */
        if (space_factor >= 2000)
            width(main_p) = width(main_p) + extra_space(cur_font);
        stretch(main_p) = xn_over_d(stretch(main_p), space_factor, 1000);
        shrink(main_p) = xn_over_d(shrink(main_p), 1000, space_factor);

        q = new_glue(main_p);
        glue_ref_count(main_p) = null;
    }
    couple_nodes(tail, q);
    tail = q;
}

void insert_dollar_sign(void)
{
    back_input();
    cur_tok = math_shift_token + '$';
    print_err("Missing $ inserted");
    help2("I've inserted a begin-math/end-math symbol since I think",
          "you left one out. Proceed, with fingers crossed.");
    ins_error();
}

/*
The `|you_cant|' procedure prints a line saying that the current command
is illegal in the current mode; it identifies these things symbolically.
*/

void you_cant(void)
{
    print_err("You can't use `");
    print_cmd_chr(cur_cmd, cur_chr);
    print_in_mode(mode);
}

void report_illegal_case(void)
{
    you_cant();
    help4("Sorry, but I'm not programmed to handle this case;",
          "I'll just pretend that you didn''t ask for it.",
          "If you're in the wrong mode, you might be able to",
          "return to the right one by typing `I}' or `I$' or `I\\par'.");
    error();
}

/*
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.
*/

boolean privileged(void)
{
    if (mode > 0) {
        return true;
    } else {
        report_illegal_case();
        return false;
    }
}

/*
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
``dead cycle.''
*/

boolean its_all_over(void)
{                               /* do this when \.{\\end} or \.{\\dump} occurs */
    if (privileged()) {
        if ((page_head == page_tail) && (head == tail) && (dead_cycles == 0)) {
            return true;
        }
        back_input();           /* we will try to end again after ejecting residual material */
        tail_append(new_null_box());
        width(tail) = hsize;
        tail_append(new_glue(fill_glue));
        tail_append(new_penalty(-010000000000));
        lua_node_filter_s(buildpage_filter_callback, "end");
        build_page();           /* append \.{\\hbox to \\hsize\{\}\\vfill\\penalty-'10000000000} */
    }
    return false;
}

/*
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.
*/

void append_glue(void)
{
    int s;                      /* modifier of skip command */
    s = cur_chr;
    switch (s) {
    case fil_code:
        cur_val = fil_glue;
        break;
    case fill_code:
        cur_val = fill_glue;
        break;
    case ss_code:
        cur_val = ss_glue;
        break;
    case fil_neg_code:
        cur_val = fil_neg_glue;
        break;
    case skip_code:
        scan_glue(glue_val_level);
        break;
    case mskip_code:
        scan_glue(mu_val_level);
        break;
    }                           /* now |cur_val| points to the glue specification */
    tail_append(new_glue(cur_val));
    if (s >= skip_code) {
        decr(glue_ref_count(cur_val));
        if (s > skip_code)
            subtype(tail) = mu_glue;
    }
}

void append_kern(void)
{
    int s;                      /* |subtype| of the kern node */
    s = cur_chr;
    scan_dimen((s == mu_glue), false, false);
    tail_append(new_kern(cur_val));
    subtype(tail) = s;
}

/*
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 off_save(void)
{
    halfword p, q;              /* inserted token */
    if (cur_group == bottom_level) {
        /* Drop current token and complain that it was unmatched */
        print_err("Extra ");
        print_cmd_chr(cur_cmd, cur_chr);
        help1("Things are pretty mixed up, but I think the worst is over.");
        error();

    } else {
        back_input();
        p = get_avail();
        set_token_link(temp_token_head, p);
        print_err("Missing ");
        /* 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 semi_simple_group:
            set_token_info(p, cs_token_flag + frozen_end_group);
            tprint_esc("endgroup");
            break;
        case math_shift_group:
            set_token_info(p, math_shift_token + '$');
            print_char('$');
            break;
        case math_left_group:
            set_token_info(p, cs_token_flag + frozen_right);
            q = get_avail();
            set_token_link(p, q);
            p = token_link(p);
            set_token_info(p, other_token + '.');
            tprint_esc("right.");
            break;
        default:
            set_token_info(p, right_brace_token + '}');
            print_char('}');
            break;
        }

        tprint(" inserted");
        ins_list(token_link(temp_token_head));
        help5("I've inserted something that you may have forgotten.",
              "(See the <inserted text> above.)",
              "With luck, this will get me unwedged. But if you",
              "really didn't forget anything, try typing `2' now; then",
              "my insertion and my current dilemma will both disappear.");
        error();
    }
}

/*
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.
*/

void handle_right_brace(void)
{
    halfword p, q;              /* for short-term use */
    scaled d;                   /* holds |split_max_depth| in |insert_group| */
    integer f;                  /* holds |floating_penalty| in |insert_group| */
    p = null;
    switch (cur_group) {
    case simple_group:
        fixup_directions();
        break;
    case bottom_level:
        print_err("Too many }'s");
        help2("You've closed more groups than you opened.",
              "Such booboos are generally harmless, so keep going.");
        error();
        break;
    case semi_simple_group:
    case math_shift_group:
    case math_left_group:
        extra_right_brace();
        break;
    case hbox_group:
        /* 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. */
        package(0);
        break;
    case adjusted_hbox_group:
        adjust_tail = adjust_head;
        pre_adjust_tail = pre_adjust_head;
        package(0);
        break;
    case vbox_group:
        end_graf(vbox_group);
        package(0);
        break;
    case vtop_group:
        end_graf(vtop_group);
        package(vtop_code);
        break;
    case insert_group:
        end_graf(insert_group);
        q = split_top_skip;
        add_glue_ref(q);
        d = split_max_depth;
        f = floating_penalty;
        unsave();
        save_ptr--;
        /* now |saved_value(0)| is the insertion number, or the |vadjust| subtype */
        p = vpack(vlink(head), 0, additional);
        pop_nest();
        if (saved_type(0) == saved_insert) {
            tail_append(new_node(ins_node, saved_value(0)));
            height(tail) = height(p) + depth(p);
            ins_ptr(tail) = list_ptr(p);
            split_top_ptr(tail) = q;
            depth(tail) = d;
            float_cost(tail) = f;
        } else if (saved_type(0) == saved_adjust) {
            tail_append(new_node(adjust_node, saved_value(0)));
            adjust_ptr(tail) = list_ptr(p);
            delete_glue_ref(q);
        } else {
            confusion("insert_group");
        }
        list_ptr(p) = null;
        flush_node(p);
        if (nest_ptr == 0) {
            check_filter("insert");
            build_page();
        }
        break;
    case output_group:
        resume_after_output();
        break;
    case disc_group:
        build_discretionary();
        break;
    case local_box_group:
        build_local_box();
        break;
    case align_group:
        back_input();
        cur_tok = cs_token_flag + frozen_cr;
        print_err("Missing \\cr inserted");
        help1("I'm guessing that you meant to end an alignment here.");
        ins_error();
        break;
    case no_align_group:
        end_graf(no_align_group);
        unsave();
        align_peek();
        break;
    case vcenter_group:
        end_graf(vcenter_group);
        finish_vcenter();
        break;
    case math_choice_group:
        build_choices();
        break;
    case math_group:
        close_math_group(p);
        break;
    default:
        confusion("rightbrace");
        break;
    }
}

void extra_right_brace(void)
{
    print_err("Extra }, or forgotten ");
    switch (cur_group) {
    case semi_simple_group:
        tprint_esc("endgroup");
        break;
    case math_shift_group:
        print_char('$');
        break;
    case math_left_group:
        tprint_esc("right");
        break;
    }
    help5("I've deleted a group-closing symbol because it seems to be",
          "spurious, as in `$x}$'. But perhaps the } is legitimate and",
          "you forgot something else, as in `\\hbox{$x}'. In such cases",
          "the way to recover is to insert both the forgotten and the",
          "deleted material, e.g., by typing `I$}'.");
    error();
    incr(align_state);
}

/*
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 normal_paragraph(void)
{
    if (looseness != 0)
        eq_word_define(int_base + looseness_code, 0);
    if (hang_indent != 0)
        eq_word_define(dimen_base + hang_indent_code, 0);
    if (hang_after != 1)
        eq_word_define(int_base + hang_after_code, 1);
    if (par_shape_ptr != null)
        eq_define(par_shape_loc, shape_ref_cmd, null);
    if (inter_line_penalties_ptr != null)
        eq_define(inter_line_penalties_loc, shape_ref_cmd, null);
}

/*
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.
*/

halfword cur_box;               /* box to be placed into its context */

/*
The |box_end| procedure does the right thing with |cur_box|, if
|box_context| represents the context as explained above.
*/

void box_end(integer box_context)
{
    if (box_context < box_flag) {
        /* Append box |cur_box| to the current list, shifted by |box_context| */
        /*
           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.
         */
        if (cur_box != null) {
            shift_amount(cur_box) = box_context;
            if (abs(mode) == vmode) {
                if (pre_adjust_tail != null) {
                    if (pre_adjust_head != pre_adjust_tail)
                        append_list(pre_adjust_head, pre_adjust_tail);
                    pre_adjust_tail = null;
                }
                append_to_vlist(cur_box);
                if (adjust_tail != null) {
                    if (adjust_head != adjust_tail)
                        append_list(adjust_head, adjust_tail);
                    adjust_tail = null;
                }
                if (mode > 0) {
                    check_filter("box");
                    build_page();
                }
            } else {
                if (abs(mode) == hmode)
                    space_factor = 1000;
                else
                    cur_box = new_sub_box(cur_box);
                vlink(tail) = cur_box;
                tail = cur_box;
            }
        }

    } else if (box_context < ship_out_flag) {
        /* Store |cur_box| in a box register */
        if (box_context < global_box_flag)
            eq_define(box_base + box_context - box_flag, box_ref_cmd, cur_box);
        else
            geq_define(box_base + box_context - global_box_flag, box_ref_cmd,
                       cur_box);

    } else if (cur_box != null) {
        if (box_context > ship_out_flag) {
            /* Append a new leader node that uses |cur_box| */
            /* Get the next non-blank non-relax... */
            do {
                get_x_token();
            } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));

            if (((cur_cmd == hskip_cmd) && (abs(mode) != vmode)) ||
                ((cur_cmd == vskip_cmd) && (abs(mode) == vmode))) {
                append_glue();
                subtype(tail) = box_context - (leader_flag - a_leaders);
                leader_ptr(tail) = cur_box;
            } else {
                print_err("Leaders not followed by proper glue");
                help3
                    ("You should say `\\leaders <box or rule><hskip or vskip>'.",
                     "I found the <box or rule>, but there's no suitable",
                     "<hskip or vskip>, so I'm ignoring these leaders.");
                back_error();
                flush_node_list(cur_box);
            }

        } else
            ship_out(static_pdf, cur_box, true);
    }
}

/* the next input should specify a box or perhaps a rule */
void scan_box(integer box_context)
{
    /* Get the next non-blank non-relax... */
    do {
        get_x_token();
    } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));

    if (cur_cmd == make_box_cmd) {
        begin_box(box_context);
    } else if ((box_context >= leader_flag)
               && ((cur_cmd == hrule_cmd) || (cur_cmd == vrule_cmd))) {
        cur_box = scan_rule_spec();
        box_end(box_context);
    } else {
        print_err("A <box> was supposed to be here");
        help3("I was expecting to see \\hbox or \\vbox or \\copy or \\box or",
              "something like that. So you might find something missing in",
              "your output. But keep trying; you can fix this later.");
        back_error();
    }
}

void new_graf(boolean indented)
{
    halfword p, q, dir_graf_tmp;
    halfword dir_rover;
    prev_graf = 0;
    if ((mode == vmode) || (head != tail)) {
        tail_append(new_param_glue(par_skip_code));
    }
    push_nest();
    mode = hmode;
    space_factor = 1000;
    /* LOCAL: Add local paragraph node */
    tail_append(make_local_par_node());

    if (indented) {
        p = new_null_box();
        box_dir(p) = par_direction;
        width(p) = par_indent;
        tail_append(p);
    }
    q = tail;
    dir_rover = text_dir_ptr;
    while (dir_rover != null) {
        if ((vlink(dir_rover) != null) || (dir_dir(dir_rover) != par_direction)) {
            dir_graf_tmp = new_dir(dir_dir(dir_rover));
            vlink(dir_graf_tmp) = vlink(q);
            vlink(q) = dir_graf_tmp;
        }
        dir_rover = vlink(dir_rover);
    }
    q = head;
    while (vlink(q) != null)
        q = vlink(q);
    tail = q;
    if (every_par != null)
        begin_token_list(every_par, every_par_text);
    if (nest_ptr == 1) {
        check_filter("new_graf");
        build_page();           /* put |par_skip| glue on current page */
    }
}

void indent_in_hmode(void)
{
    halfword p;
    if (cur_chr > 0) {          /* \.{\\indent} */
        p = new_null_box();
        width(p) = par_indent;
        if (abs(mode) == hmode)
            space_factor = 1000;
        else
            p = new_sub_box(p);
        tail_append(p);
    }
}

void head_for_vmode(void)
{
    if (mode < 0) {
        if (cur_cmd != hrule_cmd) {
            off_save();
        } else {
            print_err("You can't use `\\hrule' here except with leaders");
            help2("To put a horizontal rule in an hbox or an alignment,",
                  "you should use \\leaders or \\hrulefill (see The TeXbook).");
            error();
        }
    } else {
        back_input();
        cur_tok = par_token;
        back_input();
        token_type = inserted;
    }
}

/*
TODO (BUG?): |dir_save| 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 is probably
wrong in some way that relates to in-paragraph displays.
*/

void end_graf(integer line_break_context)
{
    if (mode == hmode) {
        if ((head == tail) || (vlink(head) == tail))
            pop_nest();         /* null paragraphs are ignored, all contain a |local_paragraph| node */
        else
            line_break(false, line_break_context);
        if (dir_save != null) {
            flush_node_list(dir_save);
            dir_save = null;
        }
        normal_paragraph();
        error_count = 0;
    }
}


void begin_insert_or_adjust(void)
{
    if (cur_cmd != vadjust_cmd) {
        scan_register_num();
        if (cur_val == output_box) {
            print_err("You can't \\insert");
            print_int(output_box);
            help1("I'm changing to \\insert0; box \\outputbox is special.");
            error();
            cur_val = 0;
        }
        set_saved_record(0, saved_insert, 0, cur_val);
    } else if (scan_keyword("pre")) {
        set_saved_record(0, saved_adjust, 0, 1);
    } else {
        set_saved_record(0, saved_adjust, 0, 0);
    }
    save_ptr++;
    new_save_level(insert_group);
    scan_left_brace();
    normal_paragraph();
    push_nest();
    mode = -vmode;
    prev_depth = pdf_ignored_dimen;
}

/*
I've renamed the |make_mark| procedure to this, because if the
current chr code is 1, then the actual command was \.{\\clearmarks},
which does not generate a mark node but instead destroys the current
mark tokenlists.
*/

void handle_mark(void)
{
    halfword p;                 /* new node */
    halfword c;                 /* the mark class */
    if (cur_chr == clear_marks_code) {
        scan_mark_num();
        c = cur_val;
        delete_top_mark(c);
        delete_bot_mark(c);
        delete_first_mark(c);
        delete_split_first_mark(c);
        delete_split_bot_mark(c);
    } else {
        if (cur_chr == 0) {
            c = 0;
        } else {
            scan_mark_num();
            c = cur_val;
            if (c > biggest_used_mark)
                biggest_used_mark = c;
        }
        p = scan_toks(false, true);
        p = new_node(mark_node, 0);     /* the |subtype| is not used */
        mark_class(p) = c;
        mark_ptr(p) = def_ref;
        couple_nodes(tail, p);
        tail = p;
    }
}


void append_penalty(void)
{
    scan_int();
    tail_append(new_penalty(cur_val));
    if (mode == vmode) {
        check_filter("penalty");
        build_page();
    }
}

/*
When |delete_last| is called, |cur_chr| is the |type| of node that
will be deleted, if present.
*/

void delete_last(void)
{
    halfword p, q;              /* run through the current list */
    if ((mode == vmode) && (tail == head)) {
        /* Apologize for inability to do the operation now,
           unless \.{\\unskip} follows non-glue */
        if ((cur_chr != glue_node) || (last_glue != max_halfword)) {
            you_cant();
            if (cur_chr == kern_node) {
                help2
                    ("Sorry...I usually can't take things from the current page.",
                     "Try `I\\kern-\\lastkern' instead.");
            } else if (cur_chr != glue_node) {
                help2
                    ("Sorry...I usually can't take things from the current page.",
                     "Perhaps you can make the output routine do it.");
            } else {
                help2
                    ("Sorry...I usually can't take things from the current page.",
                     "Try `I\\vskip-\\lastskip' instead.");
            }
            error();
        }

    } else {
        /* todo: clean this up */
        if (!is_char_node(tail)) {
            if (type(tail) == cur_chr) {
                q = head;
                do {
                    p = q;
                    if (!is_char_node(q)) {
                        if (type(q) == disc_node) {
                            if (p == tail)
                                return;
                        }
                    }
                    q = vlink(p);
                } while (q != tail);
                vlink(p) = null;
                flush_node_list(tail);
                tail = p;
            }
        }
    }
}

void unpackage(void)
{
    halfword p;                 /* the box */
    halfword r;                 /* to remove marginal kern nodes */
    int c;                      /* should we copy? */
    halfword s;                 /* for varmem assignment */
    if (cur_chr > copy_code) {
        /* Handle saved items and |goto done| */
        vlink(tail) = disc_ptr[cur_chr];
        disc_ptr[cur_chr] = null;
        goto DONE;
    }
    c = cur_chr;
    scan_register_num();
    p = box(cur_val);
    if (p == null)
        return;
    if ((abs(mode) == mmode)
        || ((abs(mode) == vmode) && (type(p) != vlist_node))
        || ((abs(mode) == hmode) && (type(p) != hlist_node))) {
        print_err("Incompatible list can't be unboxed");
        help3("Sorry, Pandora. (You sneaky devil.)",
              "I refuse to unbox an \\hbox in vertical mode or vice versa.",
              "And I can't open any boxes in math mode.");
        error();
        return;
    }
    if (c == copy_code) {
        s = copy_node_list(list_ptr(p));
        vlink(tail) = s;
    } else {
        vlink(tail) = list_ptr(p);
        box(cur_val) = null;
        list_ptr(p) = null;
        flush_node(p);
    }
  DONE:
    while (vlink(tail) != null) {
        r = vlink(tail);
        if (!is_char_node(r) && (type(r) == margin_kern_node)) {
            vlink(tail) = vlink(r);
            flush_node(r);
        }
        tail = vlink(tail);
    }
}

void append_italic_correction(void)
{
    halfword p;                 /* |char_node| at the tail of the current list */
    internal_font_number f;     /* the font in the |char_node| */
    if (tail != head) {
        if (is_char_node(tail))
            p = tail;
        else
            return;
        f = font(p);
        tail_append(new_kern(char_italic(f, character(p))));
        subtype(tail) = explicit;
    }
}


void append_local_box(integer kind)
{
    incr(save_ptr);
    set_saved_record(-1, saved_boxtype, 0, kind);
    new_save_level(local_box_group);
    scan_left_brace();
    push_nest();
    mode = -hmode;
    space_factor = 1000;
}

/*
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.
*/

void append_discretionary(void)
{
    integer c;
    tail_append(new_disc());
    subtype(tail) = cur_chr;
    if (cur_chr == explicit_disc) {
        c = get_pre_hyphen_char(cur_lang);
        if (c != 0) {
            vlink(pre_break(tail)) = new_char(equiv(cur_font_loc), c);
            alink(vlink(pre_break(tail))) = pre_break(tail);
            tlink(pre_break(tail)) = vlink(pre_break(tail));
        }
        c = get_post_hyphen_char(cur_lang);
        if (c != 0) {
            vlink(post_break(tail)) = new_char(equiv(cur_font_loc), c);
            alink(vlink(post_break(tail))) = post_break(tail);
            tlink(post_break(tail)) = vlink(post_break(tail));
        }
    } else {
        incr(save_ptr);
        set_saved_record(-1, saved_disc, 0, 0);
        new_save_level(disc_group);
        scan_left_brace();
        push_nest();
        mode = -hmode;
        space_factor = 1000;
    }
}

/* The test for |p != null| ensures that empty \.{\\localleftbox} and
    \.{\\localrightbox} commands are not applied.
*/

void build_local_box(void)
{
    halfword p;
    integer kind;
    unsave();
    assert(saved_type(-1) == saved_boxtype);
    kind = saved_value(-1);
    decr(save_ptr);
    p = vlink(head);
    pop_nest();
    if (p != null)
        p = hpack(p, 0, additional);
    if (kind == 0)
        eq_define(local_left_box_base, box_ref_cmd, p);
    else
        eq_define(local_right_box_base, box_ref_cmd, p);
    if (abs(mode) == hmode) {
        /* LOCAL: Add local paragraph node */
        tail_append(make_local_par_node());
    }
    eq_word_define(int_base + no_local_whatsits_code, no_local_whatsits + 1);
}

/*
The three discretionary lists are constructed somewhat as if they were
hboxes. A~subroutine called |build_discretionary| handles the transitions.
(This is sort of fun.)
*/


void build_discretionary(void)
{
    halfword p, q;              /* for link manipulation */
    integer n;                  /* length of discretionary list */
    unsave();
    /* 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=vlink(q)| and there are |n| items preceding |p|. */
    q = head;
    p = vlink(q);
    n = 0;
    while (p != null) {
        if (!is_char_node(p) && type(p) > rule_node && type(p) != kern_node) {
            print_err("Improper discretionary list");
            help1("Discretionary lists must contain only boxes and kerns.");
            error();
            begin_diagnostic();
            tprint_nl("The following discretionary sublist has been deleted:");
            show_box(p);
            end_diagnostic(true);
            flush_node_list(p);
            vlink(q) = null;
            break;
        }
        alink(p) = q;
        q = p;
        p = vlink(q);
        incr(n);
    }

    p = vlink(head);
    pop_nest();
    assert(saved_type(-1) == saved_disc);
    switch (saved_value(-1)) {
    case 0:
        if (n > 0) {
            vlink(pre_break(tail)) = p;
            alink(p) = pre_break(tail);
            tlink(pre_break(tail)) = q;
        }
        break;
    case 1:
        if (n > 0) {
            vlink(post_break(tail)) = p;
            alink(p) = post_break(tail);
            tlink(post_break(tail)) = q;
        }
        break;
    case 2:
        /* Attach list |p| to the current list, and record its length;
           then finish up and |return| */
        if ((n > 0) && (abs(mode) == mmode)) {
            print_err("Illegal math \\discretionary");
            help2("Sorry: The third part of a discretionary break must be",
                  "empty, in math formulas. I had to delete your third part.");
            flush_node_list(p);
            error();
        } else {
            if (n > 0) {
                vlink(no_break(tail)) = p;
                alink(p) = no_break(tail);
                tlink(no_break(tail)) = q;
            }
        }
        decr(save_ptr);
        return;
        break;
    }                           /* there are no other cases */
    set_saved_record(-1, saved_disc, 0, (saved_value(-1) + 1));
    new_save_level(disc_group);
    scan_left_brace();
    push_nest();
    mode = -hmode;
    space_factor = 1000;
}

/*
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\over2}(w-a)+h\cdot t-x\cdot s$.  If either character is
absent from the font, we will simply use the other, without shifting.
*/

void make_accent(void)
{
    double s, t;                /* amount of slant */
    halfword p, q, r;           /* character, box, and kern nodes */
    internal_font_number f;     /* relevant font */
    scaled a, h, x, w, delta;   /* heights and widths, as explained above */
    scan_char_num();
    f = equiv(cur_font_loc);
    p = new_glyph(f, cur_val);
    if (p != null) {
        x = x_height(f);
        s = slant(f) / float_constant(65536);   /* real division */
        a = glyph_width(p);
        do_assignments();
        /* Create a character node |q| for the next character,
           but set |q:=null| if problems arise */
        q = null;
        f = equiv(cur_font_loc);
        if ((cur_cmd == letter_cmd) ||
            (cur_cmd == other_char_cmd) || (cur_cmd == char_given_cmd)) {
            q = new_glyph(f, cur_chr);
        } else if (cur_cmd == char_num_cmd) {
            scan_char_num();
            q = new_glyph(f, cur_val);
        } else {
            back_input();
        }

        if (q != null) {
            /* 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}.
             */
            t = slant(f) / float_constant(65536);       /* real division */
            w = glyph_width(q);
            h = glyph_height(q);
            if (h != x) {       /* the accent must be shifted up or down */
                p = hpack(p, 0, additional);
                shift_amount(p) = x - h;
            }
            delta = round((w - a) / float_constant(2) + h * t - x * s); /* real multiplication */
            r = new_kern(delta);
            subtype(r) = acc_kern;
            couple_nodes(tail, r);
            couple_nodes(r, p);
            tail = new_kern(-a - delta);
            subtype(tail) = acc_kern;
            couple_nodes(p, tail);
            p = q;

        }
        couple_nodes(tail, p);
        tail = p;
        space_factor = 1000;
    }
}

/*
When `\.{\\cr}' or `\.{\\span}' or a tab mark comes through the scanner
into |main_control|, it might be that the user has foolishly inserted
one of them into something that has nothing to do with alignment. But it is
far more likely that a left brace or right brace has been omitted, since
|get_next| takes actions appropriate to alignment only when `\.{\\cr}'
or `\.{\\span}' or tab marks occur with |align_state=0|. The following
program attempts to make an appropriate recovery.
*/

void align_error(void)
{
    if (abs(align_state) > 2) {
        /* Express consternation over the fact that no alignment is in progress */
        print_err("Misplaced ");
        print_cmd_chr(cur_cmd, cur_chr);
        if (cur_tok == tab_token + '&') {
            help6("I can't figure out why you would want to use a tab mark",
                  "here. If you just want an ampersand, the remedy is",
                  "simple: Just type `I\\&' now. But if some right brace",
                  "up above has ended a previous alignment prematurely,",
                  "you're probably due for more error messages, and you",
                  "might try typing `S' now just to see what is salvageable.");
        } else {
            help5("I can't figure out why you would want to use a tab mark",
                  "or \\cr or \\span just now. If something like a right brace",
                  "up above has ended a previous alignment prematurely,",
                  "you're probably due for more error messages, and you",
                  "might try typing `S' now just to see what is salvageable.");
        }
        error();

    } else {
        back_input();
        if (align_state < 0) {
            print_err("Missing { inserted");
            incr(align_state);
            cur_tok = left_brace_token + '{';
        } else {
            print_err("Missing } inserted");
            decr(align_state);
            cur_tok = right_brace_token + '}';
        }
        help3("I've put in what seems to be necessary to fix",
              "the current column of the current alignment.",
              "Try to go on, since this might almost work.");
        ins_error();
    }
}

/*
The help messages here contain a little white lie, since \.{\\noalign}
and \.{\\omit} are allowed also after `\.{\\noalign\{...\}}'.
*/

void no_align_error(void)
{
    print_err("Misplaced \\noalign");
    help2("I expect to see \\noalign only after the \\cr of",
          "an alignment. Proceed, and I'll ignore this case.");
    error();
}

void omit_error(void)
{
    print_err("Misplaced \\omit");
    help2("I expect to see \\omit only after tab marks or the \\cr of",
          "an alignment. Proceed, and I'll ignore this case.");
    error();
}

/*
We've now covered most of the abuses of \.{\\halign} and \.{\\valign}.
Let's take a look at what happens when they are used correctly.

An |align_group| code is supposed to remain on the |save_stack|
during an entire alignment, until |fin_align| removes it.

A devious user might force an |endv| command to occur just about anywhere;
we must defeat such hacks.
*/

void do_endv(void)
{
    base_ptr = input_ptr;
    input_stack[base_ptr] = cur_input;
    while ((input_stack[base_ptr].index_field != v_template) &&
           (input_stack[base_ptr].loc_field == null) &&
           (input_stack[base_ptr].state_field == token_list))
        decr(base_ptr);
    if ((input_stack[base_ptr].index_field != v_template) ||
        (input_stack[base_ptr].loc_field != null) ||
        (input_stack[base_ptr].state_field != token_list))
        fatal_error("(interwoven alignment preambles are not allowed)");
    /*.interwoven alignment preambles... */
    if (cur_group == align_group) {
        end_graf(align_group);
        if (fin_col())
            fin_row();
    } else {
        off_save();
    }
}

/* Finally, \.{\\endcsname} is not supposed to get through to |main_control|. */

void cs_error(void)
{
    print_err("Extra \\endcsname");
    help1("I'm ignoring this, since I wasn't doing a \\csname.");
    error();
}


/*
  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.
*/

/*
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.
*/

#define check_def_code(A) do {						\
	if (((cur_val<0)&&(p<(A)))||(cur_val>n)) {			\
	    print_err("Invalid code (");				\
	    print_int(cur_val);						\
	    if (p<(A))							\
		tprint("), should be in the range 0..");		\
	    else							\
		tprint("), should be at most ");			\
	    print_int(n);						\
	    help1("I'm going to use 0 instead of that illegal code value."); \
	    error();							\
	    cur_val=0;							\
	}								\
    } while (0)


void prefixed_command(void)
{
    int a;                      /* accumulated prefix codes so far */
    internal_font_number f;     /* identifies a font */
    halfword j;                 /* index into a \.{\\parshape} specification */
    halfword p, q;              /* for temporary short-term use */
    integer n;                  /* ditto */
    boolean e;                  /* should a definition be expanded? or was \.{\\let} not done? */
    mathcodeval mval;           /* for handling of \.{\\mathchardef}s */
    a = 0;
    while (cur_cmd == prefix_cmd) {
        if (!odd(a / cur_chr))
            a = a + cur_chr;
        /* Get the next non-blank non-relax... */
        do {
            get_x_token();
        } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));

        if (cur_cmd <= max_non_prefixed_command) {
            /* Discard erroneous prefixes and |return| */
            print_err("You can't use a prefix with `");
            print_cmd_chr(cur_cmd, cur_chr);
            print_char('\'');
            help2
                ("I'll pretend you didn't say \\long or \\outer or \\global or",
                 "\\protected.");
            back_error();
            return;
        }
        if (int_par(tracing_commands_code) > 2)
            show_cur_cmd_chr();
    }
    /* Discard the prefixes \.{\\long} and \.{\\outer} if they are irrelevant */
    if (a >= 8) {
        j = protected_token;
        a = a - 8;
    } else {
        j = 0;
    }
    if ((cur_cmd != def_cmd) && ((a % 4 != 0) || (j != 0))) {
        print_err("You can't use `\\long' or `\\outer' with `");
        print_cmd_chr(cur_cmd, cur_chr);
        print_char('\'');
        help1("I'll pretend you didn't say \\long or \\outer here.");
        error();
    }

    /* Adjust \(f)for the setting of \.{\\globaldefs} */
    if (global_defs != 0) {
        if (global_defs < 0) {
            if (is_global(a))
                a = a - 4;
        } else {
            if (!is_global(a))
                a = a + 4;
        }
    }
    switch (cur_cmd) {
    case set_font_cmd:
        /* Here's an example of the way many of the following routines operate.
           (Unfortunately, they aren't all as simple as this.) */
        define(cur_font_loc, data_cmd, cur_chr);
        break;
    case def_cmd:
        /* 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. */

        if (odd(cur_chr) && !is_global(a) && (global_defs >= 0))
            a = a + 4;
        e = (cur_chr >= 2);
        get_r_token();
        p = cur_cs;
        q = scan_toks(true, e);
        if (j != 0) {
            q = get_avail();
            set_token_info(q, j);
            set_token_link(q, token_link(def_ref));
            set_token_link(def_ref, q);
        }
        define(p, call_cmd + (a % 4), def_ref);
        break;
    case let_cmd:
        n = cur_chr;
        get_r_token();
        p = cur_cs;
        if (n == normal) {
            do {
                get_token();
            } while (cur_cmd == spacer_cmd);
            if (cur_tok == other_token + '=') {
                get_token();
                if (cur_cmd == spacer_cmd)
                    get_token();
            }
        } else {
            get_token();
            q = cur_tok;
            get_token();
            back_input();
            cur_tok = q;
            back_input();       /* look ahead, then back up */
        }                       /* note that |back_input| doesn't affect |cur_cmd|, |cur_chr| */
        if (cur_cmd >= call_cmd)
            add_token_ref(cur_chr);
        define(p, cur_cmd, cur_chr);
        break;
    case shorthand_def_cmd:
        /* 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 ``undefined control sequence'' error or expanding the
           previous meaning.  This allows, for instance, `\.{\\chardef\\foo=123\\foo}'.
         */
        n = cur_chr;
        get_r_token();
        p = cur_cs;
        define(p, relax_cmd, too_big_char);
        scan_optional_equals();
        switch (n) {
        case char_def_code:
            scan_char_num();
            define(p, char_given_cmd, cur_val);
            break;
        case math_char_def_code:
            mval = scan_mathchar(tex_mathcode);
            cur_val =
                (mval.class_value * 16 + mval.family_value) * 256 +
                mval.character_value;
            define(p, math_given_cmd, cur_val);
            break;
        case omath_char_def_code:
            mval = scan_mathchar(aleph_mathcode);
            cur_val =
                (mval.class_value * 256 + mval.family_value) * 65536 +
                mval.character_value;
            define(p, omath_given_cmd, cur_val);
            break;
        case xmath_char_def_code:
            mval = scan_mathchar(xetex_mathcode);
            cur_val =
                (mval.class_value + (8 * mval.family_value)) * (65536 * 32) +
                mval.character_value;
            define(p, xmath_given_cmd, cur_val);
            break;
        default:
            scan_register_num();
            switch (n) {
            case count_def_code:
                define(p, assign_int_cmd, count_base + cur_val);
                break;
            case attribute_def_code:
                define(p, assign_attr_cmd, attribute_base + cur_val);
                break;
            case dimen_def_code:
                define(p, assign_dimen_cmd, scaled_base + cur_val);
                break;
            case skip_def_code:
                define(p, assign_glue_cmd, skip_base + cur_val);
                break;
            case mu_skip_def_code:
                define(p, assign_mu_glue_cmd, mu_skip_base + cur_val);
                break;
            case toks_def_code:
                define(p, assign_toks_cmd, toks_base + cur_val);
                break;
            default:
                confusion("shorthand_def");
                break;
            }
            break;
        }
        break;
    case read_to_cs_cmd:
        j = cur_chr;
        scan_int();
        n = cur_val;
        if (!scan_keyword("to")) {
            print_err("Missing `to' inserted");
            help2("You should have said `\\read<number> to \\cs'.",
                  "I'm going to look for the \\cs now.");
            error();
        }
        get_r_token();
        p = cur_cs;
        read_toks(n, p, j);
        define(p, call_cmd, cur_val);
        break;
    case toks_register_cmd:
    case assign_toks_cmd:
        /* 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.) */
        q = cur_cs;
        if (cur_cmd == toks_register_cmd) {
            scan_register_num();
            p = toks_base + cur_val;
        } else {
            p = cur_chr;        /* |p=every_par_loc| or |output_routine_loc| or \dots */
        }
        scan_optional_equals();
        /* Get the next non-blank non-relax non-call token */
        do {
            get_x_token();
        } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));

        if (cur_cmd != left_brace_cmd) {
            /* If the right-hand side is a token parameter
               or token register, finish the assignment and |goto done| */
            if (cur_cmd == toks_register_cmd) {
                scan_register_num();
                cur_cmd = assign_toks_cmd;
                cur_chr = toks_base + cur_val;
            }
            if (cur_cmd == assign_toks_cmd) {
                q = equiv(cur_chr);
                if (q == null) {
                    define(p, undefined_cs_cmd, null);
                } else {
                    add_token_ref(q);
                    define(p, call_cmd, q);
                }
                goto DONE;
            }
        }
        back_input();
        cur_cs = q;
        q = scan_toks(false, false);
        if (token_link(def_ref) == null) {      /* empty list: revert to the default */
            define(p, undefined_cs_cmd, null);
            free_avail(def_ref);
        } else {
            if (p == output_routine_loc) {      /* enclose in curlies */
                p = get_avail();
                set_token_link(q, p);
                p = output_routine_loc;
                q = token_link(q);
                set_token_info(q, right_brace_token + '}');
                q = get_avail();
                set_token_info(q, left_brace_token + '{');
                set_token_link(q, token_link(def_ref));
                set_token_link(def_ref, q);
            }
            define(p, call_cmd, def_ref);
        }
        break;
    case assign_int_cmd:
        /* Similar routines are used to assign values to the numeric parameters. */
        p = cur_chr;
        scan_optional_equals();
        scan_int();
        assign_internal_value(a, p, cur_val);
        break;
    case assign_attr_cmd:
        p = cur_chr;
        scan_optional_equals();
        scan_int();
        if ((p - attribute_base) > max_used_attr)
            max_used_attr = (p - attribute_base);
        attr_list_cache = cache_disabled;
        word_define(p, cur_val);
        break;
    case assign_dir_cmd:
        /* DIR: Assign direction codes */
        scan_direction();
        switch (cur_chr) {
        case int_base + page_direction_code:
            eq_word_define(int_base + page_direction_code, cur_val);
            break;
        case int_base + body_direction_code:
            eq_word_define(int_base + body_direction_code, cur_val);
            break;
        case int_base + par_direction_code:
            eq_word_define(int_base + par_direction_code, cur_val);
            break;
        case int_base + math_direction_code:
            eq_word_define(int_base + math_direction_code, cur_val);
            break;
        case int_base + text_direction_code:
            if ((no_local_dirs > 0) && (abs(mode) == hmode)) {
                /* DIR: Add local dir node */
                tail_append(new_dir(text_direction));

                dir_dir(tail) = dir_dir(tail);
            }
            if (dir_level(text_dir_ptr) == cur_level) {
                /* DIR: Remove from |text_dir_ptr| */
                halfword text_dir_tmp = vlink(text_dir_ptr);
                flush_node(text_dir_ptr);
                text_dir_ptr = text_dir_tmp;

            }
            eq_word_define(int_base + text_direction_code, cur_val);
            /* DIR: Add to |text_dir_ptr| */
            {
                halfword text_dir_tmp = new_dir(text_direction);
                vlink(text_dir_tmp) = text_dir_ptr;
                text_dir_ptr = text_dir_tmp;
            }
            if (abs(mode) == hmode) {
                /* DIR: Add local dir node */
                tail_append(new_dir(text_direction));

                dir_level(tail) = cur_level;
            }
            eq_word_define(int_base + no_local_dirs_code, no_local_dirs + 1);
            eq_word_define(int_base + level_local_dir_code, cur_level);
            break;
        }
        break;
    case assign_dimen_cmd:
        p = cur_chr;
        scan_optional_equals();
        scan_normal_dimen();
        assign_internal_value(a, p, cur_val);
        break;
    case assign_glue_cmd:
    case assign_mu_glue_cmd:
        p = cur_chr;
        n = cur_cmd;
        scan_optional_equals();
        if (n == assign_mu_glue_cmd)
            scan_glue(mu_val_level);
        else
            scan_glue(glue_val_level);
        trap_zero_glue();
        define(p, glue_ref_cmd, cur_val);
        break;
    case def_char_code_cmd:
    case def_del_code_cmd:
        /* Let |n| be the largest legal code value, based on |cur_chr| */
        if (cur_chr == cat_code_base)
            n = max_char_code;
        else if (cur_chr == sf_code_base)
            n = 077777;
        else
            n = biggest_char;

        p = cur_chr;
        if (cur_chr == math_code_base) {
            if (is_global(a))
                cur_val1 = level_one;
            else
                cur_val1 = cur_level;
            scan_extdef_math_code(cur_val1, tex_mathcode);
        } else if (cur_chr == lc_code_base) {
            scan_char_num();
            p = cur_val;
            scan_optional_equals();
            scan_int();
            check_def_code(lc_code_base);
            define_lc_code(p, cur_val);
        } else if (cur_chr == uc_code_base) {
            scan_char_num();
            p = cur_val;
            scan_optional_equals();
            scan_int();
            check_def_code(uc_code_base);
            define_uc_code(p, cur_val);
        } else if (cur_chr == sf_code_base) {
            scan_char_num();
            p = cur_val;
            scan_optional_equals();
            scan_int();
            check_def_code(sf_code_base);
            define_sf_code(p, cur_val);
        } else if (cur_chr == cat_code_base) {
            scan_char_num();
            p = cur_val;
            scan_optional_equals();
            scan_int();
            check_def_code(cat_code_base);
            define_cat_code(p, cur_val);
        } else if (cur_chr == del_code_base) {
            if (is_global(a))
                cur_val1 = level_one;
            else
                cur_val1 = cur_level;
            scan_extdef_del_code(cur_val1, tex_mathcode);
        }
        break;
    case extdef_math_code_cmd:
    case extdef_del_code_cmd:
        if (is_global(a))
            cur_val1 = level_one;
        else
            cur_val1 = cur_level;
        if (cur_chr == math_code_base)
            scan_extdef_math_code(cur_val1, aleph_mathcode);
        else if (cur_chr == math_code_base + 1)
            scan_extdef_math_code(cur_val1, xetex_mathcode);
        else if (cur_chr == math_code_base + 2)
            scan_extdef_math_code(cur_val1, xetexnum_mathcode);
        else if (cur_chr == del_code_base)
            scan_extdef_del_code(cur_val1, aleph_mathcode);
        else if (cur_chr == del_code_base + 1)
            scan_extdef_del_code(cur_val1, xetex_mathcode);
        else if (cur_chr == del_code_base + 2)
            scan_extdef_del_code(cur_val1, xetexnum_mathcode);
        break;
    case def_family_cmd:
        p = cur_chr;
        scan_math_family_int();
        cur_val1 = cur_val;
        scan_optional_equals();
        scan_font_ident();
        define_fam_fnt(cur_val1, p, cur_val);
        break;
    case set_math_param_cmd:
        p = cur_chr;
        get_token();
        if (cur_cmd != math_style_cmd) {
            print_err("Missing math style, treated as \\displaystyle");
            help1
                ("A style should have been here; I inserted `\\displaystyle'.");
            cur_val1 = display_style;
            back_error();
        } else {
            cur_val1 = cur_chr;
        }
        scan_optional_equals();
        if (p < math_param_first_mu_glue) {
            if (p == math_param_radical_degree_raise)
                scan_int();
            else
                scan_dimen(false, false, false);
        } else {
            scan_glue(mu_val_level);
            trap_zero_glue();
            if (cur_val == glue_par(thin_mu_skip_code))
                cur_val = thin_mu_skip_code;
            else if (cur_val == glue_par(med_mu_skip_code))
                cur_val = med_mu_skip_code;
            else if (cur_val == glue_par(thick_mu_skip_code))
                cur_val = thick_mu_skip_code;
        }
        define_math_param(p, cur_val1, cur_val);
        break;
    case register_cmd:
    case advance_cmd:
    case multiply_cmd:
    case divide_cmd:
        do_register_command(a);
        break;
    case set_box_cmd:
        /* 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. */
        scan_register_num();
        if (is_global(a))
            n = global_box_flag + cur_val;
        else
            n = box_flag + cur_val;
        scan_optional_equals();
        if (set_box_allowed) {
            scan_box(n);
        } else {
            print_err("Improper \\setbox");
            help2("Sorry, \\setbox is not allowed after \\halign in a display,",
                  "or between \\accent and an accented character.");
            error();
        }
        break;
    case set_aux_cmd:
        /* 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. */
        alter_aux();
        break;
    case set_prev_graf_cmd:
        alter_prev_graf();
        break;
    case set_page_dimen_cmd:
        alter_page_so_far();
        break;
    case set_page_int_cmd:
        alter_integer();
        break;
    case set_box_dimen_cmd:
        /* When some dimension of a box register is changed, the change isn't exactly
           global; but \TeX\ does not look at the \.{\\global} switch. */
        alter_box_dimen();
        break;
    case set_tex_shape_cmd:
        q = cur_chr;
        scan_optional_equals();
        scan_int();
        n = cur_val;
        if (n <= 0) {
            p = null;
        } else {
            p = new_node(shape_node, 2 * (n + 1) + 1);
            vinfo(p + 1) = n;
            for (j = 1; j <= n; j++) {
                scan_normal_dimen();
                varmem[p + 2 * j].cint = cur_val;       /* indentation */
                scan_normal_dimen();
                varmem[p + 2 * j + 1].cint = cur_val;   /* width */
            }
        }
        define(q, shape_ref_cmd, p);
        break;
    case set_etex_shape_cmd:
        q = cur_chr;
        scan_optional_equals();
        scan_int();
        n = cur_val;
        if (n <= 0) {
            p = null;
        } else {
            n = (cur_val / 2) + 1;
            p = new_node(shape_node, 2 * n + 1 + 1);
            vinfo(p + 1) = n;
            n = cur_val;
            varmem[p + 2].cint = n;     /* number of penalties */
            for (j = p + 3; j <= p + n + 2; j++) {
                scan_int();
                varmem[j].cint = cur_val;       /* penalty values */
            }
            if (!odd(n))
                varmem[p + n + 3].cint = 0;     /* unused */
        }
        define(q, shape_ref_cmd, p);
        break;
    case hyph_data_cmd:
        /* All of \TeX's parameters are kept in |eqtb| except the font information,
           the interaction mode, and the hyphenation tables; these are strictly global.
         */
        switch (cur_chr) {
        case 0:
            new_hyph_exceptions();
            break;
        case 1:
            new_patterns();
            break;
        case 2:
            new_pre_hyphen_char();
            break;
        case 3:
            new_post_hyphen_char();
            break;
        case 4:
            new_pre_exhyphen_char();
            break;
        case 5:
            new_post_exhyphen_char();
            break;
        }
        break;
    case assign_font_dimen_cmd:
        set_font_dimen();
        break;
    case assign_font_int_cmd:
        n = cur_chr;
        scan_font_ident();
        f = cur_val;
        if (n == no_lig_code) {
            set_no_ligatures(f);
        } else if (n < lp_code_base) {
            scan_optional_equals();
            scan_int();
            if (n == 0)
                set_hyphen_char(f, cur_val);
            else
                set_skew_char(f, cur_val);
        } else {
            scan_char_num();
            p = cur_val;
            scan_optional_equals();
            scan_int();
            switch (n) {
            case lp_code_base:
                set_lp_code(f, p, cur_val);
                break;
            case rp_code_base:
                set_rp_code(f, p, cur_val);
                break;
            case ef_code_base:
                set_ef_code(f, p, cur_val);
                break;
            case tag_code:
                set_tag_code(f, p, cur_val);
                break;
            }
        }
        break;
    case def_font_cmd:
        /* Here is where the information for a new font gets loaded. */
        tex_def_font(a);
        break;
    case letterspace_font_cmd:
        new_letterspaced_font(a);
        break;
    case pdf_copy_font_cmd:
        make_font_copy(a);
        break;
    case set_interaction_cmd:
        new_interaction();
        break;
    case set_ocp_cmd:
        print_err("To use ocps, use the \\pushocplist  primitive");
        print_ln();
        break;
    case def_ocp_cmd:
        new_ocp(a);
        break;
    case set_ocp_list_cmd:
        print_err("To use ocp lists, use the \\pushocplist primitive");
        print_ln();
        break;
    case def_ocp_list_cmd:
        new_ocp_list(a);
        break;
    case push_ocp_list_cmd:
        do_push_ocp_list(a);
        break;
    case pop_ocp_list_cmd:
        do_pop_ocp_list(a);
        break;
    case clear_ocp_lists_cmd:
        do_clear_ocp_lists(a);
        break;
    case ocp_list_op_cmd:
        print_err("To build ocp lists, use the \\ocplist primitive");
        print_ln();
        break;
    case ocp_trace_level_cmd:
        scan_optional_equals();
        scan_int();
        if (cur_val != 0)
            cur_val = 1;
        define(ocp_trace_level_base, data_cmd, cur_val);
        break;
    default:
        confusion("prefix");
        break;
    }                           /* end of Assignments cases */
  DONE:
    /* Insert a token saved by \.{\\afterassignment}, if any */
    if (after_token != 0) {
        cur_tok = after_token;
        back_input();
        after_token = 0;
    }
}

void fixup_directions(void)
{
    integer temp_no_whatsits;
    integer temp_no_dirs;
    integer temporary_dir;
    temp_no_whatsits = no_local_whatsits;
    temp_no_dirs = no_local_dirs;
    temporary_dir = text_direction;
    if (dir_level(text_dir_ptr) == cur_level) {
        /* DIR: Remove from |text_dir_ptr| */
        halfword text_dir_tmp = vlink(text_dir_ptr);
        flush_node(text_dir_ptr);
        text_dir_ptr = text_dir_tmp;

    }
    unsave();
    if (abs(mode) == hmode) {
        if (temp_no_dirs != 0) {
            /* DIR: Add local dir node */
            tail_append(new_dir(text_direction));

            dir_dir(tail) = temporary_dir - 64;
        }
        if (temp_no_whatsits != 0) {
            /* LOCAL: Add local paragraph node */
            tail_append(make_local_par_node());

        }
    }
}

/*
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 get_r_token(void)
{
  RESTART:
    do {
        get_token();
    } while (cur_tok == space_token);
    if ((cur_cs == 0) || (cur_cs > eqtb_top) ||
        ((cur_cs > frozen_control_sequence) && (cur_cs <= eqtb_size))) {
        print_err("Missing control sequence inserted");
        help5("Please don't say `\\def cs{...}', say `\\def\\cs{...}'.",
              "I've inserted an inaccessible control sequence so that your",
              "definition will be completed without mixing me up too badly.",
              "You can recover graciously from this error, if you're",
              "careful; see exercise 27.2 in The TeXbook.");
        if (cur_cs == 0)
            back_input();
        cur_tok = cs_token_flag + frozen_protection;
        ins_error();
        goto RESTART;
    }
}

void assign_internal_value(int a, halfword p, integer cur_val)
{
    halfword n;
    if ((p >= int_base) && (p < attribute_base)) {
        switch ((p - int_base)) {
        case cat_code_table_code:
            if (valid_catcode_table(cur_val)) {
                if (cur_val != int_par(cat_code_table_code))
                    word_define(p, cur_val);
            } else {
                print_err("Invalid \\catcode table");
                help2
                    ("You can only switch to a \\catcode table that is initialized",
                     "using \\savecatcodetable or \\initcatcodetable, or to table 0");
                error();
            }
            break;
        case output_box_code:
            if ((cur_val > 65535) | (cur_val < 0)) {
                print_err("Invalid \\outputbox");
                help1
                    ("The value for \\outputbox has to be between 0 and 65535.");
                error();
            } else {
                word_define(p, cur_val);
            }
            break;
        case new_line_char_code:
            if (cur_val > 127) {
                print_err("Invalid \\newlinechar");
                help1
                    ("The value for \\newlinechar has to be between 0 and 127.");
                error();
            } else if (cur_val < 0) {
                word_define(p, -1);
            } else {
                word_define(p, cur_val);
            }
            break;
        case end_line_char_code:
            if ((cur_val < 0) || (cur_val > biggest_char))
                word_define(p, -1);
            else
                word_define(p, 13);
            break;
        case pdf_compress_level_code:
            static_pdf->compress_level = cur_val;
            word_define(p, cur_val);
            break;
        case pdf_objcompresslevel_code:
            static_pdf->objcompresslevel = cur_val;
            word_define(p, cur_val);
            break;
        case language_code:
            word_define(int_base + cur_lang_code, cur_val);
            word_define(p, cur_val);
            break;
        default:
            word_define(p, cur_val);
            break;
        }
        /* 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. */
        if ((abs(mode) == hmode) &&
            ((p == (int_base + local_inter_line_penalty_code)) ||
             (p == (int_base + local_broken_penalty_code)))) {
            /* LOCAL: Add local paragraph node */
            tail_append(make_local_par_node());

            eq_word_define(int_base + no_local_whatsits_code,
                           no_local_whatsits + 1);
        }

    } else if ((p >= dimen_base) && (p < eqtb_size)) {
        if (p == (dimen_base + page_left_offset_code)) {
            n = cur_val - one_true_inch;
            word_define(dimen_base + h_offset_code, n);
        } else if (p == (dimen_base + h_offset_code)) {
            n = cur_val + one_true_inch;
            word_define(dimen_base + page_left_offset_code, n);
        } else if (p == (dimen_base + page_top_offset_code)) {
            n = cur_val - one_true_inch;
            word_define(dimen_base + v_offset_code, n);
        } else if (p == (dimen_base + v_offset_code)) {
            n = cur_val + one_true_inch;
            word_define(dimen_base + page_top_offset_code, n);
        }
        word_define(p, cur_val);

    } else if ((p >= local_base) && (p < toks_base)) {  /* internal locals  */
        define(p, call_cmd, cur_val);
    } else {
        confusion("assign internal value");
    }
}

/*
When a glue register or parameter becomes zero, it will always point to
|zero_glue| because of the following procedure. (Exception: The tabskip
glue isn't trapped while preambles are being scanned.)
*/

void trap_zero_glue(void)
{
    if ((width(cur_val) == 0) && (stretch(cur_val) == 0)
        && (shrink(cur_val) == 0)) {
        add_glue_ref(zero_glue);
        delete_glue_ref(cur_val);
        cur_val = zero_glue;
    }
}

/* We use the fact that |register<advance<multiply<divide| */

void do_register_command(int a)
{
    halfword l, q, r, s;        /* for list manipulation */
    integer p;                  /* type of register involved */
    q = cur_cmd;
    /* 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. */
    l = 0;
    if (q != register_cmd) {
        get_x_token();
        if ((cur_cmd >= assign_int_cmd) && (cur_cmd <= assign_mu_glue_cmd)) {
            l = cur_chr;
            p = cur_cmd - assign_int_cmd;
            goto FOUND;
        }
        if (cur_cmd != register_cmd) {
            print_err("You can't use `");
            print_cmd_chr(cur_cmd, cur_chr);
            tprint("' after ");
            print_cmd_chr(q, 0);
            help1("I'm forgetting what you said and not changing anything.");
            error();
            return;
        }
    }
    p = cur_chr;
    scan_register_num();
    if (p == int_val_level)
        l = cur_val + count_base;
    else if (p == attr_val_level)
        l = cur_val + attribute_base;
    else if (p == dimen_val_level)
        l = cur_val + scaled_base;
    else if (p == glue_val_level)
        l = cur_val + skip_base;
    else if (p == mu_val_level)
        l = cur_val + mu_skip_base;

  FOUND:
    if (q == register_cmd) {
        scan_optional_equals();
    } else if (scan_keyword("by")) {
        ;                       /* optional `\.{by}' */
    }
    arith_error = false;
    if (q < multiply_cmd) {
        /* Compute result of |register| or |advance|, put it in |cur_val| */
        if (p < glue_val_level) {
            if ((p == int_val_level) || (p == attr_val_level))
                scan_int();
            else
                scan_normal_dimen();
            if (q == advance_cmd)
                cur_val = cur_val + eqtb[l].cint;
        } else {
            scan_glue(p);
            if (q == advance_cmd) {
                /* Compute the sum of two glue specs */
                q = new_spec(cur_val);
                r = equiv(l);
                delete_glue_ref(cur_val);
                width(q) = width(q) + width(r);
                if (stretch(q) == 0) {
                    stretch_order(q) = normal;
                }
                if (stretch_order(q) == stretch_order(r)) {
                    stretch(q) = stretch(q) + stretch(r);
                } else if ((stretch_order(q) < stretch_order(r))
                           && (stretch(r) != 0)) {
                    stretch(q) = stretch(r);
                    stretch_order(q) = stretch_order(r);
                }
                if (shrink(q) == 0) {
                    shrink_order(q) = normal;
                }
                if (shrink_order(q) == shrink_order(r)) {
                    shrink(q) = shrink(q) + shrink(r);
                } else if ((shrink_order(q) < shrink_order(r))
                           && (shrink(r) != 0)) {
                    shrink(q) = shrink(r);
                    shrink_order(q) = shrink_order(r);
                }
                cur_val = q;
            }
        }

    } else {
        /* Compute result of |multiply| or |divide|, put it in |cur_val| */
        scan_int();
        if (p < glue_val_level) {
            if (q == multiply_cmd) {
                if ((p == int_val_level) || (p == attr_val_level)) {
                    cur_val = mult_integers(eqtb[l].cint, cur_val);
                } else {
                    cur_val = nx_plus_y(eqtb[l].cint, cur_val, 0);
                }
            } else {
                cur_val = x_over_n(eqtb[l].cint, cur_val);
            }
        } else {
            s = equiv(l);
            r = new_spec(s);
            if (q == multiply_cmd) {
                width(r) = nx_plus_y(width(s), cur_val, 0);
                stretch(r) = nx_plus_y(stretch(s), cur_val, 0);
                shrink(r) = nx_plus_y(shrink(s), cur_val, 0);
            } else {
                width(r) = x_over_n(width(s), cur_val);
                stretch(r) = x_over_n(stretch(s), cur_val);
                shrink(r) = x_over_n(shrink(s), cur_val);
            }
            cur_val = r;
        }

    }
    if (arith_error) {
        print_err("Arithmetic overflow");
        help2("I can't carry out that multiplication or division,",
              "since the result is out of range.");
        if (p >= glue_val_level)
            delete_glue_ref(cur_val);
        error();
        return;
    }
    if (p < glue_val_level) {
        if (p == attr_val_level) {
            if ((l - attribute_base) > max_used_attr)
                max_used_attr = (l - attribute_base);
            attr_list_cache = cache_disabled;
        }
        if ((p == int_val_level) || (p == dimen_val_level))
            assign_internal_value(a, l, cur_val);
        else
            word_define(l, cur_val);
    } else {
        trap_zero_glue();
        define(l, glue_ref_cmd, cur_val);
    }
}


void alter_aux(void)
{
    halfword c;                 /* |hmode| or |vmode| */
    if (cur_chr != abs(mode)) {
        report_illegal_case();
    } else {
        c = cur_chr;
        scan_optional_equals();
        if (c == vmode) {
            scan_normal_dimen();
            prev_depth = cur_val;
        } else {
            scan_int();
            if ((cur_val <= 0) || (cur_val > 32767)) {
                print_err("Bad space factor");
                help1("I allow only values in the range 1..32767 here.");
                int_error(cur_val);
            } else {
                space_factor = cur_val;
            }
        }
    }
}

void alter_prev_graf(void)
{
    int p;                      /* index into |nest| */
    nest[nest_ptr] = cur_list;
    p = nest_ptr;
    while (abs(nest[p].mode_field) != vmode)
        decr(p);
    scan_optional_equals();
    scan_int();
    if (cur_val < 0) {
        print_err("Bad \\prevgraf");
        help1("I allow only nonnegative values here.");
        int_error(cur_val);
    } else {
        nest[p].pg_field = cur_val;
        cur_list = nest[nest_ptr];
    }
}

void alter_page_so_far(void)
{
    int c;                      /* index into |page_so_far| */
    c = cur_chr;
    scan_optional_equals();
    scan_normal_dimen();
    page_so_far[c] = cur_val;
}

void alter_integer(void)
{
    int c;                      /* 0 for \.{\\deadcycles}, 1 for \.{\\insertpenalties}, etc. */
    c = cur_chr;
    scan_optional_equals();
    scan_int();
    if (c == 0) {
        dead_cycles = cur_val;
    } else if (c == 2) {
        if ((cur_val < batch_mode) || (cur_val > error_stop_mode)) {
            print_err("Bad interaction mode");
            help2("Modes are 0=batch, 1=nonstop, 2=scroll, and",
                  "3=errorstop. Proceed, and I'll ignore this case.");
            int_error(cur_val);
        } else {
            cur_chr = cur_val;
            new_interaction();
        }
    } else {
        insert_penalties = cur_val;
    }
}

void alter_box_dimen(void)
{
    int c;                      /* |width_offset| or |height_offset| or |depth_offset| */
    integer b;                  /* box number */
    c = cur_chr;
    scan_register_num();
    b = cur_val;
    scan_optional_equals();
    scan_normal_dimen();
    if (box(b) != null)
        varmem[box(b) + c].cint = cur_val;
}


void new_interaction(void)
{
    print_ln();
    interaction = cur_chr;
    if (interaction == batch_mode)
        kpsemaketexdiscarderrors = 1;
    else
        kpsemaketexdiscarderrors = 0;
    fixup_selector(log_opened);
}

/*
The \.{\\afterassignment} command puts a token into the global
variable |after_token|. This global variable is examined just after
every assignment has been performed.
*/

halfword after_token;           /* zero, or a saved token */

/*
Here is a procedure that might be called `Get the next non-blank non-relax
non-call non-assignment token'.
*/

void do_assignments(void)
{
    while (true) {
        /* Get the next non-blank non-relax... */
        do {
            get_x_token();
        } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));

        if (cur_cmd <= max_non_prefixed_command)
            return;
        set_box_allowed = false;
        prefixed_command();
        set_box_allowed = true;
    }
}

void open_or_close_in(void)
{
    int c;                      /* 1 for \.{\\openin}, 0 for \.{\\closein} */
    int n;                      /* stream number */
    c = cur_chr;
    scan_four_bit_int();
    n = cur_val;
    if (read_open[n] != closed) {
        lua_a_close_in(read_file[n], (n + 1));
        read_open[n] = closed;
    }
    if (c != 0) {
        scan_optional_equals();
        scan_file_name();
        if (cur_ext == get_nullstr())
            cur_ext = maketexstring(".tex");
        pack_cur_name();
        if (lua_a_open_in(read_file[n], (n + 1))) {
            read_file[n] = name_file_pointer;
            read_open[n] = just_open;
        }
    }
}

boolean long_help_seen;         /* has the long \.{\\errmessage} help been used? */

void issue_message(void)
{
    int old_setting;            /* holds |selector| setting */
    int c;                      /* identifies \.{\\message} and \.{\\errmessage} */
    str_number s;               /* the message */
    c = cur_chr;
    (void) scan_toks(false, true);
    old_setting = selector;
    selector = new_string;
    token_show(def_ref);
    selector = old_setting;
    flush_list(def_ref);
    str_room(1);
    s = make_string();
    if (c == 0) {
        /* Print string |s| on the terminal */
        if (term_offset + str_length(s) > max_print_line - 2)
            print_ln();
        else if ((term_offset > 0) || (file_offset > 0))
            print_char(' ');
        print(s);
        update_terminal();

    } else {
        /* 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. */
        print_err("");
        print(s);
        if (err_help != null) {
            use_err_help = true;
        } else if (long_help_seen) {
            help1("(That was another \\errmessage.)");
        } else {
            if (interaction < error_stop_mode)
                long_help_seen = true;
            help4("This error message was generated by an \\errmessage",
                  "command, so I can't give any explicit help.",
                  "Pretend that you're Hercule Poirot: Examine all clues,",
                  "and deduce the truth by order and method.");
        }
        error();
        use_err_help = false;

    }
    flush_string();
}

/*
The |error| routine calls on |give_err_help| if help is requested from
the |err_help| parameter.
*/

void give_err_help(void)
{
    token_show(err_help);
}


/*
The \.{\\uppercase} and \.{\\lowercase} commands are implemented by
building a token list and then changing the cases of the letters in it.
*/

void shift_case(void)
{
    halfword b;                 /* |lc_code_base| or |uc_code_base| */
    halfword p;                 /* runs through the token list */
    halfword t;                 /* token */
    halfword c;                 /* character code */
    halfword i;                 /* inbetween */
    b = cur_chr;
    p = scan_toks(false, false);
    p = token_link(def_ref);
    while (p != null) {
        /* 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.
         */
        t = token_info(p);
        if (t < cs_token_flag) {
            c = t % STRING_OFFSET;
            if (b == uc_code_base)
                i = get_uc_code(c);
            else
                i = get_lc_code(c);
            if (i != 0)
                set_token_info(p, t - c + i);
        } else if (is_active_cs(cs_text(t - cs_token_flag))) {
            c = active_cs_value(cs_text(t - cs_token_flag));
            if (b == uc_code_base)
                i = get_uc_code(c);
            else
                i = get_lc_code(c);
            if (i != 0)
                set_token_info(p, active_to_cs(i, true) + cs_token_flag);
        }
        p = token_link(p);
    }
    back_list(token_link(def_ref));
    free_avail(def_ref);        /* omit reference count */
}

/*
We come finally to the last pieces missing from |main_control|, namely the
`\.{\\show}' commands that are useful when debugging.
*/

void show_whatever(void)
{
    halfword p;                 /* tail of a token list to show */
    int t;                      /* type of conditional being shown */
    int m;                      /* upper bound on |fi_or_else| codes */
    integer l;                  /* line where that conditional began */
    integer n;                  /* level of \.{\\if...\\fi} nesting */
    switch (cur_chr) {
    case show_lists:
        begin_diagnostic();
        show_activities();
        break;
    case show_box_code:
        /* Show the current contents of a box */
        scan_register_num();
        begin_diagnostic();
        tprint_nl("> \\box");
        print_int(cur_val);
        print_char('=');
        if (box(cur_val) == null)
            tprint("void");
        else
            show_box(box(cur_val));
        break;
    case show_code:
        /* Show the current meaning of a token, then |goto common_ending| */
        get_token();
        if (interaction == error_stop_mode)
            wake_up_terminal();
        tprint_nl("> ");
        if (cur_cs != 0) {
            sprint_cs(cur_cs);
            print_char('=');
        }
        print_meaning();
        goto COMMON_ENDING;
        break;
        /* Cases for |show_whatever| */
    case show_groups:
        begin_diagnostic();
        show_save_groups();
        break;
    case show_ifs:
        begin_diagnostic();
        tprint_nl("");
        print_ln();
        if (cond_ptr == null) {
            tprint_nl("### ");
            tprint("no active conditionals");
        } else {
            p = cond_ptr;
            n = 0;
            do {
                incr(n);
                p = vlink(p);
            } while (p != null);
            p = cond_ptr;
            t = cur_if;
            l = if_line;
            m = if_limit;
            do {
                tprint_nl("### level ");
                print_int(n);
                tprint(": ");
                print_cmd_chr(if_test_cmd, t);
                if (m == fi_code)
                    tprint_esc("else");
                print_if_line(l);
                decr(n);
                t = if_limit_subtype(p);
                l = if_line_field(p);
                m = if_limit_type(p);
                p = vlink(p);
            } while (p != null);
        }
        break;

    default:
        /* Show the current value of some parameter or register,
           then |goto common_ending| */
        p = the_toks();
        if (interaction == error_stop_mode)
            wake_up_terminal();
        tprint_nl("> ");
        token_show(temp_token_head);
        flush_list(token_link(temp_token_head));
        goto COMMON_ENDING;
        break;
    }
    /* Complete a potentially long \.{\\show} command */
    end_diagnostic(true);
    print_err("OK");
    if (selector == term_and_log) {
        if (tracing_online <= 0) {
            selector = term_only;
            tprint(" (see the transcript file)");
            selector = term_and_log;
        }
    }

  COMMON_ENDING:
    if (interaction < error_stop_mode) {
        help0();
        decr(error_count);
    } else if (tracing_online > 0) {
        help3("This isn't an error message; I'm just \\showing something.",
              "Type `I\\show...' to show more (e.g., \\show\\cs,",
              "\\showthe\\count10, \\showbox255, \\showlists).");
    } else {
        help5("This isn't an error message; I'm just \\showing something.",
              "Type `I\\show...' to show more (e.g., \\show\\cs,",
              "\\showthe\\count10, \\showbox255, \\showlists).",
              "And type `I\\tracingonline=1\\show...' to show boxes and",
              "lists on your terminal as well as in the transcript file.");
    }
    error();
}


void initialize(void)
{                               /* this procedure gets things started properly */
    integer k;                  /* index into |mem|, |eqtb|, etc. */
    /* Initialize whatever \TeX\ might access */
    /* Set initial values of key variables */
    initialize_errors();
    initialize_arithmetic();
    max_used_attr = -1;
    attr_list_cache = cache_disabled;
    initialize_nesting();

    /* Start a new current page */
    page_contents = empty;
    page_tail = page_head;
    /* vlink(page_head) = null; */
    last_glue = max_halfword;
    last_penalty = 0;
    last_kern = 0;
    last_node_type = -1;
    page_depth = 0;
    page_max_depth = 0;

    initialize_equivalents();
    no_new_control_sequence = true;     /* new identifiers are usually forbidden */
    init_primitives();

    mag_set = 0;
    initialize_marks();
    initialize_read();

    assert(static_pdf == NULL);
    static_pdf = init_pdf_struct(static_pdf);

    format_ident = 0;
    format_name = get_nullstr();
    for (k = 0; k <= 17; k++)
        write_open[k] = false;
    initialize_directions();
    seconds_and_micros(epochseconds, microseconds);
    init_start_time(static_pdf);

    edit_name_start = 0;
    stop_at_space = true;

    if (ini_version) {
        /* Initialize table entries (done by \.{INITEX} only) */

        init_node_mem(500);
        initialize_tokens();
        /* Initialize the special list heads and constant nodes */
        initialize_alignments();
        initialize_buildpage();

        initialize_active();

        set_eq_type(undefined_control_sequence, undefined_cs_cmd);
        set_equiv(undefined_control_sequence, null);
        set_eq_level(undefined_control_sequence, level_zero);
        for (k = null_cs; k <= (eqtb_top - 1); k++)
            eqtb[k] = eqtb[undefined_control_sequence];
        set_equiv(glue_base, zero_glue);
        set_eq_level(glue_base, level_one);
        set_eq_type(glue_base, glue_ref_cmd);
        for (k = glue_base + 1; k <= local_base - 1; k++)
            eqtb[k] = eqtb[glue_base];
        glue_ref_count(zero_glue) =
            glue_ref_count(zero_glue) + local_base - glue_base;

        par_shape_ptr = null;
        set_eq_type(par_shape_loc, shape_ref_cmd);
        set_eq_level(par_shape_loc, level_one);
        for (k = etex_pen_base; k <= (etex_pens - 1); k++)
            eqtb[k] = eqtb[par_shape_loc];
        for (k = output_routine_loc; k <= toks_base + biggest_reg; k++)
            eqtb[k] = eqtb[undefined_control_sequence];
        box(0) = null;
        set_eq_type(box_base, box_ref_cmd);
        set_eq_level(box_base, level_one);
        for (k = box_base + 1; k <= (box_base + biggest_reg); k++)
            eqtb[k] = eqtb[box_base];
        cur_font = null_font;
        set_eq_type(cur_font_loc, data_cmd);
        set_eq_level(cur_font_loc, level_one);
        set_equiv(cat_code_base, 0);
        set_eq_type(cat_code_base, data_cmd);
        set_eq_level(cat_code_base, level_one);
        eqtb[internal_math_param_base] = eqtb[cat_code_base];
        eqtb[lc_code_base] = eqtb[cat_code_base];
        eqtb[uc_code_base] = eqtb[cat_code_base];
        eqtb[sf_code_base] = eqtb[cat_code_base];
        eqtb[math_code_base] = eqtb[cat_code_base];
        cat_code_table = 0;
        initialize_math_codes();
        initialize_text_codes();
        initex_cat_codes(0);
        for (k = '0'; k <= '9'; k++)
            set_math_code(k, tex_mathcode, var_code, 0, k, level_one);
        for (k = 'A'; k <= 'Z'; k++) {
            set_math_code(k, tex_mathcode, var_code, 1, k, level_one);
            set_math_code((k + 32), tex_mathcode, var_code, 1, (k + 32),
                          level_one);
            set_lc_code(k, k + 32, level_one);
            set_lc_code(k + 32, k + 32, level_one);
            set_uc_code(k, k, level_one);
            set_uc_code(k + 32, k, level_one);
            set_sf_code(k, 999, level_one);
        }
        for (k = int_base; k <= attribute_base - 1; k++)
            eqtb[k].cint = 0;
        for (k = attribute_base; k <= del_code_base - 1; k++)
            eqtb[k].cint = UNUSED_ATTRIBUTE;
        mag = 1000;
        tolerance = 10000;
        hang_after = 1;
        max_dead_cycles = 25;
        escape_char = '\\';
        end_line_char = carriage_return;
        eqtb[int_base + level_local_dir_code].cint = level_one;
        set_del_code('.', tex_mathcode, 0, 0, 0, 0, level_one); /* this null delimiter is used in error recovery */
        ex_hyphen_char = '-';
        output_box = 255;
        for (k = dimen_base; k <= eqtb_size; k++)
            eqtb[k].cint = 0;
        page_left_offset = one_inch;
        page_top_offset = one_inch;
        page_right_offset = one_inch;
        page_bottom_offset = one_inch;
        pdf_ignored_dimen = ignore_depth;
        pdf_each_line_height = pdf_ignored_dimen;
        pdf_each_line_depth = pdf_ignored_dimen;
        pdf_first_line_height = pdf_ignored_dimen;
        pdf_last_line_depth = pdf_ignored_dimen;
        ini_init_primitives();
        hash_used = frozen_control_sequence;    /* nothing is used */
        hash_high = 0;
        cs_count = 0;
        set_eq_type(frozen_dont_expand, dont_expand_cmd);
        cs_text(frozen_dont_expand) = maketexstring("notexpanded:");
        set_eq_type(frozen_primitive, ignore_spaces_cmd);
        set_equiv(frozen_primitive, 1);
        set_eq_level(frozen_primitive, level_one);
        cs_text(frozen_primitive) = maketexstring("primitive");
        create_null_font();
        font_bytes = 0;
        init_null_ocp(get_nullstr(), maketexstring("nullocp"));
        initialize_init_ocplists();
        pdf_h_origin = one_inch;
        pdf_v_origin = one_inch;
        pdf_compress_level = 9;
        pdf_objcompresslevel = 0;
        pdf_decimal_digits = 3;
        pdf_image_resolution = 72;
        pdf_minor_version = 4;
        pdf_gamma = 1000;
        pdf_image_gamma = 2200;
        pdf_image_hicolor = 1;
        pdf_image_apply_gamma = 0;
        pdf_px_dimen = one_bp;
        pdf_draftmode = 0;
        cs_text(frozen_protection) = maketexstring("inaccessible");
        format_ident = maketexstring(" (INITEX)");
        cs_text(end_write) = maketexstring("endwrite");
        set_eq_level(end_write, level_one);
        set_eq_type(end_write, outer_call_cmd);
        set_equiv(end_write, null);

    }
    synctexoffset = int_base + synctex_code;

}
