/*

Copyright 2009-2012 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/>.

*/

#include "ptexlib.h"

static int nesting = 0;
# define max_nesting 1000
static void scan_expr(void);

/*tex

    Let's turn now to some procedures that \TeX\ calls upon frequently to digest
    certain kinds of patterns in the input. Most of these are quite simple; some
    are quite elaborate. Almost all of the routines call |get_x_token|, which can
    cause them to be invoked recursively.

    The |scan_left_brace| routine is called when a left brace is supposed to be
    the next non-blank token. (The term ``left brace'' means, more precisely, a
    character whose catcode is |left_brace|.) \TeX\ allows \.{\\relax} to appear
    before the |left_brace|.

*/

/* This reads a mandatory |left_brace|: */

void scan_left_brace(void)
{
    /*tex 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) {
        print_err("Missing { inserted");
        help4(
            "A left brace was mandatory here, so I've put one in.",
            "You might want to delete and/or insert some corrections",
            "so that I will find a matching right brace soon.",
            "If you're confused by all this, try typing `I}' now."
        );
        back_error();
        cur_tok = left_brace_token + '{';
        cur_cmd = left_brace_cmd;
        cur_chr = '{';
        incr(align_state);
    }
}

/*tex

    The |scan_optional_equals| routine looks for an optional `\.=' sign preceded
    by optional spaces; `\.{\\relax}' is not ignored here.

*/

void scan_optional_equals(void)
{
    /*tex Get the next non-blank non-call token */
    do {
        get_x_token();
    } while (cur_cmd == spacer_cmd);
    if (cur_tok != other_token + '=')
        back_input();
}

/*tex

    Here is a procedure that sounds an alarm when mu and non-mu units are being
    switched.

*/

static void mu_error(void)
{
    print_err("Incompatible glue units");
    help1("I'm going to assume that 1mu=1pt when they're mixed.");
    error();
}

/*tex

    The next routine `|scan_something_internal|' is used to fetch internal
    numeric quantities like `\.{\\hsize}', and also to handle the `\.{\\the}'
    when expanding constructions like `\.{\\the\\toks0}' and
    `\.{\\the\\baselineskip}'. Soon we will be considering the |scan_int|
    procedure, which calls |scan_something_internal|; on the other hand,
    |scan_something_internal| also calls |scan_int|, for constructions like
    `\.{\\catcode\`\\\$}' or `\.{\\fontdimen} \.3 \.{\\ff}'. So we have to
    declare |scan_int| as a |forward| procedure. A few other procedures are also
    declared at this point.

    \TeX\ doesn't know exactly what to expect when |scan_something_internal|
    begins. For example, an integer or dimension or glue value could occur
    immediately after `\.{\\hskip}'; and one can even say \.{\\the} with respect
    to token lists in constructions like `\.{\\xdef\\o\{\\the\\output\}}'. On the
    other hand, only integers are allowed after a construction like
    `\.{\\count}'. To handle the various possibilities, |scan_something_internal|
    has a |level| parameter, which tells the ``highest'' kind of quantity that
    |scan_something_internal| is allowed to produce. Eight levels are
    distinguished, namely |int_val|, |attr_val|, |dimen_val|, |glue_val|,
    |mu_val|, |dir_val|, |ident_val|, and |tok_val|.

    The output of |scan_something_internal| (and of the other routines
    |scan_int|, |scan_dimen|, and |scan_glue| below) is put into the global
    variable |cur_val|, and its level is put into |cur_val_level|. The highest
    values of |cur_val_level| are special: |mu_val| is used only when |cur_val|
    points to something in a ``muskip'' register, or to one of the three
    parameters \.{\\thinmuskip}, \.{\\medmuskip}, \.{\\thickmuskip}; |ident_val|
    is used only when |cur_val| points to a font identifier; |tok_val| is used
    only when |cur_val| points to |null| or to the reference count of a token
    list. The last two cases are allowed only when |scan_something_internal| is
    called with |level=tok_val|.

    If the output is glue, |cur_val| will point to a glue specification, and the
    reference count of that glue will have been updated to reflect this
    reference; if the output is a nonempty token list, |cur_val| will point to
    its reference count, but in this case the count will not have been updated.
    Otherwise |cur_val| will contain the integer or scaled value in question.

*/

/*tex The value returned by numeric scanners: */

int cur_val;

/*tex Delcodes are sometimes 51 digits: */

int cur_val1;

/*tex The level of this value: */

int cur_val_level;

#define scanned_result(A,B) do { \
    cur_val=A; \
    cur_val_level=B; \
} while (0)

/*tex

    When a |glue_val| changes to a |dimen_val|, we use the width component of the
    glue; there is no need to decrease the reference count, since it has not yet
    been increased. When a |dimen_val| changes to an |int_val|, we use scaled
    points so that the value doesn't actually change. And when a |mu_val| changes
    to a |glue_val|, the value doesn't change either.

*/

static void downgrade_cur_val(boolean delete_glue)
{
    if (cur_val_level == glue_val_level) {
        halfword m = cur_val;
        cur_val = width(m);
        if (delete_glue)
            flush_node(m);
    } else if (cur_val_level == mu_val_level) {
        mu_error();
    }
    decr(cur_val_level);
}

void negate_cur_val(boolean modify_glue)
{
    if (cur_val_level >= glue_val_level) {
        if (modify_glue) {
            /*tex We modify in-place. */
        } else {
            cur_val = new_spec(cur_val);
        }
        negate(width(cur_val));
        negate(stretch(cur_val));
        negate(shrink(cur_val));
    } else {
        negate(cur_val);
    }
}

/*tex

    Some of the internal items can be fetched both routines, and these have been
    split off into the next routine, that returns true if the command code was
    understood.

*/

static boolean short_scan_something_internal(int cmd, int chr, int level, boolean negative)
{
    /*tex |chr_code| part of the operand token */
    halfword m;
    /*tex general purpose index */
    halfword q;
    /*tex index into |nest| */
    int p;
    int save_cur_chr;
    boolean succeeded = true;
    m = chr;
    switch (cmd) {
        case assign_toks_cmd:
            scanned_result(equiv(m), tok_val_level);
            break;
        case assign_int_cmd:
            scanned_result(eqtb[m].cint, int_val_level);
            break;
        case assign_attr_cmd:
            scanned_result(eqtb[m].cint, int_val_level);
            break;
        case assign_dimen_cmd:
            scanned_result(eqtb[m].cint, dimen_val_level);
            break;
        case assign_glue_cmd:
            scanned_result(equiv(m), glue_val_level);
            break;
        case assign_mu_glue_cmd:
            scanned_result(equiv(m), mu_val_level);
            break;
        case assign_direction_cmd:
            if (m == (int_base + line_direction_code)) {
                m = int_base + text_direction_code;
            }
            scanned_result(eqtb[m].cint, int_val_level);
            break;
        case assign_dir_cmd:
            if (m == (int_base + line_direction_code)) {
                m = int_base + text_direction_code;
            }
            scanned_result(eqtb[m].cint, dir_val_level);
            break;
        case math_style_cmd:
            scanned_result(m, int_val_level);
            break;
        case set_aux_cmd:
            /*tex Fetch the |space_factor| or the |prev_depth|. */
            if (abs(cur_list.mode_field) != m) {
                print_err("Improper ");
                print_cmd_chr(set_aux_cmd, m);
                help4(
                    "You can refer to \\spacefactor only in horizontal mode;",
                    "you can refer to \\prevdepth only in vertical mode; and",
                    "neither of these is meaningful inside \\write. So",
                    "I'm forgetting what you said and using zero instead."
                );
                error();
                if (level != tok_val_level)
                    scanned_result(0, dimen_val_level);
                else
                    scanned_result(0, int_val_level);
            } else if (m == vmode) {
                scanned_result(prev_depth_par, dimen_val_level);
            } else {
                scanned_result(space_factor_par, int_val_level);
            }
            break;
        case set_prev_graf_cmd:
            /*tex Fetch the |prev_graf| */
            if (cur_list.mode_field == 0) {
                /*tex |prev_graf=0| within \.{\\write} */
                scanned_result(0, int_val_level);
            } else {
                p = nest_ptr;
                while (abs(nest[p].mode_field) != vmode)
                    decr(p);
                scanned_result(nest[p].pg_field, int_val_level);
            }
            break;
        case set_page_int_cmd:
            /*tex Fetch the |dead_cycles| or the |insert_penalties| */
            if (m == 0)
                cur_val = dead_cycles;
            else if (m == 2)
                cur_val = interaction;
            else
                cur_val = insert_penalties;
            cur_val_level = int_val_level;
            break;
        case set_page_dimen_cmd:
            /*tex Fetch something on the |page_so_far|. */
            if ((page_contents == empty) && (!output_active)) {
                if (m == 0)
                    cur_val = max_dimen;
                else
                    cur_val = 0;
            } else {
                cur_val = page_so_far[m];
            }
            cur_val_level = dimen_val_level;
            break;
        case set_tex_shape_cmd:
            /*tex Fetch the |par_shape| size. */
            if (par_shape_par_ptr == null)
                cur_val = 0;
            else
                cur_val = vinfo(par_shape_par_ptr + 1);
            cur_val_level = int_val_level;
            break;
        case set_etex_shape_cmd:
            /*tex Fetch a penalties array element. */
            scan_int();
            if ((equiv(m) == null) || (cur_val < 0)) {
                cur_val = 0;
            } else {
                if (cur_val > penalty(equiv(m)))
                    cur_val = penalty(equiv(m));
                cur_val = penalty(equiv(m) + cur_val);
            }
            cur_val_level = int_val_level;
            break;
        case char_given_cmd:
        case math_given_cmd:
        case xmath_given_cmd:
            scanned_result(cur_chr, int_val_level);
            break;
        case last_item_cmd:
            /*tex

                Because the items in this case directly refer to |cur_chr|, it
                needs to be saved and restored.

            */
            save_cur_chr = cur_chr;
            cur_chr = chr;
            /*tex

                Fetch an item in the current node, if appropriate. Here is where
                \.{\\lastpenalty}, \.{\\lastkern}, and \.{\\ } are implemented.
                The reference count for \.{\\lastskip} will be updated later.

                We also handle \.{\\inputlineno} and \.{\\badness} here, because
                they are legal in similar contexts.

            */
            if (m >= input_line_no_code) {
                if (m >= eTeX_glue) {
                    /*tex Process an expression and |return|. */
                    if (m < eTeX_mu) {
                        if (m == mu_to_glue_code) {
                            scan_mu_glue();
                        };
                        cur_val_level = glue_val_level;
                    } else if (m < eTeX_expr) {
                        if (m == glue_to_mu_code) {
                            scan_normal_glue();
                        }
                        cur_val_level = mu_val_level;
                    } else {
                        cur_val_level = m - eTeX_expr + int_val_level;
                        scan_expr();
                    }
                    /*tex

                        This code for reducing |cur_val_level| and\slash or
                        negating the result is similar to the one for all the
                        other cases of |scan_something_internal|; we free a
                        glue_spec when needed.

                    */
                    while (cur_val_level > level) {
                        downgrade_cur_val(true);
                    }
                    if (negative) {
                        /*tex

                            We get a new glue spec node with negated values and
                            the old intermediate is deleted.

                        */
                        negate_cur_val(true);
                    }
                    return succeeded;
                } else if (m >= eTeX_dim) {
                    switch (m) {
                        case font_char_wd_code:
                        case font_char_ht_code:
                        case font_char_dp_code:
                        case font_char_ic_code:
                            scan_font_ident();
                            q = cur_val;
                            scan_char_num();
                            if (char_exists(q, cur_val)) {
                                switch (m) {
                                case font_char_wd_code:
                                    cur_val = char_width(q, cur_val);
                                    break;
                                case font_char_ht_code:
                                    cur_val = char_height(q, cur_val);
                                    break;
                                case font_char_dp_code:
                                    cur_val = char_depth(q, cur_val);
                                    break;
                                case font_char_ic_code:
                                    cur_val = char_italic(q, cur_val);
                                    break;
				}
                            } else {
                                cur_val = 0;
                            }
                            break;
                        case par_shape_length_code:
                        case par_shape_indent_code:
                        case par_shape_dimen_code:
                            q = cur_chr - par_shape_length_code;
                            scan_int();
                            if ((par_shape_par_ptr == null) || (cur_val <= 0)) {
                                cur_val = 0;
                            } else {
                                if (q == 2) {
                                    q = cur_val % 2;
                                    cur_val = (cur_val + q) / 2;
                                }
                                if (cur_val > vinfo(par_shape_par_ptr + 1))
                                    cur_val = vinfo(par_shape_par_ptr + 1);
                                cur_val =
                                    varmem[par_shape_par_ptr + 2 * cur_val - q + 1].cint;
                            }
                            cur_val_level = dimen_val_level;
                            break;
                        case glue_stretch_code:
                        case glue_shrink_code:
                            scan_normal_glue();
                            q = cur_val;
                            if (m == glue_stretch_code)
                                cur_val = stretch(q);
                            else
                                cur_val = shrink(q);
                            flush_node(q);
                            break;
                    }
                    cur_val_level = dimen_val_level;
                } else {
                    switch (m) {
                        case input_line_no_code:
                            cur_val = line;
                            break;
                        case badness_code:
                            cur_val = last_badness;
                            break;
                        case luatex_version_code:
                            cur_val = get_luatexversion();
                            break;
                        case last_saved_box_resource_index_code:
                            cur_val = last_saved_box_index;
                            break;
                        case last_saved_image_resource_index_code:
                            cur_val = last_saved_image_index;
                            break;
                        case last_saved_image_resource_pages_code:
                            cur_val = last_saved_image_pages;
                            break;
                        case last_x_pos_code:
                            cur_val = last_position.h;
                            break;
                        case last_y_pos_code:
                            cur_val = last_position.v;
                            break;
                        case random_seed_code:
                            cur_val = random_seed;
                            break;
                        case eTeX_version_code:
                            cur_val = eTeX_version;
                            break;
                        case eTeX_minor_version_code:
                            cur_val = eTeX_minor_version;
                            break;
                        case current_group_level_code:
                            cur_val = cur_level - level_one;
                            break;
                        case current_group_type_code:
                            cur_val = cur_group;
                            break;
                        case current_if_level_code:
                            q = cond_ptr;
                            cur_val = 0;
                            while (q != null) {
                                incr(cur_val);
                                q = vlink(q);
                            }
                            break;
                        case current_if_type_code:
                            if (cond_ptr == null)
                                cur_val = 0;
                            else if (cur_if < unless_code)
                                cur_val = cur_if + 1;
                            else
                                cur_val = -(cur_if - unless_code + 1);
                            break;
                        case current_if_branch_code:
                            if ((if_limit == or_code) || (if_limit == else_code))
                                cur_val = 1;
                            else if (if_limit == fi_code)
                                cur_val = -1;
                            else
                                cur_val = 0;
                            break;
                        case glue_stretch_order_code:
                        case glue_shrink_order_code:
                        case eTeX_glue_stretch_order_code:
                        case eTeX_glue_shrink_order_code:
                            scan_normal_glue();
                            q = cur_val;
                            if (m == glue_stretch_order_code || m == eTeX_glue_stretch_order_code) {
                                cur_val = stretch_order(q);
                            } else {
                                cur_val = shrink_order(q);
                            }
                            if (cur_val && (m == eTeX_glue_stretch_order_code || m == eTeX_glue_shrink_order_code)) {
                                cur_val = (cur_val == 1) ? - 1 : cur_val - 1;
                            }
                            flush_node(q);
                            break;
                    }
                    cur_val_level = int_val_level;
                }
            } else {
                if (cur_chr == glue_val_level)
                    cur_val = zero_glue;
                else
                    cur_val = 0;
                if (cur_chr == last_node_type_code) {
                    cur_val_level = int_val_level;
                    if ((cur_list.tail_field == cur_list.head_field)
                        || (cur_list.mode_field == 0))
                        cur_val = -1;
                } else {
                    /*tex assumes identical values */
                    cur_val_level = cur_chr;
                }
                if ((cur_list.tail_field != contrib_head) && (cur_list.mode_field != 0)) {
                    switch (cur_chr) {
                        case lastpenalty_code:
                            if (type(cur_list.tail_field) == penalty_node)
                                cur_val = penalty(cur_list.tail_field);
                            break;
                        case lastkern_code:
                            if (type(cur_list.tail_field) == kern_node)
                                cur_val = width(cur_list.tail_field);
                            break;
                        case lastskip_code:
                            if (type(cur_list.tail_field) == glue_node)
                                cur_val = cur_list.tail_field;
                            if (subtype(cur_list.tail_field) == mu_glue)
                                cur_val_level = mu_val_level;
                            break;
                        case last_node_type_code:
                            cur_val = visible_last_node_type(cur_list.tail_field);
                            break;
                    }
                } else if ((cur_list.mode_field == vmode) && (cur_list.tail_field == cur_list.head_field)) {
                    switch (cur_chr) {
                        case lastpenalty_code:
                            cur_val = last_penalty;
                            break;
                        case lastkern_code:
                            cur_val = last_kern;
                            break;
                        case lastskip_code:
                            if (last_glue != max_halfword)
                                cur_val = last_glue;
                            break;
                        case last_node_type_code:
                            cur_val = last_node_type;
                            break;
                    }
                }
            }
            cur_chr = save_cur_chr;
            break;
        default:
            succeeded = false;
    }
    if (succeeded) {
        while (cur_val_level > level) {
            /*tex Convert |cur_val| to a lower level. */
            downgrade_cur_val(false);
        }
        /*tex

            Fix the reference count, if any, and negate |cur_val| if |negative|.
            If |cur_val| points to a glue specification at this point, the
            reference count for the glue does not yet include the reference by
            |cur_val|. If |negative| is |true|, |cur_val_level| is known to be
            |<=mu_val|.

         */
        if (negative) {
            /*tex We create a new (negated) glue spec and keep the old one. */
            negate_cur_val(false);
        } else if ((cur_val_level >= glue_val_level) && (cur_val_level <= mu_val_level)) {
			cur_val = new_spec(cur_val);
        }
    }
    return succeeded;
}

/*tex

    First, here is a short routine that is called from lua code. All the real
    work is delegated to |short_scan_something_internal| that is shared between
    this routine and |scan_something_internal|.

*/

void scan_something_simple(halfword cmd, halfword subitem)
{
    /*tex Negative is never true. */
    if (!short_scan_something_internal(cmd, subitem, tok_val_level, false)) {
        /*tex Complain that |texlib| can not do this; give zero result. */
        print_err("You can't use `");
        print_cmd_chr((quarterword) cmd, subitem);
        tprint("' as tex library index");
        help1("I'm forgetting what you said and using zero instead.");
        error();
        scanned_result(0, int_val_level);
    }
}

/*tex

    OK, we're ready for |scan_something_internal| itself. A second parameter,
    |negative|, is set |true| if the value that is found should be negated. It is
    assumed that |cur_cmd| and |cur_chr| represent the first token of the
    internal quantity to be scanned; an error will be signalled if
    |cur_cmd<min_internal| or |cur_cmd>max_internal|.

*/

/*tex Fetch an internal parameter: */

void scan_something_internal(int level, boolean negative)
{
    /*tex |chr_code| part of the operand token */
    halfword m;
    /*tex accumulators */
    int n, k;
  RESTART:
    m = cur_chr;
    if (!short_scan_something_internal(cur_cmd, cur_chr, level, negative)) {
        switch (cur_cmd) {
            case def_char_code_cmd:
                /*tex Fetch a character code from some table */
                scan_char_num();
                if (m == math_code_base) {
                    cur_val1 = get_math_code_num(cur_val);
                    scanned_result(cur_val1, int_val_level);
                } else if (m == lc_code_base) {
                    cur_val1 = get_lc_code(cur_val);
                    scanned_result(cur_val1, int_val_level);
                } else if (m == uc_code_base) {
                    cur_val1 = get_uc_code(cur_val);
                    scanned_result(cur_val1, int_val_level);
                } else if (m == sf_code_base) {
                    cur_val1 = get_sf_code(cur_val);
                    scanned_result(cur_val1, int_val_level);
                } else if (m == cat_code_base) {
                    cur_val1 = get_cat_code(cat_code_table_par, cur_val);
                    scanned_result(cur_val1, int_val_level);
                } else {
                    confusion("def_char");
                }
                break;
            case def_del_code_cmd:
            case extdef_del_code_cmd:
                /*tex Fetch a character code from some table. */
                scan_char_num();
                cur_val1 = get_del_code_num(cur_val);
                scanned_result(cur_val1, int_val_level);
                break;
            case extdef_math_code_cmd:
                /*tex Fetch an extended math code table value. */
                scan_char_num();
                cur_val1 = get_math_code_num(cur_val);
                scanned_result(cur_val1, int_val_level);
                break;
            case toks_register_cmd:
            case set_font_cmd:
            case def_font_cmd:
            case letterspace_font_cmd:
            case copy_font_cmd:
                /*tex Fetch a token list or font identifier, provided that |level=tok_val|. */
                if (level != tok_val_level) {
                    print_err("Missing number, treated as zero");
                    help3(
                        "A number should have been here; I inserted `0'.",
                        "(If you can't figure out why I needed to see a number,",
                        "look up `weird error' in the index to The TeXbook.)"
                    );
                    back_error();
                    scanned_result(0, dimen_val_level);
                } else if (cur_cmd == toks_register_cmd) {
                    scan_register_num();
                    m = toks_base + cur_val;
                    scanned_result(equiv(m), tok_val_level);
                } else {
                    back_input();
                    scan_font_ident();
                    scanned_result(font_id_base + cur_val, ident_val_level);
                }
                break;
            case set_font_id_cmd:
                scan_int();
                scanned_result(font_id_base + cur_val, ident_val_level);
                break;
            case def_family_cmd:
                /*tex Fetch a math font identifier. */
                scan_char_num();
                cur_val1 = fam_fnt(cur_val, m);
                scanned_result(font_id_base + cur_val1, ident_val_level);
                break;
            case set_math_param_cmd:
                /*tex Fetch a math parameter. */
                cur_val1 = 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_val = display_style;
                    back_error();
                } else {
                    cur_val = cur_chr;
                }
                if (cur_val1 < math_param_first_mu_glue) {
                    if (cur_val1 == math_param_radical_degree_raise) {
                        cur_val1 = get_math_param(cur_val1, cur_chr);
                        scanned_result(cur_val1, int_val_level);
                    } else {
                        cur_val1 = get_math_param(cur_val1, cur_chr);
                        scanned_result(cur_val1, dimen_val_level);
                    }
                } else {
                    cur_val1 = get_math_param(cur_val1, cur_chr);
                    if (cur_val1 == thin_mu_skip_code)
                        cur_val1 = thin_mu_skip_par;
                    else if (cur_val1 == med_mu_skip_code)
                        cur_val1 = med_mu_skip_par;
                    else if (cur_val1 == thick_mu_skip_code)
                        cur_val1 = thick_mu_skip_par;
                    scanned_result(cur_val1, mu_val_level);
                }
                break;
            case assign_box_dir_cmd:
                scan_register_num();
                m = cur_val;
                if (box(m) != null)
                    cur_val = box_dir(box(m));
                else
                    cur_val = 0;
                cur_val_level = dir_val_level;
                break;
            case set_box_dimen_cmd:
                /*tex Fetch a box dimension. */
                scan_register_num();
                if (box(cur_val) == null)
                    cur_val = 0;
                else
                    cur_val = varmem[box(cur_val) + m].cint;
                cur_val_level = dimen_val_level;
                break;
            case assign_font_dimen_cmd:
                /*tex Fetch a font dimension. */
                get_font_dimen();
                break;
            case assign_font_int_cmd:
                /*tex Fetch a font integer. */
                scan_font_ident();
                if (m == 0) {
                    scanned_result(hyphen_char(cur_val), int_val_level);
                } else if (m == 1) {
                    scanned_result(skew_char(cur_val), int_val_level);
                } else if (m == no_lig_code) {
                    scanned_result(test_no_ligatures(cur_val), int_val_level);
                } else {
                    n = cur_val;
                    scan_char_num();
                    k = cur_val;
                    switch (m) {
                        case lp_code_base:
                            scanned_result(get_lp_code(n, k), int_val_level);
                            break;
                        case rp_code_base:
                            scanned_result(get_rp_code(n, k), int_val_level);
                            break;
                        case ef_code_base:
                            scanned_result(get_ef_code(n, k), int_val_level);
                            break;
                        case tag_code:
                            scanned_result(get_tag_code(n, k), int_val_level);
                            break;
                    }
                }
                break;
            case register_cmd:
                /*tex Fetch a register */
                scan_register_num();
                switch (m) {
                    case int_val_level:
                        cur_val = count(cur_val);
                        break;
                    case attr_val_level:
                        cur_val = attribute(cur_val);
                        break;
                    case dimen_val_level:
                        cur_val = dimen(cur_val);
                        break;
                    case glue_val_level:
                        cur_val = skip(cur_val);
                        break;
                    case mu_val_level:
                        cur_val = mu_skip(cur_val);
                        break;
                }
                cur_val_level = m;
                break;
            case ignore_spaces_cmd:
                /*tex Trap unexpandable primitives. */
                if (cur_chr == 1) {
                    /*tex

                        Reset |cur_tok| for unexpandable primitives, goto
                        restart. This block deals with unexpandable
                        \.{\\primitive} appearing at a spot where an integer or
                        an internal values should have been found. It fetches the
                        next token then resets |cur_cmd|, |cur_cs|, and
                        |cur_tok|, based on the primitive value of that token. No
                        expansion takes place, because the next token may be all
                        sorts of things. This could trigger further expansion
                        creating new errors.

                    */
                    get_token();
                    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 = token_val(cur_cmd, cur_chr);
                    } else {
                        cur_cmd = relax_cmd;
                        cur_chr = 0;
                        cur_tok = cs_token_flag + frozen_relax;
                        cur_cs = frozen_relax;
                    }
                    goto RESTART;
                }
                break;
            case hyph_data_cmd:
                switch (cur_chr) {
                    case 0:
                    case 1:
                        goto DEFAULT;
                        break;
                    case 2:
                        cur_val = get_pre_hyphen_char(language_par);
                        cur_val_level = int_val_level;
                        break;
                    case 3:
                        cur_val = get_post_hyphen_char(language_par);
                        cur_val_level = int_val_level;
                        break;
                    case 4:
                        cur_val = get_pre_exhyphen_char(language_par);
                        cur_val_level = int_val_level;
                        break;
                    case 5:
                        cur_val = get_post_exhyphen_char(language_par);
                        cur_val_level = int_val_level;
                        break;
                    case 6:
                        cur_val = get_hyphenation_min(language_par);
                        cur_val_level = int_val_level;
                        break;
                    case 7:
                        scan_int();
                        cur_val = get_hj_code(language_par,cur_val);
                        cur_val_level = int_val_level;
                        break;
                }
                break;
            default:
                DEFAULT:
                /*tex Complain that \.{\\the} can not do this; give zero result. */
                print_err("You can't use `");
                print_cmd_chr((quarterword) cur_cmd, cur_chr);
                tprint("' after \\the");
                help1("I'm forgetting what you said and using zero instead.");
                error();
                if (level != tok_val_level)
                    scanned_result(0, dimen_val_level);
                else
                    scanned_result(0, int_val_level);
                break;
        }
        while (cur_val_level > level) {
            /*tex Convert |cur_val| to a lower level. */
            downgrade_cur_val(false);
        }
        /*tex

            Fix the reference count, if any, and negate |cur_val| if |negative|.
            If |cur_val| points to a glue specification at this point, the
            reference count for the glue does not yet include the reference by
            |cur_val|. If |negative| is |true|, |cur_val_level| is known to be
            |<=mu_val|.

         */
        if (negative) {
            /*tex We create a new (negated) glue spec and keep the old one. */
            negate_cur_val(false);
        } else if ((cur_val_level >= glue_val_level) && (cur_val_level <= mu_val_level)) {
			cur_val = new_spec(cur_val);
        }
    }
}

/*tex

    It is nice to have routines that say what they do, so the original
    |scan_eight_bit_int| is superceded by |scan_register_num| and
    |scan_mark_num|. It may become split up even further in the future.

    Many of the |restricted classes| routines are the essentially the same except
    for the upper limit and the error message, so it makes sense to combine these
    all into one function.

*/

void scan_limited_int(int max, const char *name)
{
    char hlp[80];
    scan_int();
    if ((cur_val < 0) || (cur_val > max)) {
        if (name == NULL) {
            snprintf(hlp, 80, "Since I expected to read a number between 0 and %d,", max);
            print_err("Bad number");
        } else {
            char msg[80];
            snprintf(hlp, 80, "A %s must be between 0 and %d.", name, max);
            snprintf(msg, 80, "Bad %s", name);
            print_err(msg);
        }
        help2(hlp, "I changed this one to zero.");
        int_error(cur_val);
        cur_val = 0;
    }
}

void scan_fifteen_bit_int(void)
{
    scan_real_fifteen_bit_int();
    cur_val = ((cur_val / 0x1000) * 0x1000000) + (((cur_val % 0x1000) / 0x100) * 0x10000) + (cur_val % 0x100);
}

void scan_fifty_one_bit_int(void)
{
    int iiii;
    scan_int();
    if ((cur_val < 0) || (cur_val > 0777777777)) {
        print_err("Bad delimiter code");
        help2(
            "A numeric delimiter (first part) must be between 0 and 2^{27}-1.",
            "I changed this one to zero."
        );
        int_error(cur_val);
        cur_val = 0;
    }
    iiii = cur_val;
    scan_int();
    if ((cur_val < 0) || (cur_val > 0xFFFFFF)) {
        print_err("Bad delimiter code");
        help2(
            "A numeric delimiter (second part) must be between 0 and 2^{24}-1.",
            "I changed this one to zero."
        );
        int_error(cur_val);
        cur_val = 0;
    }
    cur_val1 = cur_val;
    cur_val = iiii;
}

/*tex

    An integer number can be preceded by any number of spaces and `\.+' or `\.-'
    signs. Then comes either a decimal constant (i.e., radix 10), an octal
    constant (i.e., radix 8, preceded by~'), a hexadecimal constant (radix 16,
    preceded by~"), an alphabetic constant (preceded by~`), or an internal
    variable. After scanning is complete, |cur_val| will contain the answer,
    which must be at most $2^{31}-1=2147483647$ in absolute value. The value of
    |radix| is set to 10, 8, or 16 in the cases of decimal, octal, or hexadecimal
    constants, otherwise |radix| is set to zero. An optional space follows a
    constant.

*/

/*tex |scan_int| sets this to 8, 10, 16, or zero */

int radix;

/*tex

    The |scan_int| routine is used also to scan the integer part of a fraction;
    for example, the `\.3' in `\.{3.14159}' will be found by |scan_int|. The
    |scan_dimen| routine assumes that |cur_tok=point_token| after the integer
    part of such a fraction has been scanned by |scan_int|, and that the decimal
    point has been backed up to be scanned again.

*/

/*tex Sets |cur_val| to an integer: */

void scan_int(void)
{
    /*tex should the answer be negated? */
    boolean negative;
    /*tex |$2^{31}$ / radix|, the threshold of danger */
    int m;
    /*tex the digit just scanned */
    int d;
    /*tex have no digits appeared? */
    boolean vacuous;
    /*tex has an error message been issued? */
    boolean OK_so_far;
    radix = 0;
    OK_so_far = true;
    /*tex Get the next non-blank non-sign token; set |negative| appropriately. */
    negative = false;
    do {
        /*tex Get the next non-blank non-call token. */
        do {
            get_x_token();
        } while (cur_cmd == spacer_cmd);
        if (cur_tok == other_token + '-') {
            negative = !negative;
            cur_tok = other_token + '+';
        }
    } while (cur_tok == other_token + '+');

  RESTART:
    if (cur_tok == alpha_token) {
        /*tex

            Scan an alphabetic character code into |cur_val|. A space is ignored
            after an alphabetic character constant, so that such constants behave
            like numeric ones.

        */
        /*tex Suppress macro expansion: */
        get_token();
        if (cur_tok < cs_token_flag) {
            cur_val = cur_chr;
            if (cur_cmd <= right_brace_cmd) {
                if (cur_cmd == right_brace_cmd)
                    incr(align_state);
                else
                    decr(align_state);
            }
        } else {
            /*tex The value of a csname in this context is its name. */
            str_number txt = cs_text(cur_tok - cs_token_flag);
            if (is_active_cs(txt))
                cur_val = active_cs_value(txt);
            else if (single_letter(txt))
                cur_val = pool_to_unichar(str_string(txt));
            else
                cur_val = (biggest_char + 1);
        }
        if (cur_val > biggest_char) {
            print_err("Improper alphabetic constant");
            help2(
                "A one-character control sequence belongs after a ` mark.",
                "So I'm essentially inserting \\0 here."
            );
            cur_val = '0';
            back_error();
        } else {
            /*tex Scan an optional space. */
            get_x_token();
            if (cur_cmd != spacer_cmd)
                back_input();
        }

    } else if (cur_tok == cs_token_flag + frozen_primitive) {
        /*tex

            Reset |cur_tok| for unexpandable primitives, goto restart This block
            deals with unexpandable \.{\\primitive} appearing at a spot where an
            integer or an internal values should have been found. It fetches the
            next token then resets |cur_cmd|, |cur_cs|, and |cur_tok|, based on
            the primitive value of that token. No expansion takes place, because
            the next token may be all sorts of things. This could trigger further
            expansion creating new errors.

        */
        get_token();
        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 = token_val(cur_cmd, cur_chr);
        } else {
            cur_cmd = relax_cmd;
            cur_chr = 0;
            cur_tok = cs_token_flag + frozen_relax;
            cur_cs = frozen_relax;
        }
        goto RESTART;
    } else if (cur_cmd == math_style_cmd) {
        cur_val = cur_chr;
    } else if ((cur_cmd >= min_internal_cmd) && (cur_cmd <= max_internal_cmd)) {
        scan_something_internal(int_val_level, false);
    } else {
        /*tex Scan a numeric constant. */
        radix = 10;
        m = 214748364;
        if (cur_tok == octal_token) {
            radix = 8;
            m = 02000000000;
            get_x_token();
        } else if (cur_tok == hex_token) {
            radix = 16;
            m = 01000000000;
            get_x_token();
        }
        vacuous = true;
        cur_val = 0;
        /*tex Accumulate the constant until |cur_tok| is not a suitable digit. */
        while (1) {
            if ((cur_tok < zero_token + radix) && (cur_tok >= zero_token) && (cur_tok <= nine_token)) {
                d = cur_tok - zero_token;
            } else if (radix == 16) {
                if ((cur_tok <= A_token + 5) && (cur_tok >= A_token)) {
                    d = cur_tok - A_token + 10;
                } else if ((cur_tok <= other_A_token + 5) && (cur_tok >= other_A_token)) {
                    d = cur_tok - other_A_token + 10;
                } else {
                    break;
                }
            } else {
                break;
            }
            vacuous = false;
            if ((cur_val >= m) && ((cur_val > m) || (d > 7) || (radix != 10))) {
                if (OK_so_far) {
                    print_err("Number too big");
                    help2(
                        "I can only go up to 2147483647='17777777777=\"7FFFFFFF,",
                        "so I'm using that number instead of yours."
                    );
                    error();
                    cur_val = infinity;
                    OK_so_far = false;
                }
            } else {
                cur_val = cur_val * radix + d;
            }
            get_x_token();
        }
        if (vacuous) {
            /*tex Express astonishment that no number was here */
            print_err("Missing number, treated as zero");
            help3(
                "A number should have been here; I inserted `0'.",
                "(If you can't figure out why I needed to see a number,",
                "look up `weird error' in the index to The TeXbook.)"
            );
            back_error();
        } else if (cur_cmd != spacer_cmd) {
            back_input();
        }
    }
    if (negative)
        negate(cur_val);
}

/*tex

    The following code is executed when |scan_something_internal| was called
    asking for |mu_val|, when we really wanted a ``mudimen'' instead of
    ``muglue.''

*/

static void coerce_glue(void)
{
    int v;
    if (cur_val_level >= glue_val_level) {
        v = width(cur_val);
        flush_node(cur_val);
        cur_val = v;
    }
}

/*tex

    The |scan_dimen| routine is similar to |scan_int|, but it sets |cur_val| to a
    |scaled| value, i.e., an integral number of sp. One of its main tasks is
    therefore to interpret the abbreviations for various kinds of units and to
    convert measurements to scaled points.

    There are three parameters: |mu| is |true| if the finite units must be
    `\.{mu}', while |mu| is |false| if `\.{mu}' units are disallowed; |inf| is
    |true| if the infinite units `\.{fil}', `\.{fill}', `\.{filll}' are
    permitted; and |shortcut| is |true| if |cur_val| already contains an integer
    and only the units need to be considered.

    The order of infinity that was found in the case of infinite glue is returned
    in the global variable |cur_order|.

*/

/*tex The order of infinity found by |scan_dimen|: */

int cur_order;

/*tex

    Constructions like `\.{-\'77 pt}' are legal dimensions, so |scan_dimen| may
    begin with |scan_int|. This explains why it is convenient to use |scan_int|
    also for the integer part of a decimal fraction.

    Several branches of |scan_dimen| work with |cur_val| as an integer and with
    an auxiliary fraction |f|, so that the actual quantity of interest is
    $|cur_val|+|f|/2^{16}$. At the end of the routine, this ``unpacked''
    representation is put into the single word |cur_val|, which suddenly switches
    significance from |integer| to |scaled|.

    The necessary conversion factors can all be specified exactly as fractions
    whose numerator and denominator add to 32768 or less. According to the
    definitions here, $\rm2660\,dd\approx1000.33297\,mm$; this agrees well with
    the value $\rm1000.333\,mm$ cited by Bosshard \^{Bosshard, Hans Rudolf} in
    {\sl Technische Grundlagen zur Satzherstellung\/} (Bern, 1980). The Didot
    point has been newly standardized in 1978; it's now exactly $\rm
    1\,nd=0.375\,mm$. Conversion uses the equation
    $0.375=21681/20320/72.27\cdot25.4$. The new Cicero follows the new Didot
    point; $\rm 1\,nc=12\,nd$. These would lead to the ratios $21681/20320$ and
    $65043/5080$, respectively. The closest approximations supported by the
    algorithm would be $11183/10481$ and $1370/107$. In order to maintain the
    relation $\rm 1\,nc=12\,nd$, we pick the ratio $685/642$ for $\rm nd$,
    however.

*/

static void scan_dimen_mu_error(void) {
    print_err("Illegal unit of measure (mu inserted)");
    help4(
        "The unit of measurement in math glue must be mu.",
        "To recover gracefully from this error, it's best to",
        "delete the erroneous units; e.g., type `2' to delete",
        "two letters. (See Chapter 27 of The TeXbook.)"
    );
    error();
}

static void scan_dimen_unknown_unit_error(void) {
    print_err("Illegal unit of measure (pt inserted)");
    help6(
        "Dimensions can be in units of em, ex, in, pt, pc,",
        "cm, mm, dd, cc, nd, nc, bp, or sp; but yours is a new one!",
        "I'll assume that you meant to say pt, for printer's points.",
        "To recover gracefully from this error, it's best to",
        "delete the erroneous units; e.g., type `2' to delete",
        "two letters. (See Chapter 27 of The TeXbook.)"
    );
    error();
}

static void scan_dimen_out_of_range_error(void) {
    print_err("Dimension too large");
    help2(
        "I can't work with sizes bigger than about 19 feet.",
        "Continue and I'll use the largest value I can."
    );
    error();
}

#define set_conversion(A,B) do { num=(A); denom=(B); } while(0)

/*tex

    This function sets |cur_val| to a dimension. It could be optimized a bit more
    (but not now, something for luatex > 1).

*/

void scan_dimen(boolean mu, boolean inf, boolean shortcut)
{
    /*tex should the answer be negated? */
    boolean negative = false;
    boolean is_true  = false;
    /*tex numerator of a fraction whose denominator is $2^{16}$ */
    int f = 0;
    /*tex conversion ratio for the scanned units */
    int num = 0;
    int denom = 0;
    /*tex top of decimal digit stack */
    halfword q;
    /*tex an internal dimension */
    scaled v;
    /*tex temporary storage of |cur_val| */
    int save_cur_val;
    arith_error = false;
    cur_order = normal;
    if (!shortcut) {
        /*tex Get the next non-blank non-sign. */
        do {
            /*tex Get the next non-blank non-call token. */
            do {
                get_x_token();
            } while (cur_cmd == spacer_cmd);
            if (cur_tok == minus_token) {
                negative = !negative;
                cur_tok = plus_token;
            }
        } while (cur_tok == plus_token);
        if ((cur_cmd >= min_internal_cmd) && (cur_cmd <= max_internal_cmd)) {
            /*tex Fetch an internal dimension and |goto attach_sign|, or fetch an internal integer. */
            if (mu) {
                scan_something_internal(mu_val_level, false);
                coerce_glue();
                if (cur_val_level == mu_val_level) {
                    goto ATTACH_SIGN;
                } else if (cur_val_level != int_val_level) {
                    mu_error();
                }
            } else {
                scan_something_internal(dimen_val_level, false);
                if (cur_val_level == dimen_val_level) {
                    goto ATTACH_SIGN;
                }
            }
        } else {
            back_input();
            if (cur_tok == continental_point_token) {
                cur_tok = point_token;
            }
            if (cur_tok != point_token) {
                scan_int();
            } else {
                radix = 10;
                cur_val = 0;
            }
            if (cur_tok == continental_point_token) {
                cur_tok = point_token;
            }
            if ((radix == 10) && (cur_tok == point_token)) {
                /*tex

                    Scan decimal fraction. When the following code is executed,
                    we have |cur_tok=point_token|, but this token has been backed
                    up using |back_input|; we must first discard it. It turns out
                    that a decimal point all by itself is equivalent to
                    `\.{0.0}'. Let's hope people don't use that fact.

                */
                int k = 0;
                halfword p = null;
                int kk;
                /*tex The |point_token| is being re-scanned. */
                get_token();
                while (1) {
                    get_x_token();
                    if ((cur_tok > nine_token) || (cur_tok < zero_token))
                        break;
                    if (k < 17) {
                        /*tex Digits for |k>=17| cannot affect the result. */
                        q = get_avail();
                        set_token_link(q, p);
                        set_token_info(q, cur_tok - zero_token);
                        p = q;
                        incr(k);
                    }
                }
                for (kk = k; kk >= 1; kk--) {
                    dig[kk - 1] = token_info(p);
                    q = p;
                    p = token_link(p);
                    free_avail(q);
                }
                f = round_decimals(k);
                if (cur_cmd != spacer_cmd) {
                    back_input();
                }
            }
        }
    }
    if (cur_val < 0) {
        /*tex In this case |f=0|. */
        negative = !negative;
        negate(cur_val);
    }
    /*tex

        Scan units and set |cur_val| to $x\cdot(|cur_val|+f/2^{16})$, where there
        are |x| sp per unit; |goto attach_sign| if the units are internal. Now
        comes the harder part: At this point in the program, |cur_val| is a
        nonnegative integer and $f/2^{16}$ is a nonnegative fraction less than 1;
        we want to multiply the sum of these two quantities by the appropriate
        factor, based on the specified units, in order to produce a |scaled|
        result, and we want to do the calculation with fixed point arithmetic
        that does not overflow.

    */
    if (inf) {
         if (scan_keyword("fi")) {
             cur_order = sfi;
             if (scan_keyword("l")) {
                cur_order = fil;
                if (scan_keyword("l")) {
                    cur_order = fill;
                    if (scan_keyword("l")) {
                        cur_order = filll;
                    }
                }
            }
            goto ATTACH_FRACTION;
         }
    }
    /*tex

        Scan for (u)units that are internal dimensions; |goto attach_sign| with
        |cur_val| set if found.

    */
    save_cur_val = cur_val;
    /*tex

        Get the next non-blank non-call; a pitty if just backed up the input.

    */
    do {
        get_x_token();
    } while (cur_cmd == spacer_cmd);

    if ((cur_cmd < min_internal_cmd) || (cur_cmd > max_internal_cmd)) {
        back_input();
    } else {
        /*tex |math_given_cmd|, |xmath_given_cmd| and |last_item_cmd} */
        if (mu) {
            scan_something_internal(mu_val_level, false);
            coerce_glue();
            if (cur_val_level != mu_val_level) {
                mu_error();
            }
        } else {
            scan_something_internal(dimen_val_level, false);
        }
        v = cur_val;
        goto FOUND;
    }
    /*tex Bah |true| forces to split the unit scanner. */
    if (mu) {
        /*tex Scan for (m)\.{mu} units and |goto attach_fraction|. */
        if (! scan_keyword("mu")) {
            scan_dimen_mu_error();
        }
        goto ATTACH_FRACTION;
    } else if (scan_keyword("em")) {
        v = quad(get_cur_font());
    } else if (scan_keyword("ex")) {
        v = x_height(get_cur_font());
    } else if (scan_keyword("px")) {
        v = px_dimen_par;
    } else {
        goto PICKUP_UNIT;
    }
    /*tex Scan an optional space (after em, ex or px) */
    get_x_token();
    if (cur_cmd != spacer_cmd) {
        back_input();
    }
  FOUND:
    cur_val = nx_plus_y(save_cur_val, v, xn_over_d(v, f, 0200000));
    goto ATTACH_SIGN;
    /*tex

        Scan for (a)all other units and adjust |cur_val| and |f| accordingly;
        |goto done| in the case of scaled points.

    */
  PICKUP_UNIT:
    if (scan_keyword("pt")) {
        /*tex The easy case: */
        goto SCALE_VALUE;
    } else if (scan_keyword("mm")) {
        set_conversion(7227, 2540);
        goto SCALE_VALUE;
    } else if (scan_keyword("cm")) {
        set_conversion(7227, 254);
        goto SCALE_VALUE;
    } else if (scan_keyword("sp")) {
        goto DONE;
    } else if (scan_keyword("bp")) {
        set_conversion(7227, 7200);
        goto SCALE_VALUE;
    } else if (scan_keyword("in")) {
        set_conversion(7227, 100);
        goto SCALE_VALUE;
    } else if (scan_keyword("dd")) {
        set_conversion(1238, 1157);
        goto SCALE_VALUE;
    } else if (scan_keyword("cc")) {
        set_conversion(14856, 1157);
        goto SCALE_VALUE;
    } else if (scan_keyword("pc")) {
        set_conversion(12, 1);
        goto SCALE_VALUE;
    } else if (scan_keyword("nd")) {
        set_conversion(685, 642);
        goto SCALE_VALUE;
    } else if (scan_keyword("nc")) {
        set_conversion(1370, 107);
        goto SCALE_VALUE;
    } else if (!is_true && scan_keyword("true")) {
        is_true = true;
        goto PICKUP_UNIT;
    }
    /*tex Complain about unknown unit and |goto done2|. */
    scan_dimen_unknown_unit_error();
    goto BAD_NEWS;
  SCALE_VALUE:
    /*tex Adjust |f| for the magnification ratio. */
    if (is_true) {
        /*tex Maybe at some point we will drop mag completely, even in \DVI\ mode. */
        if (output_mode_used <= OMODE_DVI) {
            prepare_mag();
            if (mag_par != 1000) {
                cur_val = xn_over_d(cur_val, 1000, mag_par);
                f = (1000 * f + 0200000 * tex_remainder) / mag_par;
                cur_val = cur_val + (f / 0200000);
                f = f % 0200000;
            }
        } else {
            /*tex in \PDF\ mode we're always |true|. */
            one_true_inch = one_inch;
        }
    }
    if (num) {
        cur_val = xn_over_d(cur_val, num, denom);
        f = (num * f + 0200000 * tex_remainder) / denom;
        cur_val = cur_val + (f / 0200000);
        f = f % 0200000;
    }
  BAD_NEWS:
  ATTACH_FRACTION:
    if (cur_val >= 040000) {
        arith_error = true;
    } else {
        cur_val = cur_val * unity + f;
    }
  DONE:
    /*tex Scan an optional space; this happens too often. */
    get_x_token();
    if (cur_cmd != spacer_cmd) {
        back_input();
    }
  ATTACH_SIGN:
    if (arith_error || (abs(cur_val) >= 010000000000)) {
        /*tex Report that this dimension is out of range. */
        scan_dimen_out_of_range_error();
        cur_val = max_dimen;
        arith_error = false;
    }
    if (negative) {
        negate(cur_val);
    }
}

/*tex

    The final member of \TeX's value-scanning trio is |scan_glue|, which makes
    |cur_val| point to a glue specification. The reference count of that glue
    spec will take account of the fact that |cur_val| is pointing to~it.

    The |level| parameter should be either |glue_val| or |mu_val|.

    Since |scan_dimen| was so much more complex than |scan_int|, we might expect
    |scan_glue| to be even worse. But fortunately, it is very simple, since most
    of the work has already been done.

*/

void scan_glue(int level)
{
    /*tex should the answer be negated? */
    boolean negative = false;
    /*tex new glue specification */
    halfword q = null;
    /*tex does |level=mu_val|? */
    boolean mu = (level == mu_val_level);
    /*tex Get the next non-blank non-sign. */
    do {
        /*tex Get the next non-blank non-call token. */
        do {
            get_x_token();
        } while (cur_cmd == spacer_cmd);
        if (cur_tok == minus_token) {
            negative = !negative;
            cur_tok = plus_token;
        }
    } while (cur_tok == plus_token);
    if ((cur_cmd >= min_internal_cmd) && (cur_cmd <= max_internal_cmd)) {
        scan_something_internal(level, negative);
        if (cur_val_level >= glue_val_level) {
            if (cur_val_level != level)
                mu_error();
            return;
        }
        if (cur_val_level == int_val_level)
            scan_dimen(mu, false, true);
        else if (level == mu_val_level)
            mu_error();
    } else {
        back_input();
        scan_dimen(mu, false, false);
        if (negative)
            negate(cur_val);
    }
    /*tex

        Create a new glue specification whose width is |cur_val|; scan for its
        stretch and shrink components.

    */
    q = new_spec(zero_glue);
    width(q) = cur_val;
    if (scan_keyword("plus")) {
        scan_dimen(mu, true, false);
        stretch(q) = cur_val;
        stretch_order(q) = (quarterword) cur_order;
    }
    if (scan_keyword("minus")) {
        scan_dimen(mu, true, false);
        shrink(q) = cur_val;
        shrink_order(q) = (quarterword) cur_order;
    }
    cur_val = q;
}

/*tex

    This procedure is supposed to scan something like `\.{\\skip\\count12}',
    i.e., whatever can follow `\.{\\the}', and it constructs a token list
    containing something like `\.{-3.0pt minus 0.5fill}'.

*/

halfword the_toks(void)
{
    /*tex holds |selector| setting */
    int old_setting;
    /*tex used for copying a token list */
    halfword p, q, r;
    /*tex value of |cur_chr| */
    int c;
    str_number s;
    halfword retval;
    /*tex Handle \.{\\unexpanded} or \.{\\detokenize} and |return|. */
    if (odd(cur_chr)) {
        c = cur_chr;
        scan_general_text();
        if (c == 1) {
            return cur_val;
        } else {
            old_setting = selector;
            selector = new_string;
            p = get_avail();
            set_token_link(p, token_link(temp_token_head));
            token_show(p);
            flush_list(p);
            selector = old_setting;
            s = make_string();
            retval = str_toks(str_lstring(s));
            flush_str(s);
            return retval;
        }
    }
    get_x_token();
    scan_something_internal(tok_val_level, false);
    if (cur_val_level >= ident_val_level) {
        /*tex Copy the token list */
        p = temp_token_head;
        set_token_link(p, null);
        if (cur_val_level == ident_val_level) {
            store_new_token(cs_token_flag + cur_val);
        } else if (cur_val != null) {
            /*tex Do not copy the reference count! */
            r = token_link(cur_val);
            while (r != null) {
                fast_store_new_token(token_info(r));
                r = token_link(r);
            }
        }
        return p;
    } else {
        old_setting = selector;
        selector = new_string;
        switch (cur_val_level) {
            case int_val_level:
                print_int(cur_val);
                break;
            case attr_val_level:
                print_int(cur_val);
                break;
            case dir_val_level:
                print_dir_par(cur_val);
                break;
            case dimen_val_level:
                print_scaled(cur_val);
                tprint("pt");
                break;
            case glue_val_level:
                print_spec(cur_val, "pt");
                flush_node(cur_val);
                break;
            case mu_val_level:
                print_spec(cur_val, "mu");
                flush_node(cur_val);
                break;
        }
        selector = old_setting;
        s = make_string();
        retval = str_toks(str_lstring(s));
        flush_str(s);
        return retval;
    }
}

str_number the_scanned_result(void)
{
    /*tex holds |selector| setting */
    int old_setting;
    /*tex return value */
    str_number r;
    old_setting = selector;
    selector = new_string;
    if (cur_val_level >= ident_val_level) {
        if (cur_val != null) {
            show_token_list(token_link(cur_val), null, -1);
            r = make_string();
        } else {
            r = get_nullstr();
        }
    } else {
        switch (cur_val_level) {
            case int_val_level:
                print_int(cur_val);
                break;
            case attr_val_level:
                print_int(cur_val);
                break;
            case dir_val_level:
                print_dir_par(cur_val);
                break;
            case dimen_val_level:
                print_scaled(cur_val);
                tprint("pt");
                break;
            case glue_val_level:
                print_spec(cur_val, "pt");
                flush_node(cur_val);
                break;
            case mu_val_level:
                print_spec(cur_val, "mu");
                flush_node(cur_val);
                break;
        }
        r = make_string();
    }
    selector = old_setting;
    return r;
}

/*tex

    The following routine is used to implement `\.{\\fontdimen} |n| |f|'. The
    boolean parameter |writing| is set |true| if the calling program intends to
    change the parameter value.

*/

static void font_param_error(int f)
{
    print_err("Font ");
    print_esc(font_id_text(f));
    tprint(" has only ");
    print_int(font_params(f));
    tprint(" fontdimen parameters");
    help2(
        "To increase the number of font parameters, you must",
        "use \\fontdimen immediately after the \\font is loaded."
    );
    error();
}

void set_font_dimen(void)
{
    internal_font_number f;
    int n;
    scan_int();
    n = cur_val;
    scan_font_ident();
    f = cur_val;
    if (n <= 0) {
        font_param_error(f);
    } else if (n > font_params(f)) {
        if (font_used(f)) {
            font_param_error(f);
        } else {
            /*tex Increase the number of parameters in the font. */
         // do {
         //     set_font_param(f, (font_params(f) + 1), 0);
         // } while (n != font_params(f));
            set_font_params(f, n);
        }
    }
    scan_optional_equals();
    scan_normal_dimen();
    set_font_param(f, n, cur_val);
}

void get_font_dimen(void)
{
    internal_font_number f;
    /*tex The parameter number: */
    int n;
    scan_int();
    n = cur_val;
    scan_font_ident();
    f = cur_val;
    /*tex Initialize return value: */
    cur_val = 0;
    if (n <= 0) {
        font_param_error(f);
        goto EXIT;
    } else if (n > font_params(f)) {
        if (font_used(f)) {
            font_param_error(f);
            goto EXIT;
        } else {
            /*tex Increase the number of parameters in the font. */
         // do {
         //     set_font_param(f, (font_params(f) + 1), 0);
         // } while (n != font_params(f));
            set_font_params(f, n);
        }
    }
    cur_val = font_param(f, n);
  EXIT:
    scanned_result(cur_val, dimen_val_level);
}

/*tex

    Here's a similar procedure that returns a pointer to a rule node. This
    routine is called just after \TeX\ has seen \.{\\hrule} or \.{\\vrule};
    therefore |cur_cmd| will be either |hrule| or |vrule|. The idea is to store
    the default rule dimensions in the node, then to override them if
    `\.{height}' or `\.{width}' or `\.{depth}' specifications are found (in any
    order).

*/

halfword scan_rule_spec(void)
{
    /*tex |width|, |depth|, and |height| all equal |null_flag| now */
    halfword q;
    if (cur_cmd == no_vrule_cmd) {
        q = new_rule(empty_rule);
        cur_cmd = vrule_cmd;
    } else if (cur_cmd == no_hrule_cmd) {
        q = new_rule(empty_rule);
        cur_cmd = hrule_cmd;
    } else {
        q = new_rule(normal_rule);
    }
    if (cur_cmd == vrule_cmd) {
        width(q) = default_rule;
        rule_dir(q) = body_direction_par;
    } else {
        height(q) = default_rule;
        depth(q) = 0;
        rule_dir(q) = text_direction_par;
    }
  RESWITCH:
    if (scan_keyword("width")) {
        scan_normal_dimen();
        width(q) = cur_val;
        goto RESWITCH;
    }
    if (scan_keyword("height")) {
        scan_normal_dimen();
        height(q) = cur_val;
        goto RESWITCH;
    }
    if (scan_keyword("depth")) {
        scan_normal_dimen();
        depth(q) = cur_val;
        goto RESWITCH;
    }
    return q;
}

/*tex Declare procedures that scan font-related stuff. */

void scan_font_ident(void)
{
    internal_font_number f;
    halfword m;
    /*tex Get the next non-blank non-call. */
    do {
        get_x_token();
    } while (cur_cmd == spacer_cmd);
    if ((cur_cmd == def_font_cmd) || (cur_cmd == letterspace_font_cmd) || (cur_cmd == copy_font_cmd)) {
        f = get_cur_font();
    } else if (cur_cmd == set_font_cmd) {
        f = cur_chr;
        set_font_touched(f, 1);
    } else if (cur_cmd == def_family_cmd) {
        m = cur_chr;
        scan_math_family_int();
        f = fam_fnt(cur_val, m);
        set_font_touched(f, 1);
    } else {
        print_err("Missing font identifier");
        help2(
            "I was looking for a control sequence whose",
            "current meaning has been defined by \\font."
        );
        back_error();
        f = null_font;
    }
    cur_val = f;
}

/*tex

    The |scan_general_text| procedure is much like |scan_toks(false,false)|, but
    will be invoked via |expand|, i.e., recursively.

    The token list (balanced text) created by |scan_general_text| begins at
    |link(temp_token_head)| and ends at |cur_val|. (If |cur_val=temp_token_head|,
    the list is empty.)

*/

void scan_general_text(void)
{
    /*tex Here we save |scanner_status|: */
    int s;
    /*tex Here we save |warning_index|: */
    halfword w;
    /*tex Here we save |def_ref|: */
    halfword d;
    /*tex The tail of the token list being built: */
    halfword p;
    /*tex The new node being added to the token list via |store_new_token|: */
    halfword q;
    /*tex The number of unmatched left braces: */
    halfword unbalance;
    s = scanner_status;
    w = warning_index;
    d = def_ref;
    scanner_status = absorbing;
    warning_index = cur_cs;
    p = get_avail();
    def_ref = p;
    set_token_ref_count(def_ref, 0);
    p = def_ref;
    /*tex Remove the compulsory left brace. */
    scan_left_brace();
    unbalance = 1;
    while (1) {
        get_token();
        if (cur_tok < right_brace_limit) {
            if (cur_cmd < right_brace_cmd) {
                incr(unbalance);
            } else {
                decr(unbalance);
                if (unbalance == 0)
                    break;
            }
        }
        store_new_token(cur_tok);
    }
    q = token_link(def_ref);
    /*tex Discard reference count. */
    free_avail(def_ref);
    if (q == null)
        cur_val = temp_token_head;
    else
        cur_val = p;
    set_token_link(temp_token_head, q);
    scanner_status = s;
    warning_index = w;
    def_ref = d;
}

/*tex

    The |get_x_or_protected| procedure is like |get_x_token| except that
    protected macros are not expanded. It sets |cur_cmd|, |cur_chr|,
    |cur_tok|,and expands non-protected macros.

*/

void get_x_or_protected(void)
{

    while (1) {
        get_token();
        if (cur_cmd <= max_command_cmd)
            return;
        if ((cur_cmd >= call_cmd) && (cur_cmd < end_template_cmd)) {
            if (token_info(token_link(cur_chr)) == protected_token)
                return;
        }
        expand();
    }
}

/*tex

    |scan_toks|. This function returns a pointer to the tail of a new token list,
    and it also makes |def_ref| point to the reference count at the head of that
    list.

    There are two boolean parameters, |macro_def| and |xpand|. If |macro_def| is
    true, the goal is to create the token list for a macro definition; otherwise
    the goal is to create the token list for some other \TeX\ primitive:
    \.{\\mark}, \.{\\output}, \.{\\everypar}, \.{\\lowercase}, \.{\\uppercase},
    \.{\\message}, \.{\\errmessage}, \.{\\write}, or \.{\\special}. In the latter
    cases a left brace must be scanned next; this left brace will not be part of
    the token list, nor will the matching right brace that comes at the end. If
    |xpand| is false, the token list will simply be copied from the input using
    |get_token|. Otherwise all expandable tokens will be expanded until
    unexpandable tokens are left, except that the results of expanding
    `\.{\\the}' are not expanded further. If both |macro_def| and |xpand| are
    true, the expansion applies only to the macro body (i.e., to the material
    following the first |left_brace| character).

    The value of |cur_cs| when |scan_toks| begins should be the |eqtb| address of
    the control sequence to display in ``runaway'' error messages.

*/

halfword scan_toks(boolean macro_def, boolean xpand)
{
    /*tex token representing the highest parameter number */
    halfword t;
    /*tex saved token */
    halfword s;
    /*tex tail of the token list being built */
    halfword p;
    /*tex new node being added to the token list via |store_new_token| */
    halfword q;
    /*tex number of unmatched left braces */
    halfword unbalance;
    /*tex possible `\.{\#\{}' token */
    halfword hash_brace;
    if (macro_def)
        scanner_status = defining;
    else
        scanner_status = absorbing;
    warning_index = cur_cs;
    p = get_avail();
    def_ref = p;
    set_token_ref_count(def_ref, 0);
    p = def_ref;
    hash_brace = 0;
    t = zero_token;
    if (macro_def) {
        /*tex Scan and build the parameter part of the macro definition. */
        while (1) {
            /*tex Set |cur_cmd|, |cur_chr|, |cur_tok|: */
            get_token();
            if (cur_tok < right_brace_limit)
                break;
            if (cur_cmd == mac_param_cmd) {
                /*tex

                        If the next character is a parameter number, make
                        |cur_tok| a |match| token; but if it is a left brace,
                        store `|left_brace|, |end_match|', set |hash_brace|, and
                        |goto done|.

                */
                s = match_token + cur_chr;
                get_token();
             /* if (cur_cmd == left_brace_cmd) { */
                if (cur_tok < left_brace_limit) {
                    hash_brace = cur_tok;
                    store_new_token(cur_tok);
                    store_new_token(end_match_token);
                    goto DONE;
                }
                if (t == nine_token) {
                    print_err("You already have nine parameters");
                    help2(
                        "I'm going to ignore the # sign you just used,",
                        "as well as the token that followed it."
                    );
                    error();
                } else {
                    incr(t);
                    if (cur_tok != t) {
                        print_err("Parameters must be numbered consecutively");
                        help2(
                            "I've inserted the digit you should have used after the #.",
                            "Type `1' to delete what you did use."
                        );
                        back_error();
                    }
                    cur_tok = s;
                }
            }
            store_new_token(cur_tok);
        }
        store_new_token(end_match_token);
        if (cur_cmd == right_brace_cmd) {
            /*tex Express shock at the missing left brace; |goto found|. */
            print_err("Missing { inserted");
            incr(align_state);
            help2(
                "Where was the left brace? You said something like `\\def\\a}',",
                "which I'm going to interpret as `\\def\\a{}'."
            );
            error();
            goto FOUND;
        }

    } else {
        /*tex Remove the compulsory left brace. */
        scan_left_brace();
    }
  DONE:
    /*tex Scan and build the body of the token list; |goto found| when finished. */
    unbalance = 1;
    while (1) {
        if (xpand) {
            /*tex

                Expand the next part of the input. Here we insert an entire token
                list created by |the_toks| without expanding it further.

            */
            while (1) {
                get_next();
                if (cur_cmd >= call_cmd) {
                    if (token_info(token_link(cur_chr)) == protected_token) {
                        cur_cmd = relax_cmd;
                        cur_chr = no_expand_flag;
                    }
                }
                if (cur_cmd <= max_command_cmd)
                    break;
                if (cur_cmd != the_cmd) {
                    expand();
                } else {
                    q = the_toks();
                    if (token_link(temp_token_head) != null) {
                        set_token_link(p, token_link(temp_token_head));
                        p = q;
                    }
                }
            }
            x_token();
        } else {
            get_token();
        }
        if (cur_tok < right_brace_limit) {
            if (cur_cmd < right_brace_cmd) {
                incr(unbalance);
            } else {
                decr(unbalance);
                if (unbalance == 0)
                    goto FOUND;
            }
        } else if (cur_cmd == mac_param_cmd) {
            if (macro_def) {
                /*tex Look for parameter number or \.{\#\#}. */
                s = cur_tok;
                if (xpand)
                    get_x_token();
                else
                    get_token();
                if (cur_cmd != mac_param_cmd) {
                    if ((cur_tok <= zero_token) || (cur_tok > t)) {
                        print_err("Illegal parameter number in definition of ");
                        sprint_cs(warning_index);
                        help3(
                            "You meant to type ## instead of #, right?",
                            "Or maybe a } was forgotten somewhere earlier, and things",
                            "are all screwed up? I'm going to assume that you meant ##."
                        );
                        back_error();
                        cur_tok = s;
                    } else {
                        cur_tok = out_param_token - '0' + cur_chr;
                    }
                }
            }
        }
        store_new_token(cur_tok);
    }
  FOUND:
    scanner_status = normal;
    if (hash_brace != 0)
        store_new_token(hash_brace);
    return p;
}

/*tex

    Here we declare two trivial procedures in order to avoid mutually recursive
    procedures with parameters.

*/

void scan_normal_glue(void)
{
    scan_glue(glue_val_level);
}

void scan_mu_glue(void)
{
    scan_glue(mu_val_level);
}

/*tex

    The |scan_expr| procedure scans and evaluates an expression. Evaluating an
    expression is a recursive process: When the left parenthesis of a
    subexpression is scanned we descend to the next level of recursion; the
    previous level is resumed with the matching right parenthesis.

*/

typedef enum {
    /*tex \.( seen, or \.( $\langle\it expr\rangle$ \.) seen */
    expr_none  = 0,
    /*tex \.( $\langle\it expr\rangle$ \.+ seen */
    expr_add   = 1,
    /*tex \.( $\langle\it expr\rangle$ \.- seen */
    expr_sub   = 2,
    /*tex $\langle\it term\rangle$ \.* seen */
    expr_mult  = 3,
    /*tex $\langle\it term\rangle$ \./ seen */
    expr_div   = 4,
    /*tex $\langle\it term\rangle$ \.* $\langle\it factor\rangle$ \./ seen */
    expr_scale = 5,
} expression_states;

/*tex

    We want to make sure that each term and (intermediate) result is in the
    proper range. Integer values must not exceed |infinity| ($2^{31}-1$) in
    absolute value, dimensions must not exceed |max_dimen| ($2^{30}-1$). We avoid
    the absolute value of an integer, because this might fail for the value
    $-2^{31}$ using 32-bit arithmetic.

*/

/* Clear a number or dimension and set |arith_error|: */

#define num_error(A) do { \
    arith_error=true; \
    A=0; \
} while (0)

/*tex Clear a glue spec and set |arith_error|: */

#define glue_error(A) do { \
    arith_error=true; \
    reset_glue_to_zero(A); \
} while (0)

#define normalize_glue(A) do { \
    if (stretch(A)==0) stretch_order(A)=normal; \
    if (shrink(A)==0) shrink_order(A)=normal; \
} while (0)

/*tex

    Parenthesized subexpressions can be inside expressions, and this nesting has
    a stack. Seven local variables represent the top of the expression stack: |p|
    points to pushed-down entries, if any; |l| specifies the type of expression
    currently beeing evaluated; |e| is the expression so far and |r| is the state
    of its evaluation; |t| is the term so far and |s| is the state of its
    evaluation; finally |n| is the numerator for a combined multiplication and
    division, if any.

*/

#define expr_add_sub(A,B,C) add_or_sub((A),(B),(C),(r==expr_sub))
#define expr_a(A,B) expr_add_sub((A),(B),max_dimen)

/*tex

    The function |add_or_sub(x,y,max_answer,negative)| computes the sum (for
    |negative=false|) or difference (for |negative=true|) of |x| and |y|,
    provided the absolute value of the result does not exceed |max_answer|.

*/

inline static int add_or_sub(int x, int y, int max_answer, boolean negative)
{
    int a;
    if (negative)
        negate(y);
    if (x >= 0) {
        if (y <= max_answer - x)
            a = x + y;
        else
            num_error(a);
    } else if (y >= -max_answer - x) {
        a = x + y;
    } else {
        num_error(a);
    }
    return a;
}

#define expr_m(A) A = nx_plus_y((A),f,0)
#define expr_d(A) A=quotient((A),f)

/*tex

    The function |quotient(n,d)| computes the rounded quotient $q=\lfloor
    n/d+{1\over2}\rfloor$, when $n$ and $d$ are positive.

*/

inline static int quotient(int n, int d)
{
    /*tex Should the answer be negated? */
    boolean negative;
    /*tex The answer: */
    int a;
    if (d == 0) {
        num_error(a);
    } else {
        if (d > 0) {
            negative = false;
        } else {
            negate(d);
            negative = true;
        }
        if (n < 0) {
            negate(n);
            negative = !negative;
        }
        a = n / d;
        n = n - a * d;
        /*tex Avoid certain compiler optimizations! */
        d = n - d;
        if (d + n >= 0)
            incr(a);
        if (negative)
            negate(a);
    }
    return a;
}

#define expr_s(A) A=fract((A),n,f,max_dimen)

/*tex

    Finally, the function |fract(x,n,d,max_answer)| computes the integer
    $q=\lfloor xn/d+{1\over2}\rfloor$, when $x$, $n$, and $d$ are positive and
    the result does not exceed |max_answer|. We can't use floating point
    arithmetic since the routine must produce identical results in all cases; and
    it would be too dangerous to multiply by~|n| and then divide by~|d|, in
    separate operations, since overflow might well occur. Hence this subroutine
    simulates double precision arithmetic, somewhat analogous to Metafont's
    |make_fraction| and |take_fraction| routines.

*/

int fract(int x, int n, int d, int max_answer)
{
    /*tex should the answer be negated? */
    boolean negative;
    /*tex the answer */
    int a;
    /*tex a proper fraction */
    int f;
    /*tex smallest integer such that |2*h>=d| */
    int h;
    /*tex intermediate remainder */
    int r;
    /*tex temp variable */
    int t;
    if (d == 0)
        goto TOO_BIG;
    a = 0;
    if (d > 0) {
        negative = false;
    } else {
        negate(d);
        negative = true;
    }
    if (x < 0) {
        negate(x);
        negative = !negative;
    } else if (x == 0) {
        goto DONE;
    }
    if (n < 0) {
        negate(n);
        negative = !negative;
    }
    t = n / d;
    if (t > max_answer / x)
        goto TOO_BIG;
    a = t * x;
    n = n - t * d;
    if (n == 0)
        goto FOUND;
    t = x / d;
    if (t > (max_answer - a) / n)
        goto TOO_BIG;
    a = a + t * n;
    x = x - t * d;
    if (x == 0)
        goto FOUND;
    if (x < n) {
        t = x;
        x = n;
        n = t;
    }
    /*tex

        Now |0<n<=x<d| and we compute $f=\lfloor xn/d+{1\over2}\rfloor$. The loop
        here preserves the following invariant relations between |f|, |x|, |n|,
        and~|r|: (i)~$f+\lfloor(xn+(r+d))/d\rfloor=\lfloor
        x_0n_0/d+{1\over2}\rfloor$; (ii)~|-d<=r<0<n<=x<d|, where $x_0$, $n_0$ are
        the original values of~$x$ and $n$.

        Notice that the computation specifies |(x-d)+x| instead of |(x+x)-d|,
        because the latter could overflow.

    */
    f = 0;
    r = (d / 2) - d;
    h = -r;
    while (1) {
        if (odd(n)) {
            r = r + x;
            if (r >= 0) {
                r = r - d;
                incr(f);
            }
        }
        n = n / 2;
        if (n == 0)
            break;
        if (x < h) {
            x = x + x;
        } else {
            t = x - d;
            x = t + x;
            f = f + n;
            if (x < n) {
                if (x == 0)
                    break;
                t = x;
                x = n;
                n = t;
            }
        }
    }
    if (f > (max_answer - a))
        goto TOO_BIG;
    a = a + f;
  FOUND:
    if (negative)
        negate(a);
    goto DONE;
  TOO_BIG:
    num_error(a);
  DONE:
    return a;
}

/*tex Scans and evaluates an expression: */

static void scan_expr(void)
{
    /*tex saved values of |arith_error| */
    boolean a, b;
    /*tex type of expression */
    int l;
    /*tex state of expression so far */
    int r;
    /*tex state of term so far */
    int s;
    /*tex next operation or type of next factor */
    int o;
    /*tex expression so far */
    int e;
    /*tex term so far */
    int t;
    /*tex current factor */
    int f;
    /*tex numerator of combined multiplication and division */
    int n;
    /*tex top of expression stack */
    halfword p;
    /*tex for stack manipulations */
    halfword q;
    l = cur_val_level;
    a = arith_error;
    b = false;
    p = null;
    /*tex

         Scan and evaluate an expression |e| of type |l|.
         To avoid an infinite recursion we set|max_nesting| as upper limit.
         This limit is unrelated to the expansion limit |expand_depth| and it cannot be modify at compile time.

     */
    nesting++;
    if (nesting > max_nesting) {
        formatted_error("tex", "\\*expr can only be nested %d deep",max_nesting);
    }
  RESTART:
    r = expr_none;
    e = 0;
    s = expr_none;
    t = 0;
    n = 0;
  CONTINUE:
    if (s == expr_none)
        o = l;
    else
        o = int_val_level;
    /*tex

        Scan a factor |f| of type |o| or start a subexpression. Get the next
        non-blank non-call token.

    */
    do {
        get_x_token();
    } while (cur_cmd == spacer_cmd);

    if (cur_tok == other_token + '(') {
        /*tex Push the expression stack and |goto restart|. */
        q = new_node(expr_node, 0);
        vlink(q) = p;
        expr_type(q) = (quarterword) l;
        expr_state(q) = (quarterword) (4 * s + r);
        expr_e_field(q) = e;
        expr_t_field(q) = t;
        expr_n_field(q) = n;
        p = q;
        l = o;
        goto RESTART;
    }
    back_input();
    if ((o == int_val_level) || (o == attr_val_level))
        scan_int();
    else if (o == dimen_val_level)
        scan_normal_dimen();
    else if (o == glue_val_level)
        scan_normal_glue();
    else
        scan_mu_glue();
    f = cur_val;

  FOUND:
    /*tex

        Scan the next operator and set |o| and Get the next non-blank non-call
        token.

    */
    do {
        get_x_token();
    } while (cur_cmd == spacer_cmd);

    if (cur_tok == other_token + '+') {
        o = expr_add;
    } else if (cur_tok == other_token + '-') {
        o = expr_sub;
    } else if (cur_tok == other_token + '*') {
        o = expr_mult;
    } else if (cur_tok == other_token + '/') {
        o = expr_div;
    } else {
        o = expr_none;
        if (p == null) {
            if (cur_cmd != relax_cmd)
                back_input();
        } else if (cur_tok != other_token + ')') {
            print_err("Missing ) inserted for expression");
            help1("I was expecting to see `+', `-', `*', `/', or `)'. Didn't.");
            back_error();
        }
    }
    arith_error = b;
    /*tex Make sure that |f| is in the proper range. */
    if (((l == int_val_level) || (l == attr_val_level)) || (s > expr_sub)) {
        if ((f > infinity) || (f < -infinity))
            num_error(f);
    } else if (l == dimen_val_level) {
        if (abs(f) > max_dimen)
            num_error(f);
    } else {
        if ((abs(width(f)) > max_dimen) || (abs(stretch(f)) > max_dimen) || (abs(shrink(f)) > max_dimen))
            glue_error(f);
    }
    /*tex Cases for evaluation of the current term. */
    switch (s) {
        case expr_none:
            /*tex

                Applying the factor |f| to the partial term |t| (with the
                operator |s|) is delayed until the next operator |o| has been
                scanned. Here we handle the first factor of a partial term. A
                glue spec has to be copied unless the next operator is a right
                parenthesis; this allows us later on to simply modify the glue
                components.

            */
            t = f;
            if ((l >= glue_val_level) && (o != expr_none)) {
                /*tex Do we really need to copy here? */
                normalize_glue(t);
            } else {
                t = f;
            }
            break;
        case expr_mult:
            /*tex

                If a multiplication is followed by a division, the two operations
                are combined into a `scaling' operation. Otherwise the term |t|
                is multiplied by the factor |f|.

            */
            if (o == expr_div) {
                n = f;
                o = expr_scale;
            } else if ((l == int_val_level) || (l == attr_val_level)) {
                t = mult_integers(t, f);
            } else if (l == dimen_val_level) {
                expr_m(t);
            } else {
                expr_m(width(t));
                expr_m(stretch(t));
                expr_m(shrink(t));
            }
            break;
        case expr_div:
            /*tex Here we divide the term |t| by the factor |f|. */
            if (l < glue_val_level) {
                expr_d(t);
            } else {
                expr_d(width(t));
                expr_d(stretch(t));
                expr_d(shrink(t));
            }
            break;
        case expr_scale:
            /*tex Here the term |t| is multiplied by the quotient $n/f$. */
            if ((l == int_val_level) || (l == attr_val_level)) {
                t = fract(t, n, f, infinity);
            } else if (l == dimen_val_level) {
                expr_s(t);
            } else {
                expr_s(width(t));
                expr_s(stretch(t));
                expr_s(shrink(t));
            }
            break;
    }
    if (o > expr_sub) {
        s = o;
    } else {
        /*tex

            Evaluate the current expression. When a term |t| has been completed
            it is copied to, added to, or subtracted from the expression |e|.

        */
        s = expr_none;
        if (r == expr_none) {
            e = t;
        } else if ((l == int_val_level) || (l == attr_val_level)) {
            e = expr_add_sub(e, t, infinity);
        } else if (l == dimen_val_level) {
            e = expr_a(e, t);
        } else {
            /*tex

                Compute the sum or difference of two glue specs. We know that
                |stretch_order(e)>normal| implies |stretch(e)<>0| and
                |shrink_order(e)>normal| implies |shrink(e)<>0|.

            */
            width(e) = expr_a(width(e), width(t));
            if (stretch_order(e) == stretch_order(t)) {
                stretch(e) = expr_a(stretch(e), stretch(t));
            } else if ((stretch_order(e) < stretch_order(t)) && (stretch(t) != 0)) {
                stretch(e) = stretch(t);
                stretch_order(e) = stretch_order(t);
            }
            if (shrink_order(e) == shrink_order(t)) {
                shrink(e) = expr_a(shrink(e), shrink(t));
            } else if ((shrink_order(e) < shrink_order(t)) && (shrink(t) != 0)) {
                shrink(e) = shrink(t);
                shrink_order(e) = shrink_order(t);
            }
            flush_node(t);
            normalize_glue(e);
        }
        r = o;
    }
    b = arith_error;
    if (o != expr_none)
        goto CONTINUE;
    if (p != null) {
        /*tex Pop the expression stack and |goto found|. */
        f = e;
        q = p;
        e = expr_e_field(q);
        t = expr_t_field(q);
        n = expr_n_field(q);
        s = expr_state(q) / 4;
        r = expr_state(q) % 4;
        l = expr_type(q);
        p = vlink(q);
        flush_node(q);
        goto FOUND;
    }

    if (b) {
        print_err("Arithmetic overflow");
        help2(
            "I can't evaluate this expression,",
            "since the result is out of range."
        );
        error();
        if (l >= glue_val_level) {
            reset_glue_to_zero(e);
        } else {
            e = 0;
        }
    }
    arith_error = a;
    cur_val = e;
    cur_val_level = l;
    nesting--;
}
