/*
    See license.txt in the root of this project.
*/

# include "luametatex.h"

/*tex

    The code can be simplified a lot when we decide that traditional 8 bit fonts are virtualized in
    a way that avoids the split delimiter definitions (small and large) and that the traditional
    way to define characters is dropped in favor of the unicode variants. So, this might happen at
    some point. After all it makes no sense to use this engine with traditional fonts because there
    \PDFTEX\ is a better choice.

    We might also benefit more from the fact that we have prev pointers. Occasionally I visit this
    file and make some variables more verbose. I'm in no hurry with that.

*/

/*tex

    When \TEX\ reads a formula that is enclosed between |$|'s, it constructs an \quote {mlist},
    which is essentially a tree structure representing that formula. An mlist is a linear sequence
    of items, but we can regard it as a tree structure because mlists can appear within mlists. For
    example, many of the entries can be subscripted or superscripted, and such \quote {scripts} are
    mlists in their own right.

    An entire formula is parsed into such a tree before any of the actual typesetting is done,
    because the current style of type is usually not known until the formula has been fully scanned.
    For example, when the formula |$a+b \over c+d$| is being read, there is no way to tell that |a+b|
    will be in script size until |\over| has appeared.

    During the scanning process, each element of the mlist being built is classified as a relation,
    a binary operator, an open parenthesis, etc., or as a construct like |\sqrt| that must be built
    up. This classification appears in the mlist data structure.

    After a formula has been fully scanned, the mlist is converted to an hlist so that it can be
    incorporated into the surrounding text. This conversion is controlled by a recursive procedure
    that decides all of the appropriate styles by a \quote {top-down} process starting at the
    outermost level and working in towards the subformulas. The formula is ultimately pasted together
    using combinations of horizontal and vertical boxes, with glue and penalty nodes inserted as
    necessary.

    An mlist is represented internally as a linked list consisting chiefly of \quote {noads}
    (pronounced \quotation {no-adds}), to distinguish them from the somewhat similar \quote {nodes}
    in hlists and vlists. Certain kinds of ordinary nodes are allowed to appear in mlists together
    with the noads; \TEX\ tells the difference by means of the |type| field, since a noad's |type|
    is always greater than that of a node. An mlist does not contain character nodes, hlist nodes,
    vlist nodes, math nodes or unset nodes; in particular, each mlist item appears in the
    variable-size part of |mem|, so the |type| field is always present.

    Each noad is five or more words long. The first word contains the |type| and |subtype| and |link|
    fields that are already so familiar to us; the second contains the attribute list pointer, and
    the third, fourth an fifth words are called the noad's |nucleus|, |subscr|, and |supscr| fields.
    (This use of a combined attribute list is temporary. Eventually, each of fields need their own
    list)

    Consider, for example, the simple formula |$x^2$|, which would be parsed into an mlist containing
    a single element called an |ord_noad|. The |nucleus| of this noad is a representation of |x|, the
    |subscr| is empty, and the |supscr| is a representation of |2|.

    The |nucleus|, |subscr|, and |supscr| fields are further broken into subfields. If |p| points to
    a noad, and if |q| is one of its principal fields (e.g., |q=subscr(p)|), |q=null| indicates a
    field with no value (the corresponding attribute of noad |p| is not present). Otherwise, there are
    several possibilities for the subfields, depending on the |type| of |q|.

    \startitemize

        \startitem
            |type(q)=math_char_node| means that |math_fam(q)| refers to one of the sixteen font
            families, and |character(q)| is the number of a character within a font of that family, as
            in a character node.
        \stopitem

        \startitem
            |type(q) = math_text_char_node| is similar, but the character is unsubscripted and
            unsuperscripted and it is followed immediately by another character from the same font.
            (This |type| setting appears only briefly during the processing; it is used to suppress
            unwanted italic corrections.)
        \stopitem

        \startitem
            |type(q) = sub_box_node| means that |math_list(q)| points to a box node (either an
            |hlist_node| or a |vlist_node|) that should be used as the value of the field. The
            |shift_amount| in the subsidiary box node is the amount by which that box will be
            shifted downward.
        \stopitem

        \startitem
            |type(q) = sub_mlist_node| means that |math_list(q)| points to an mlist; the mlist must
            be converted to an hlist in order to obtain the value of this field.
        \stopitem

        \startitem
            In the latter case, we might have |math_list(q) = null|. This is not the same as |q =
            null|; for example, |$P_{\}$| and |$P$| produce different results (the former will not
            have the \quote {italic correction} added to the width of |P|, but the \quote {script
            skip} will be added).
        \stopitem

    \startitemize

    Concerning display skips, \TEX\ normally always inserts before and only after when larger than
    zero. This can now be controlled with |\mathdisplayskipmode|:

    \starttabulate
    \NC 0 \NC normal \TEX \NC \NR
    \NC 1 \NC always      \NC \NR
    \NC 2 \NC non-zero    \NC \NR
    \NC 3 \NC ignore      \NC \NR
    \stoptabulate

*/

math_state_info lmt_math_state = {
    .size       = 0,
    .level      = 0,
    .par_head   = NULL,
    .fam_head   = NULL,
    .last_left  = 0,
    .last_right = 0,
    .last_atom  = 0,
    .scale      = scaling_factor,
    .single     = 0,
    .padding    = 0,
};

/* */

typedef enum saved_math_entries {
    saved_math_direction_entry = 0, /* value_1 */
    saved_math_n_of_records    = 1,
} saved_math_entries;

# define saved_math_direction saved_value_1(saved_math_direction_entry)

static inline void saved_math_initialize(void)
{
    saved_type(0) = saved_record_0;
    saved_record(0) = math_save_type;
}

int tex_show_math_record(void)
{
    tex_print_str("math ");
    switch (saved_type(0)) {
       case saved_record_0:
            tex_print_format("direction %i", saved_value_1(0));
            break;
        default:
           return 0;
    }
    return 1;
}

/* */

typedef enum saved_equation_number_entries {
    saved_equation_number_location_entry = 0, /* value_1 */
    saved_equation_number_n_of_records   = 1,
} saved_equation_number_entries;

# define saved_equation_number_location saved_value_1(saved_equation_number_location_entry)

static inline void saved_equation_number_initialize(void)
{
    saved_type(0) = saved_record_0;
    saved_record(0) = number_save_type;
}

void tex_show_math_number_group(void)
{
    tex_print_cmd_chr(equation_number_cmd, saved_equation_number_location);
}

int tex_show_math_number_record(void)
{
    tex_print_str("equation number ");
    switch (saved_type(0)) {
       case saved_record_0:
            tex_print_format("location %i", saved_value_1(0));
            break;
        default:
           return 0;
    }
    return 1;
}

/* */

typedef enum saved_choice_entries {
    saved_choice_count_entry  = 0, /* value_1 */
    saved_choice_n_of_records = 1,
} saved_choice_entries;

# define saved_choice_count saved_value_1(saved_choice_count_entry)

static inline void saved_choice_initialize(void)
{
    saved_type(0) = saved_record_0;
    saved_record(0) = choice_save_type;
}

static inline int saved_choice_current_component(void)
{
    return saved_type(saved_choice_count_entry - saved_choice_n_of_records) == saved_record_0
        ? saved_value_1(saved_choice_count_entry - saved_choice_n_of_records) : -1 ;
}

static inline void saved_choice_update_component(void)
{
    saved_value_1(saved_choice_count_entry - saved_choice_n_of_records) += 1;
}

void tex_show_math_choice_group(void)
{
    tex_print_str_esc("mathchoice");
    tex_aux_show_group_count(saved_choice_count);
}

int tex_show_math_choice_record(void)
{
    tex_print_str("choice ");
    switch (saved_type(0)) {
       case saved_record_0:
            tex_print_format("count %i", saved_value_1(0));
            break;
        default:
           return 0;
    }
    return 1;
}

/* */

typedef enum saved_fraction_entries {
    saved_fraction_variant_entry   = 0, /* value_1 */
    saved_fraction_userstyle_entry = 0, /* value 2 */
    saved_fraction_autostyle_entry = 0, /* value_3 */
    saved_fraction_n_of_records    = 1,
} saved_fraction_entries;

# define saved_fraction_variant   saved_value_1(saved_fraction_variant_entry)
# define saved_fraction_userstyle saved_value_2(saved_fraction_userstyle_entry)
# define saved_fraction_autostyle saved_value_3(saved_fraction_autostyle_entry)

static inline void saved_fraction_initialize(void)
{
    saved_type(0) = saved_record_0;
    saved_record(0) = fraction_save_type;
}

static inline int saved_fraction_current_component(void)
{
    return saved_type(saved_fraction_variant_entry - saved_fraction_n_of_records) == saved_record_0
        ? saved_value_1(saved_fraction_variant_entry - saved_fraction_n_of_records) : -1 ;
}

static inline void saved_fraction_update_component(void)
{
    saved_value_1(saved_fraction_variant_entry - saved_fraction_n_of_records) += 1;
}

static inline int saved_fraction_current_userstyle(void)
{
    return saved_value_2(saved_fraction_userstyle_entry - saved_fraction_n_of_records);
}

static inline int saved_fraction_current_autostyle(void)
{
    return saved_value_3(saved_fraction_autostyle_entry - saved_fraction_n_of_records);
}

void tex_show_math_fraction_group(void)
{
    tex_print_str_esc("fraction");
    tex_aux_show_group_count(saved_fraction_variant);
}

int tex_show_math_fraction_record(void)
{
    tex_print_str("fraction ");
    switch (saved_type(0)) {
       case saved_record_0:
            tex_print_format("variant %i, userstyle %i, autostyle %i", saved_value_1(0), saved_value_2(0), saved_value_3(0));
            break;
        default:
           return 0;
    }
    return 1;
}

/* */

typedef enum saved_radical_entries {
    saved_radical_degree_done_entry = 0, /* value_1 */
    saved_radical_style_entry       = 0, /* value_2 */
    saved_radical_n_of_records      = 1,
} saved_radical_entries;

# define saved_radical_degree_done saved_value_1(saved_radical_degree_done_entry)
# define saved_radical_style       saved_value_2(saved_radical_style_entry)

static inline void saved_radical_initialize(void)
{
    saved_type(0) = saved_record_0;
    saved_record(0) = radical_save_type;
}

static inline int saved_radical_current_component(void)
{
    return saved_type(saved_radical_degree_done_entry - saved_radical_n_of_records) == saved_record_0
        ? saved_value_1(saved_radical_degree_done_entry - saved_radical_n_of_records) : -1 ;
}

static inline int saved_radical_current_style(void)
{
    return saved_value_2(saved_radical_style_entry - saved_radical_n_of_records);
}

static inline void saved_radical_update_component(void)
{
    saved_value_1(saved_radical_degree_done_entry - saved_radical_n_of_records) += 1;
}

void tex_show_math_radical_group(void)
{
    tex_print_str_esc("radical");
    tex_aux_show_group_count(saved_radical_degree_done);
}

int tex_show_math_radical_record(void)
{
    tex_print_str("radical ");
    switch (saved_type(0)) {
       case saved_record_0:
            tex_print_format("degree %i, style %i", saved_value_1(0), saved_value_2(0));
            break;
        default:
           return 0;
    }
    return 1;
}

/* */

typedef enum saved_operator_entries {
    saved_operator_variant_entry = 0, /* value_1 */
    saved_operator_n_of_records  = 1,
} saved_operator_entries;

# define saved_operator_variant saved_value_1(saved_operator_variant_entry)

static inline void saved_operator_initialize(void)
{
    saved_type(0) = saved_record_0;
    saved_record(0) = operator_save_type;
}

static inline int saved_operator_current_component(void)
{
    return saved_type(saved_operator_variant_entry - saved_operator_n_of_records) == saved_record_0
        ? saved_value_1(saved_operator_variant_entry - saved_operator_n_of_records) : -1 ;
}

static inline void saved_operator_update_component(void)
{
    saved_value_1(saved_operator_variant_entry - saved_operator_n_of_records) += 1;
}

void tex_show_math_operator_group(void)
{
    tex_print_str_esc("operator");
    tex_aux_show_group_count(saved_operator_variant);
}

int tex_show_math_operator_record(void)
{
    tex_print_str("operator ");
    switch (saved_type(0)) {
       case saved_record_0:
            tex_print_format("variant %i", saved_value_1(0));
            break;
        default:
           return 0;
    }
    return 1;
}

/* */

typedef enum saved_math_group_entries {
    saved_math_group_pointer_entry   = 0, /* value_1 */
    saved_math_group_all_class_entry = 0, /* value_2 */
    saved_math_group_n_of_records    = 1,
} saved_math_group_entries;

# define saved_math_group_pointer   saved_value_1(saved_math_group_pointer_entry)
# define saved_math_group_all_class saved_value_2(saved_math_group_all_class_entry)

static inline void saved_math_group_initialize(void)
{
    saved_type(0) = saved_record_0;
    saved_record(0) = math_group_save_type;
}

int tex_show_math_group_record(void)
{
    tex_print_str("math group ");
    switch (saved_type(0)) {
       case saved_record_0:
            tex_print_format("pointer %i, allclass %i", saved_value_1(0), saved_value_2(0));
            break;
        default:
           return 0;
    }
    return 1;
}

/* */

static int      tex_aux_scan_math           (halfword p, halfword style, int usetextfont, halfword toks, halfword toks_text, int nocomponent, halfword cls, halfword all);
static halfword tex_aux_finish_math_list    (halfword p);
static void     tex_aux_math_math_component (halfword n, int append);

# define cramped 1

# define cramped_style(A) (2 * ((A) / 2) + cramped)                     /*tex cramp the style */
# define sub_style(A)     (2 * ((A) / 4) + script_style + cramped)      /*tex smaller and cramped */
# define sup_style(A)     (2 * ((A) / 4) + script_style + ((A) % 2))    /*tex smaller */
# define small_style      sup_style
# define num_style(A)     ((A) + 2 - 2 * ((A) / 6))                     /*tex smaller unless already scriptscript */
# define smaller_style    num_style
# define denom_style(A)   (2 * ((A) / 2) + cramped + 2 - 2 * ((A) / 6)) /*tex smaller, cramped */
# define sup_sup_style(A) sup_style(sup_style((A)))                     /*tex smaller */

static inline mathdictval tex_fake_math_dict(halfword chr)
{
    mathdictval d = tex_no_dict_code();
    if (math_dict_properties_par || math_dict_group_par) {
        d.properties = (unsigned short) math_dict_properties_par;
        d.group = (unsigned short) math_dict_group_par;
        d.index = (unsigned int) chr;
    }
    return d;
}

void tex_math_copy_char_data(halfword target, halfword source, int wipelist)
{
    if (node_type(source) == math_char_node) {
        kernel_math_family(target) = kernel_math_family(source);
        kernel_math_character(target) = kernel_math_character(source);
        kernel_math_options(target) = kernel_math_options(source);
        kernel_math_properties(target) = kernel_math_properties(source);
        kernel_math_group(target) = kernel_math_group(source);
        kernel_math_index(target) = kernel_math_index(source);
    } else {
        kernel_math_list(target) = kernel_math_list(source);
        if (wipelist) {
           kernel_math_list(source) = null;
        }
    }
}

static inline void tex_math_set_scripts_options(halfword n)
{
    if (math_scripts_mode_par & fixed_super_or_sub_script_code) {
        noad_options(n) |= noad_option_fixed_super_or_sub_script;
    }
    if (math_scripts_mode_par & fixed_super_and_sub_script_code) {
        noad_options(n) |= noad_option_fixed_super_and_sub_script;
    }
    if (math_scripts_mode_par & ignore_empty_super_script_code) {
        noad_options(n) |= noad_option_ignore_empty_super_script;
    }
    if (math_scripts_mode_par & ignore_empty_sub_script_code) {
        noad_options(n) |= noad_option_ignore_empty_sub_script;
    }
    if (math_scripts_mode_par & ignore_empty_prime_script_code) {
        noad_options(n) |= noad_option_ignore_empty_prime_script;
    }
}

/*tex identity style */

# define display_style_nibble(a)               (a << 28)
# define cramped_display_style_nibble(a)       (a << 24)
# define text_style_nibble(a)                  (a << 20)
# define cramped_text_style_nibble(a)          (a << 16)
# define script_style_nibble(a)                (a << 12)
# define cramped_script_style_nibble(a)        (a <<  8)
# define script_script_style_nibble(a)         (a <<  4)
# define cramped_script_script_style_nibble(a) (a <<  0)

static const unsigned int math_variant_presets[last_math_style_variant+1] = {
    /*tex identity style */
    // math_normal_style_variant_preset
    (unsigned int) (0
      + display_style_nibble              (display_style              )
      + cramped_display_style_nibble      (cramped_display_style      )
      + text_style_nibble                 (text_style                 )
      + cramped_text_style_nibble         (cramped_text_style         )
      + script_style_nibble               (script_style               )
      + cramped_script_style_nibble       (cramped_script_style       )
      + script_script_style_nibble        (script_script_style        )
      + cramped_script_script_style_nibble(cramped_script_script_style)
    ),
    /*tex cramp the style */
    // math_cramped_style_variant_preset
    (unsigned int) (0
      + display_style_nibble              (cramped_display_style      )
      + cramped_display_style_nibble      (cramped_display_style      )
      + text_style_nibble                 (cramped_text_style         )
      + cramped_text_style_nibble         (cramped_text_style         )
      + script_style_nibble               (cramped_script_style       )
      + cramped_script_style_nibble       (cramped_script_style       )
      + script_script_style_nibble        (cramped_script_script_style)
      + cramped_script_script_style_nibble(cramped_script_script_style)
    ),
    /*tex smaller and cramped */
    // math_subscript_style_variant_preset
    (unsigned int) (0
      + display_style_nibble              (cramped_script_style       )
      + cramped_display_style_nibble      (cramped_script_style       )
      + text_style_nibble                 (cramped_script_style       )
      + cramped_text_style_nibble         (cramped_script_style       )
      + script_style_nibble               (cramped_script_script_style)
      + cramped_script_style_nibble       (cramped_script_script_style)
      + script_script_style_nibble        (cramped_script_script_style)
      + cramped_script_script_style_nibble(cramped_script_script_style)
    ),
    /*tex smaller */
    // math_superscript_style_variant_preset
    (unsigned int) (0
      + display_style_nibble              (script_style               )
      + cramped_display_style_nibble      (cramped_script_style       )
      + text_style_nibble                 (script_style               )
      + cramped_text_style_nibble         (cramped_script_style       )
      + script_style_nibble               (script_script_style        )
      + cramped_script_style_nibble       (cramped_script_script_style)
      + script_script_style_nibble        (script_script_style        )
      + cramped_script_script_style_nibble(cramped_script_script_style)
    ),
    // math_small_style_variant_preset
    (unsigned int) (0
      + display_style_nibble              (script_style               )
      + cramped_display_style_nibble      (cramped_script_style       )
      + text_style_nibble                 (script_style               )
      + cramped_text_style_nibble         (cramped_script_style       )
      + script_style_nibble               (script_script_style        )
      + cramped_script_style_nibble       (cramped_script_script_style)
      + script_script_style_nibble        (script_script_style        )
      + cramped_script_script_style_nibble(cramped_script_script_style)
    ),
    /*tex smaller unless already scriptscript */
    // math_smaller_style_variant_preset
    (unsigned int) (0
      + display_style_nibble              (text_style                 )
      + cramped_display_style_nibble      (cramped_text_style         )
      + text_style_nibble                 (script_style               )
      + cramped_text_style_nibble         (cramped_script_style       )
      + script_style_nibble               (script_script_style        )
      + cramped_script_style_nibble       (cramped_script_script_style)
      + script_script_style_nibble        (script_script_style        )
      + cramped_script_script_style_nibble(cramped_script_script_style)
    ),
    // math_numerator_style_variant_preset
    (unsigned int) (0
      + display_style_nibble              (text_style                 )
      + cramped_display_style_nibble      (cramped_text_style         )
      + text_style_nibble                 (script_style               )
      + cramped_text_style_nibble         (cramped_script_style       )
      + script_style_nibble               (script_script_style        )
      + cramped_script_style_nibble       (cramped_script_script_style)
      + script_script_style_nibble        (script_script_style        )
      + cramped_script_script_style_nibble(cramped_script_script_style)
    ),
    // math_denominator_style_variant_preset
    (unsigned int) (0
      + display_style_nibble              (cramped_text_style         )
      + cramped_display_style_nibble      (cramped_text_style         )
      + text_style_nibble                 (cramped_script_style       )
      + cramped_text_style_nibble         (cramped_script_style       )
      + script_style_nibble               (cramped_script_script_style)
      + cramped_script_style_nibble       (cramped_script_script_style)
      + script_script_style_nibble        (cramped_script_script_style)
      + cramped_script_script_style_nibble(cramped_script_script_style)
    ),
    /*tex smaller, keep cramped */
    // math_double_superscript_style_variant_preset
    (unsigned int) (0
      + display_style_nibble              (script_script_style        )
      + cramped_display_style_nibble      (cramped_script_script_style)
      + text_style_nibble                 (script_script_style        )
      + cramped_text_style_nibble         (cramped_script_script_style)
      + script_style_nibble               (script_script_style        )
      + cramped_script_style_nibble       (cramped_script_script_style)
      + script_script_style_nibble        (script_script_style        )
      + cramped_script_script_style_nibble(cramped_script_script_style)
    ),
};

int tex_get_math_variant_preset(int i)
{
    return valid_math_style_variant(i) ? math_variant_presets[i] : 0;
}

/*tex
    This is very \TEX: a variable class influences the family being used.
*/

halfword tex_size_of_style(halfword style)
{
    switch (style) {
        case script_style:
        case cramped_script_style:
            return script_size;
        case script_script_style:
        case cramped_script_script_style:
            return script_script_size;
        default:
            return text_size;
    }
}

// halfword tex_math_style_variant(halfword style, halfword parameter)
// {
//     switch (tex_get_math_parameter(style, parameter, NULL)) {
//         case math_normal_style_variant:
//             return style;
//         case math_cramped_style_variant:
//             return cramped_style(style);
//         case math_subscript_style_variant:
//             return sub_style(style);
//         case math_superscript_style_variant:
//             return sup_style(style);
//         case math_small_style_variant:
//             return small_style(style);
//         case math_smaller_style_variant:
//             return smaller_style(style);
//         case math_numerator_style_variant:
//             return num_style(style);
//         case math_denominator_style_variant:
//             return denom_style(style);
//         case math_double_superscript_variant:
//             return sup_sup_style(style);
//         default:
//             return style;
//     }
// }

halfword tex_math_style_variant(halfword style, halfword parameter)
{
    return ((unsigned int) tex_get_math_parameter(style,parameter,NULL) >> (4 * (7 - style))) & 0xF;
}

int tex_math_has_class_option(halfword cls, int option)
{
    halfword value = count_parameter(first_math_options_code + cls);
    if (value == no_class_options) {
        unsigned parent = (unsigned) count_parameter(first_math_parent_code + cls);
        cls = (parent >> 16) & 0xFF;
        if (! valid_math_class_code(cls)) {
            return 0;
        }
        value = count_parameter(first_math_options_code + cls);
    }
    return (value & option) == option;
}

int tex_math_has_class_parent(halfword cls)
{
    halfword value = count_parameter(first_math_options_code + cls);
    if (value == no_class_options) {
        unsigned parent = (unsigned) count_parameter(first_math_parent_code + cls);
        return (parent >> 16) & 0xFF;
    }
    return 0;
}

static void tex_aux_unsave_math(void)
{
    tex_unsave();
    lmt_save_state.save_stack_data.ptr -= saved_math_n_of_records;
    tex_flush_node_list(lmt_dir_state.text_dir_ptr);
    lmt_dir_state.text_dir_ptr = saved_math_direction;
}

/*tex

    Sometimes it is necessary to destroy an mlist. The following subroutine empties the current
    list, assuming that |abs(mode) = mmode| aka |is_m_mode(mode)|.

*/

void tex_flush_math(void)
{
    halfword head = cur_list.head;
    tex_flush_node_list(node_next(head));
    tex_flush_node_list(cur_list.incomplete_noad);
    node_next(head) = null;
    cur_list.tail = head;
    cur_list.incomplete_noad = null;
}

/*tex A printing helper. */

static void tex_aux_print_parameter(const char *what, halfword style, halfword param, halfword indirect, halfword value)
{
    tex_begin_diagnostic();
    tex_print_format("{%s ", what);
    if (indirect >= 0 && indirect <= last_math_indirect) {
        tex_print_str(lmt_interface.math_indirect_values[indirect].name);
        tex_print_char(' ');
    }
    if (param < math_parameter_last) {
        tex_print_cmd_chr(math_parameter_cmd, param);
    } else {
        tex_print_format("%x %x ", math_parameter_spacing_left(param), math_parameter_spacing_right(param));
    }
    tex_print_cmd_chr(math_style_cmd, style);
    tex_print_char('=');
    switch (math_parameter_value_type(param)) {
        case math_integer_parameter:
        case math_style_parameter:
            tex_print_int(value);
            break;
        case math_dimension_parameter:
            tex_print_dimension(value, pt_unit);
            break;
        case math_muglue_parameter:
            tex_print_spec(value, mu_unit);
            break;
        default:
            tex_print_int(value);
            break;
    }
    tex_print_char('}');
    tex_end_diagnostic();
}

static void tex_aux_print_fam(const char *what, halfword size, halfword fam)
{
    tex_begin_diagnostic();
    tex_print_format("{%s %C family %i: %F}", what, define_family_cmd, size, fam, tex_fam_fnt(fam, size));
    tex_end_diagnostic();
}

/*tex
    Before we can do anything in math mode, we need fonts. We can use |max_n_of_math_families|
    instead of 256 but we need to pack in bytes anyway so there is no gain.
*/

int tex_fam_fnt(int fam, int size)
{
    /* todo check valid fam */
    sa_tree_item item;
    sa_get_item_4(lmt_math_state.fam_head, fam + (256 * size), &item);
    return (int) item.int_value;
}

void tex_def_fam_fnt(int fam, int size, int fnt, int level)
{
    sa_tree_item item;
    item.int_value = fnt;
    sa_set_item_4(lmt_math_state.fam_head, fam + (256 * size), item, level);
    if (tracing_assigns_par > 1) {
        tex_aux_print_fam("assigning", size, fam);
    }
    tex_fixup_math_parameters(fam, size, fnt, level);
}

static void tex_aux_unsave_math_fam_data(int gl)
{
    if (lmt_math_state.fam_head->stack) {
        while (lmt_math_state.fam_head->sa_stack_ptr > 0 && abs(lmt_math_state.fam_head->stack[lmt_math_state.fam_head->sa_stack_ptr].level) >= (int) gl) {
            sa_stack_item item = lmt_math_state.fam_head->stack[lmt_math_state.fam_head->sa_stack_ptr];
            if (item.level > 0) {
                sa_rawset_item_4(lmt_math_state.fam_head, item.code, item.value_1);
                /*tex Now do a trace message, if requested. */
                if (tracing_restores_par > 1) {
                    int size = item.code / 256;
                    int fam = item.code % 256;
                    tex_aux_print_fam("restoring", size, fam);
                }
            }
            --lmt_math_state.fam_head->sa_stack_ptr;
        }
    }
}

/*tex Math parameters, we have a lot of them! Todo: move the style into 2 */

void tex_def_math_parameter(int style, int param, scaled value, int level, int indirect, int fixup)
{
    if (fixup && ignore_math_parameter(param) == 2) {
        return;
    } else {
        sa_tree_item item1, item2;
        int different = 1;
        if (level <= 1) {
            if (math_parameter_value_type(param) == math_muglue_parameter) {
                sa_get_item_8(lmt_math_state.par_head, (param + (math_parameter_max_range * style)), &item1, &item2);
                if (item2.int_value == indirect_math_regular && item1.int_value > thick_muskip_code) {
                    if (lmt_node_memory_state.nodesizes[item1.int_value]) {
                        tex_free_node(item1.int_value, glue_spec_size);
                    }
                }
            }
        } else {
            /*tex Less tracing at the cost of a lookup. */
            sa_get_item_8(lmt_math_state.par_head, (param + (math_parameter_max_range * style)), &item1, &item2);
            different = item1.int_value != value || item2.int_value != indirect;
        }
     // if (different) { // maybe, because we did a lookup anyway, but there is little gain
            item1.int_value = value;
            item2.int_value = indirect;
            sa_set_item_8(lmt_math_state.par_head, (param + (math_parameter_max_range * style)), item1, item2, level);
            if (different && tracing_assigns_par > 1) {
                tex_aux_print_parameter("assigning", style, param, indirect, value);
            }
     // }
    }
}

// mukern .. there is no mudimen

scaled tex_get_math_parameter(int style, int param, halfword *type)
{
    halfword indirect, value;
    sa_tree_item v1, v2;
    sa_get_item_8(lmt_math_state.par_head, (param + (math_parameter_max_range * style)), &v1, &v2);
    indirect = v2.int_value == lmt_math_state.par_head->dflt.int_value ? indirect_math_unset : v2.uint_value;
    value = v1.int_value;
    switch (indirect) {
        case indirect_math_unset:
            if (type) {
                *type = no_val_level;
            }
            return MATHPARAMDEFAULT;
        /* we stored nothing */
        case indirect_math_regular:
            switch (math_parameter_value_type(param)) {
                case math_dimension_parameter:
                    if (type) {
                        *type = dimension_val_level;
                    }
                    return value;
                case math_muglue_parameter:
                    if (type) {
                        *type = muglue_val_level;
                    }
                    return value <= thick_muskip_code ? muglue_parameter(value) : value;
             // case math_integer_parameter:
             // case math_style_parameter:
                default:
                    if (type) {
                        *type = integer_val_level;
                    }
                    return value;
            }
        /* we stored cs */
        case indirect_math_integer:
            if (! value) {
                if (type) {
                    *type = integer_val_level;
                }
                return value;
            } else if (eq_type(value) == integer_cmd) {
                if (type) {
                    *type = integer_val_level;
                }
                return eq_value(value);
            } else {
                goto MISMATCH;
            }
        case indirect_math_dimension:
            if (! value) {
                if (type) {
                    *type = dimension_val_level;
                }
                return value;
            } else if (eq_type(value) == dimension_cmd) {
                if (type) {
                    *type = dimension_val_level;
                }
                return eq_value(value);
            } else if (eq_type(value) == posit_cmd) {
                if (type) {
                    *type = dimension_val_level;
                }
                return tex_posit_to_dimension(eq_value(value));
            } else {
                goto MISMATCH;
            }
        case indirect_math_mugluespec:
            if (! value) {
                if (type) {
                    *type = muglue_val_level;
                }
                return value;
            } else {
                switch (eq_type(value)) {
                    case mugluespec_cmd:
                        if (type) {
                            *type = muglue_val_level;
                        }
                        return eq_value(value);
                    default:
                        goto MISMATCH;
                }

            }
        case indirect_math_gluespec:
            if (! value) {
                if (type) {
                    *type = glue_val_level;
                }
                return value;
            } else {
                switch (eq_type(value)) {
                    case gluespec_cmd:
                        if (type) {
                            *type = glue_val_level;
                        }
                        return eq_value(value);
                    default:
                        goto MISMATCH;
                }
            }
        /* we stored chr */
        case indirect_math_register_integer:
            if (! value) {
                if (type) {
                    *type = integer_val_level;
                }
                return value;
            } else if (eq_type(value) == register_integer_reference_cmd) {
                if (type) {
                    *type = integer_val_level;
                }
                return eq_value(value);
            } else {
                goto MISMATCH;
            }
        case indirect_math_register_dimension:
            if (! value) {
                if (type) {
                    *type = dimension_val_level;
                }
                return value;
            } else if (eq_type(value) == register_dimension_reference_cmd) {
                if (type) {
                    *type = dimension_val_level;
                }
                return eq_value(value);
            } else {
                goto MISMATCH;
            }
        case indirect_math_register_gluespec:
            if (! value) {
                if (type) {
                    *type = glue_val_level;
                }
                return value;
            } else if (eq_type(value) == register_glue_reference_cmd) {
                if (type) {
                    *type = glue_val_level;
                }
                return eq_value(value);
            } else {
                goto MISMATCH;
            }
        case indirect_math_register_mugluespec:
            if (! value) {
                if (type) {
                    *type = muglue_val_level;
                }
                return value;
            } else if (eq_type(value) == register_muglue_reference_cmd) {
                if (type) {
                    *type = muglue_val_level;
                }
                return eq_value(value);
            } else {
                goto MISMATCH;
            }
        case indirect_math_internal_integer:
            if (! value) {
                if (type) {
                    *type = integer_val_level;
                }
                return value;
            } else if (eq_type(value) == internal_integer_reference_cmd) {
                if (type) {
                    *type = integer_val_level;
                }
                return eq_value(value);
            } else {
                goto MISMATCH;
            }
        case indirect_math_internal_dimension:
            if (! value) {
                if (type) {
                    *type = dimension_val_level;
                }
                return value;
            } else if (eq_type(value) == internal_dimension_reference_cmd) {
                if (type) {
                    *type = dimension_val_level;
                }
                return eq_value(value);
            } else {
                goto MISMATCH;
            }
        case indirect_math_internal_gluespec:
            if (! value) {
                if (type) {
                    *type = glue_val_level;
                }
                return value;
            } else if (eq_type(value) == internal_glue_reference_cmd) {
                if (type) {
                    *type = glue_val_level;
                }
                return eq_value(value);
            } else {
                goto MISMATCH;
            }
        case indirect_math_internal_mugluespec:
            if (! value) {
                if (type) {
                    *type = muglue_val_level;
                }
                return value;
            } else  if (eq_type(value) == internal_muglue_reference_cmd) {
                if (type) {
                    *type = muglue_val_level;
                }
                return eq_value(value);
            } else {
                goto MISMATCH;
            }
        default:
          MISMATCH:
            tex_handle_error(
                normal_error_type,
                "Invalid inherited math parameter",
                "You probably changed the type of the inherited math parameter, so I will "
                "use zero instead."
            );
            return 0;
    }
}

int tex_has_math_parameter(int style, int param)
{
    sa_tree_item v1, v2;
    sa_get_item_8(lmt_math_state.par_head, (param + (math_parameter_max_range * style)), &v1, &v2);
    return v2.int_value == lmt_math_state.par_head->dflt.int_value ? indirect_math_unset : v2.uint_value;
}

static void tex_aux_unsave_math_parameter_data(int gl)
{
    if (lmt_math_state.par_head->stack) {
     // printf("unsaving level %i >= gl %i\n", lmt_math_state.par_head->stack[lmt_math_state.par_head->sa_stack_ptr].level, gl);
        while (lmt_math_state.par_head->sa_stack_ptr > 0 && abs(lmt_math_state.par_head->stack[lmt_math_state.par_head->sa_stack_ptr].level) >= gl) {
            sa_stack_item item = lmt_math_state.par_head->stack[lmt_math_state.par_head->sa_stack_ptr];
            if (item.level > 0) {
                int param = item.code % math_parameter_max_range;
                int style = item.code / math_parameter_max_range;
                if (math_parameter_value_type(param) == math_muglue_parameter) {
                    sa_tree_item item1, item2;
                    sa_get_item_8(lmt_math_state.par_head, item.code, &item1, &item2);
                    if (item2.int_value == indirect_math_regular && item1.int_value > thick_muskip_code) {
                     /* if (tex_valid_node(item1.int_value)) { */
                        if (lmt_node_memory_state.nodesizes[item1.int_value]) {
                            // printf("HERE 2.1: %i %i / %i %i / %i\n",item2.int_value,item1.int_value, item.value_1.int_value, item.value_2.int_value, node_type(item1.int_value));
                            tex_free_node(item1.int_value, glue_spec_size);
                        } else {
                            // printf("HERE 2.2: %i %i / %i %i / %i\n",item2.int_value,item1.int_value, item.value_1.int_value, item.value_2.int_value, node_type(item1.int_value));
                        }
                    }
                }
                sa_rawset_item_8(lmt_math_state.par_head, item.code, item.value_1, item.value_2);
                /*tex Do a trace message, if requested. */
                if (tracing_restores_par > 1) {
                    int indirect = item.value_2.int_value;
                    tex_aux_print_parameter("restoring", style, param, indirect, tex_get_math_parameter(style, param, NULL));
                }
            }
         // printf("restored %i @ %i\n",lmt_math_state.par_head->sa_stack_ptr,gl);
            --lmt_math_state.par_head->sa_stack_ptr;
        }
    }
}

/*tex Saving and unsaving of both: */

void tex_unsave_math_data(int grouplevel)
{
    tex_aux_unsave_math_fam_data(grouplevel);
    tex_aux_unsave_math_parameter_data(grouplevel);
}

/*tex Dumping and undumping: */

void tex_dump_math_data(dumpstream f)
{
    if (! lmt_math_state.fam_head) {
        lmt_math_state.fam_head = sa_new_tree(mathfont_sparse_identifier, MATHFONTSTACK, MATHFONTSTEP, 4, (sa_tree_item) { .int_value = MATHFONTDEFAULT });
    }
    sa_dump_tree(f, lmt_math_state.fam_head);
    if (! lmt_math_state.par_head) {
        lmt_math_state.par_head = sa_new_tree(mathparam_sparse_identifier, MATHPARAMSTACK, MATHPARAMSTEP, 8, (sa_tree_item) { .int_value = MATHPARAMDEFAULT });
    }
    sa_dump_tree(f, lmt_math_state.par_head);
}

void tex_undump_math_data(dumpstream f)
{
    lmt_math_state.fam_head = sa_undump_tree(f);
    lmt_math_state.par_head = sa_undump_tree(f);
}

void tex_initialize_math(void)
{
    if (! lmt_math_state.fam_head) {
        lmt_math_state.fam_head = sa_new_tree(mathfont_sparse_identifier, MATHFONTSTACK, MATHFONTSTEP, 4, (sa_tree_item) { .int_value = MATHFONTDEFAULT });
    }
    if (! lmt_math_state.par_head) {
        lmt_math_state.par_head = sa_new_tree(mathparam_sparse_identifier, MATHPARAMSTACK, MATHPARAMSTEP, 8, (sa_tree_item) { .int_value = MATHPARAMDEFAULT });
        tex_initialize_math_spacing();
    }
    return;
}

/*tex

    Each portion of a formula is classified as Ord, Op, Bin, Rel, Ope, Clo, Pun, or Inn, for purposes
    of spacing and line breaking. An |ord_noad|, |op_noad|, |bin_noad|, |rel_noad|, |open_noad|,
    |close_noad|, |punct_noad|, or |inner_noad| is used to represent portions of the various types.
    For example, an |=| sign in a formula leads to the creation of a |rel_noad| whose |nucleus| field
    is a representation of an equals sign (usually |fam = 0|, |character = 075|). A formula preceded
    by |\mathrel| also results in a |rel_noad|. When a |rel_noad| is followed by an |op_noad|, say,
    and possibly separated by one or more ordinary nodes (not noads), \TEX\ will insert a penalty
    node (with the current |rel_penalty|) just after the formula that corresponds to the |rel_noad|,
    unless there already was a penalty immediately following; and a \quote {thick space} will be
    inserted just before the formula that corresponds to the |op_noad|.

    A noad of type |ord_noad|, |op_noad|, \dots, |inner_noad| usually has a |subtype = normal|. The
    only exception is that an |op_noad| might have |subtype = limits| or |no_limits|, if the normal
    positioning of limits has been overridden for this operator.

    A |radical_noad| also has a |left_delimiter| field, which usually represents a square root sign.

    A |fraction_noad| has a |right_delimiter| field as well as a |left_delimiter|.

    Delimiter fields have four subfields called |small_fam|, |small_char|, |large_fam|, |large_char|.
    These subfields represent variable-size delimiters by giving the \quote {small} and \quote
    {large} starting characters, as explained in Chapter~17 of {\em The \TEX book}.

    A |fraction_noad| is actually quite different from all other noads. It has |thickness|,
    |denominator|, and |numerator| fields instead of |nucleus|, |subscr|, and |supscr|. The
    |thickness| is a scaled value that tells how thick to make a fraction rule; however, the special
    value |preset_rule_thickness| is used to stand for the |preset_rule_thickness| of the current
    size. The |numerator| and |denominator| point to mlists that define a fraction; we always have
    |type(numerator) = type(denominator) = sub_mlist|. The |left_delimiter| and |right_delimiter|
    fields specify delimiters that will be placed at the left and right of the fraction. In this way,
    a |fraction_noad| is able to represent all of \TEX's operators |\over|, |\atop|, |\above|,
    |\overwithdelims|, |\atopwithdelims|, and |\abovewithdelims|.

    The |new_noad| function creates an |ord_noad| that is completely |null|.

*/

halfword tex_new_sub_box(halfword curbox)
{
    halfword noad = tex_new_node(simple_noad, ordinary_noad_subtype);
    halfword sbox = tex_new_node(sub_box_node, 0);
    noad_nucleus(noad) = sbox;
    kernel_math_list(sbox) = curbox;
    return noad;
}

static quarterword tex_aux_set_math_char(halfword target, mathcodeval *mval, mathdictval *dval)
{
    halfword hmcode = tex_get_hm_code(mval->character_value);
    kernel_math_character(target) = mval->character_value;
    if (variable_family_par == -2) {
        /*tex For those (read: context) who want to use this variable class as intended. */
        kernel_math_family(target) = cur_fam_par_in_range ? cur_fam_par : mval->family_value;
        node_subtype(target) = mval->class_value;
    } else if (mval->class_value == math_use_current_family_code) {
        /*tex For CMS chairman MS, so that he can answer a ltx question someplace. */
        kernel_math_family(target) = cur_fam_par_in_range ? cur_fam_par : mval->family_value;
        node_subtype(target) = ordinary_noad_subtype;
    } else if (mval->family_value == variable_family_par) {
        kernel_math_family(target) = cur_fam_par_in_range ? cur_fam_par : mval->family_value;
        node_subtype(target) = mval->class_value;
    } else {
        kernel_math_family(target) = mval->family_value;
        node_subtype(target) = mval->class_value;
    }
    if (dval) {
        kernel_math_properties(target) = dval->properties;
        kernel_math_group(target) = dval->group;
        kernel_math_index(target) = dval->index;
    }
    if ((hmcode & auto_discretionary_normal) == auto_discretionary_normal) { // has_discretionary_normal
        math_kernel_node_set_option(target, math_kernel_auto_discretionary);
    }
    if ((hmcode & auto_discretionary_italic) == auto_discretionary_italic) {  // has_discretionary_italic
        math_kernel_node_set_option(target, math_kernel_full_discretionary);
    }
    return node_subtype(target);
}

/*tex

    A few more kinds of noads will complete the set: An |under_noad| has its nucleus underlined; an
    |over_noad| has it overlined. An |accent_noad| places an accent over its nucleus; the accent
    character appears as |math_fam (accent_chr (p))| and |math_character (accent_chr (p))|. A
    |vcenter_noad| centers its nucleus vertically with respect to the axis of the formula; in such
    noads we always have |type (nucleus (p)) = sub_box|.

    And finally, we have the |fence_noad| type, to implement \TEX's |\left| and |\right| as well as
    \ETEX's |\middle|. The |nucleus| of such noads is replaced by a |delimiter| field; thus, for
    example, |\left(| produces a |fence_noad| such that |delimiter(p)| holds the family and
    character codes for all left parentheses. A |fence_noad| of subtype |left_noad_side| never
    appears in an mlist except as the first element, and a |fence_noad| with subtype
    |right_noad_side| never appears in an mlist except as the last element; furthermore, we either
    have both a |left_noad_side| and a |right_noad_side|, or neither one is present.

    Math formulas can also contain instructions like |\textstyle| that override \TeX's normal style
    rules. A |style_node| is inserted into the data structure to record such instructions; it is
    three words long, so it is considered a node instead of a noad. The |subtype| is either
    |display_style| or |text_style| or |script_style| or |script_script_style|. The second and
    third words of a |style_node| are not used, but they are present because a |choice_node| is
    converted to a |style_node|.

    \TEX\ uses even numbers 0, 2, 4, 6 to encode the basic styles |display_style|, \dots,
    |script_script_style|, and adds~1 to get the \quote {cramped} versions of these styles. This
    gives a numerical order that is backwards from the convention of Appendix~G in {\em The \TEX
    book}; i.e., a smaller style has a larger numerical value.

*/

void tex_run_math_style(void) {
    switch (cur_chr) {
        case yet_unset_math_style:
            {
                halfword style = tex_scan_math_style_identifier(1, 0);
                if (is_valid_math_style(style)) {
                    halfword noad = tex_new_node(style_node, (quarterword) style);
                    cur_list.math_style = style;
                    tex_tail_append(noad);
                }
            }
            break;
        case scaled_math_style:
            {
                halfword noad = tex_new_node(style_node, scaled_math_style);
                style_scale(noad) = tex_scan_integer(0, NULL, NULL);
             // style_scale(noad) = tex_scan_positive_scale(0);
                cur_list.math_scale = style_scale(noad);
                tex_tail_append(noad);
            }
            break;
        case currently_set_math_style:
            /* no node gets injected */
            break;
        default:
            if (is_valid_math_style(cur_chr)) {
                halfword noad = tex_new_node(style_node, (quarterword) cur_chr);
                cur_list.math_style = cur_chr;
                tex_tail_append(noad);
            } else {
                /*tex For now silently ignored. */
            }
    }
}

/*tex

    Let's consider now the previously unwritten part of |show_node_list| that displays the things
    that can only be present in mlists; this program illustrates how to access the data structures
    just defined.

    In the context of the following program, |p| points to a node or noad that should be displayed,
    and the current string contains the \quote {recursion history} that leads to this point. The
    recursion history consists of a dot for each outer level in which |p| is subsidiary to some
    node, or in which |p| is subsidiary to the |nucleus| field of some noad; the dot is replaced by
    |_| or |^| or |/| or |\\| if |p| is descended from the |subscr| or |supscr| or |denominator| or
    |numerator| fields of noads. For example, the current string would be |{\^_/}| if |p| points to
    the |ord_noad| for |x| in the (ridiculous) formula {$\sqrt {a ^ {\mathinner {b _
    {c \over x+y}}}}$|.

*/

static void tex_aux_display_choice_noad    (halfword n, int threshold, int max);
static void tex_aux_display_parameter_node (halfword n);
static void tex_aux_display_simple_noad    (halfword n, int threshold, int max);
static void tex_aux_display_radical_noad   (halfword n, int threshold, int max);
static void tex_aux_display_accent_noad    (halfword n, int threshold, int max);
static void tex_aux_display_fence_noad     (halfword n, int threshold, int max);
static void tex_aux_display_fraction_noad  (halfword n, int threshold, int max);

static void tex_aux_print_fam_and_char(halfword n)
{
    tex_print_format(", family %x, character %x, original %x", kernel_math_family(n), kernel_math_character(n));
    tex_aux_show_dictionary(n, kernel_math_properties(n), kernel_math_group(n), kernel_math_index(n), tex_fam_fnt(kernel_math_family(n), 0), kernel_math_character(n));
}

int tex_show_math_node(halfword n, int threshold, int max)
{
    switch (node_type(n)) {
        case style_node:
            /* why not shown? */
            break;
        case choice_node:
            tex_aux_display_choice_noad(n, threshold, max);
            break;
        case parameter_node:
            tex_aux_display_parameter_node(n);
            break;
        case simple_noad:
            tex_aux_display_simple_noad(n, threshold, max);
            break;
        case radical_noad:
            tex_aux_display_radical_noad(n, threshold, max);
            break;
        case accent_noad:
            tex_aux_display_accent_noad(n, threshold, max);
            break;
        case fence_noad:
            tex_aux_display_fence_noad(n, threshold, max);
            break;
        case fraction_noad:
            tex_aux_display_fraction_noad(n, threshold, max);
            break;
        case math_text_char_node:
        case math_char_node:
            tex_aux_print_fam_and_char(n);
            break;
        case sub_box_node:
            tex_print_node_list(kernel_math_list(n), NULL, threshold, max);
            break;
        case sub_mlist_node:
            if (kernel_math_list(n)) {
                tex_print_node_list(kernel_math_list(n), NULL, threshold, max);
            } else {
                tex_print_str(", empty");
            }
            break;
        default:
            return 0;
    }
    return 1;
}

static inline halfword tex_aux_valid_delimiter(halfword d)
{
    return (d && (delimiter_small_family(d) || delimiter_small_character(d) || delimiter_large_family(d) || delimiter_large_character(d))) ? d : null;
}

static void tex_aux_print_delimiter(halfword d)
{
    if (delimiter_small_family(d) < 0) {
        /*tex This should never happen. */
        tex_print_int(-1);
    } else if (delimiter_small_family(d) < 16 && delimiter_large_family(d) < 16 && delimiter_small_character(d) < 256 && delimiter_large_character(d) < 256) {
        /*tex Traditional tex style. */
        int a = delimiter_small_family(d) * 256 + delimiter_small_character(d);
        a = a * 0x1000 + delimiter_large_family(d) * 256 + delimiter_large_character(d);
        tex_print_format(", code %x", a);
    } else if ((delimiter_large_family(d) == 0 && delimiter_large_character(d) == 0) || delimiter_small_character(d) > 65535 || delimiter_large_character(d) > 65535) {
        /*tex \LUATEX\ style. */
        tex_print_format(", family %x, character %x", delimiter_small_family(d), delimiter_small_character(d));
    }
}

/*tex

    The next subroutine will descend to another level of recursion when a subsidiary mlist needs to
    be displayed. The parameter |c| indicates what character is to become part of the recursion
    history. An empty mlist is distinguished from a missing field, because these are not equivalent
    (as explained above).

    There are more fields and options that we have to report here so that is a todo for a rainy day
    with nothing else to do.

*/

static void tex_aux_display_common_noad(halfword n, int threshold, int max)
{
    tex_print_node_list(noad_nucleus(n), "nucleus", threshold, max);
    tex_print_node_list(noad_supscr(n), "superscript", threshold, max);
    tex_print_node_list(noad_subscr(n), "subscript", threshold, max);
    tex_print_node_list(noad_supprescr(n), "superprescript", threshold, max);
    tex_print_node_list(noad_subprescr(n), "subprescript", threshold, max);
    tex_print_node_list(noad_prime(n), "primescript", threshold, max);
    tex_print_node_list(noad_new_hlist(n), "newhlist", threshold, max);
}

static void tex_aux_display_parameter_node(halfword n)
{
    tex_print_format(", id %i, style %i", parameter_name(n), parameter_style(n));
}

static void tex_aux_display_choice_noad(halfword n, int threshold, int max)
{
    switch (node_subtype(n)) {
        case normal_choice_subtype:
            tex_print_node_list(choice_display_mlist(n), "display", threshold, max);
            tex_print_node_list(choice_text_mlist(n), "text", threshold, max);
            tex_print_node_list(choice_script_mlist(n), "script", threshold, max);
            tex_print_node_list(choice_script_script_mlist(n), "scriptscript", threshold, max);
            break;
        case discretionary_choice_subtype:
            tex_print_format(", class %i", choice_class(n));
            tex_print_node_list(choice_pre_break(n), "pre", threshold, max);
            tex_print_node_list(choice_post_break(n), "post", threshold, max);
            tex_print_node_list(choice_no_break(n), "replace", threshold, max);
            break;
    }
}

static void tex_aux_display_simple_noad(halfword n, int threshold, int max)
{
    if (noad_source(n)) {
        tex_print_format(", source %i", noad_source(n));
    }
    tex_aux_display_common_noad(n, threshold, max);
}

static void tex_aux_display_radical_noad(halfword n, int threshold, int max) /* todo: more fields */
{
    if (noad_width(n)) {
        tex_print_format(", width %p", noad_width(n));
    }
    if (radical_height(n)) {
        tex_print_format(", height %p", radical_height(n));
    }
    if (radical_depth(n)) {
        tex_print_format(", depth %p", radical_depth(n));
    }
    if (radical_size(n)) {
        tex_print_format(", size %i", radical_size(n));
    }
    if (noad_source(n) != 0) {
        tex_print_format(", source %i", noad_source(n));
    }
    if (noad_options(n)) {
        tex_print_format(", options %x", noad_options(n));
    }
    if (radical_left_delimiter(n)) {
        tex_print_str(", left");
        tex_aux_print_delimiter(radical_left_delimiter(n));
    }
    if (radical_right_delimiter(n)) {
        tex_print_str(", right");
        tex_aux_print_delimiter(radical_right_delimiter(n));
    }
    if (radical_degree(n)) {
        tex_print_node_list(radical_degree(n), "degree", threshold, max);
    }
    tex_aux_display_common_noad(n, threshold, max);
}

static void tex_aux_display_accent_noad(halfword n, int threshold, int max) /* todo: more fields */
{
    halfword top_char = accent_top_character(n);
    halfword bottom_char = accent_bottom_character(n);
    halfword fraction = accent_fraction(n);
    if (fraction) {
        tex_print_str(", fraction ");
        tex_print_int(fraction);
    }
    switch (node_subtype(n)) {
        case bothflexible_accent_subtype:
            if (top_char) {
                tex_print_str(", top ");
                tex_aux_print_fam_and_char(top_char);
            }
            if (bottom_char) {
                tex_print_str(", bottom ");
                tex_aux_print_fam_and_char(bottom_char);
            }
            if (! (top_char || bottom_char)) {
                tex_print_str(", overlay ");
                tex_aux_print_fam_and_char(accent_middle_character(n));
            }
            break;
        case fixedtop_accent_subtype:
            if (top_char) {
                tex_print_str(", fixed top ");
                tex_aux_print_fam_and_char(top_char);
            }
            if (bottom_char) {
                tex_print_str(", bottom ");
                tex_aux_print_fam_and_char(bottom_char);
            }
            break;
        case fixedbottom_accent_subtype:
            if (top_char) {
                tex_print_str(", top ");
                tex_aux_print_fam_and_char(top_char);
            }
            if (bottom_char) {
                tex_print_str(", fixed bottom ");
                tex_aux_print_fam_and_char(bottom_char);
            }
            break;
        case fixedboth_accent_subtype:
            if (top_char) {
                tex_print_str(", fixed top ");
                tex_aux_print_fam_and_char(top_char);
            }
            if (bottom_char) {
                tex_print_str(", fixed bottom ");
                tex_aux_print_fam_and_char(bottom_char);
            }
            break;
    }
    tex_aux_display_common_noad(n, threshold, max);
}

static void tex_aux_display_fence_noad(halfword n, int threshold, int max) /* todo: more fields */
{
    if (noad_height(n)) {
        tex_print_format(", height %p", noad_height(n));
    }
    if (noad_depth(n)) {
        tex_print_format(", depth %p", noad_depth(n));
    }
    if (fence_top_overshoot(n)) {
        tex_print_format(", top %p", fence_top_overshoot(n));
    }
    if (fence_bottom_overshoot(n)) {
        tex_print_format(", top %p", fence_bottom_overshoot(n));
    }
    if (get_noad_main_class(n) != unset_noad_class) {
        tex_print_format(", class %i", get_noad_main_class(n));
    }
    if (get_noad_left_class(n) != unset_noad_class) {
        tex_print_format(", leftclass %i", get_noad_left_class(n));
    }
    if (get_noad_right_class(n) != unset_noad_class) {
        tex_print_format(", rightclass %i", get_noad_right_class(n));
    }
    if (noad_source(n) != 0) {
        tex_print_format(", source %i", noad_source(n));
    }
    if (noad_options(n)) {
        tex_print_format(", options %x", noad_options(n));
    }
    tex_aux_print_delimiter(fence_delimiter(n));
    tex_print_node_list(fence_delimiter_top(n), "top", threshold, max);
    tex_print_node_list(fence_delimiter_bottom(n), "bottom", threshold, max);
}

static void tex_aux_display_fraction_noad(halfword n, int threshold, int max) /* todo: more fields */
{
    halfword leftdelimiter = tex_aux_valid_delimiter(fraction_left_delimiter(n));
    halfword rightdelimiter = tex_aux_valid_delimiter(fraction_right_delimiter(n));
    tex_print_str(", thickness ");
    if (fraction_rule_thickness(n) == preset_rule_thickness) {
        tex_print_str("default");
    } else {
        tex_print_dimension(fraction_rule_thickness(n), pt_unit);
    }
    if (leftdelimiter) {
        tex_print_str(", leftdelimiter ");
        tex_aux_print_delimiter(leftdelimiter);
    }
    if (rightdelimiter) {
        tex_print_str(", rightdelimiter ");
        tex_aux_print_delimiter(rightdelimiter);
    }
    if (noad_source(n) != 0) {
        tex_print_str(", source ");
        tex_print_int(noad_source(n));
    }
    if (noad_options(n)) {
        tex_print_str(", options ");
        tex_print_qhex(noad_options(n));
    }
    tex_print_node_list(fraction_numerator(n), "numerator", threshold, max);
    tex_print_node_list(fraction_denominator(n), "denominator", threshold, max);
}

/*tex

    The routines that \TEX\ uses to create mlists are similar to those we have just seen for the
    generation of hlists and vlists. But it is necessary to make \quote {noads} as well as nodes,
    so the reader should review the discussion of math mode data structures before trying to make
    sense out of the following program.

    Here is a little routine that needs to be done whenever a subformula is about to be processed.
    The parameter is a code like |math_group|.

*/

static void tex_aux_new_save_level_math(quarterword group)
{
    halfword direction = math_direction_par;
    saved_math_initialize();
    saved_math_direction = lmt_dir_state.text_dir_ptr;
    lmt_save_state.save_stack_data.ptr += saved_math_n_of_records;
    lmt_dir_state.text_dir_ptr = tex_new_dir(normal_dir_subtype, direction);
    tex_new_save_level(group);
    update_tex_par_direction(direction);
    update_tex_text_direction(direction);
}

static void tex_aux_push_math(quarterword group, int style, int outerstyle)
{
    halfword main_style = cur_list.math_main_style;
    halfword parent_style = cur_list.math_style;
    if (math_direction_par != text_direction_par) {
        cur_list.math_dir = 1;
    }
    cur_list.math_begin = math_begin_class_par;
    cur_list.math_end = math_end_class_par;
    if (outerstyle >= 0) {
        cur_list.math_main_style = outerstyle;
        main_style = outerstyle;
        cur_list.math_parent_style = outerstyle;
        parent_style = outerstyle;
    }
    tex_push_nest();
    cur_list.mode = inline_mmode;
    cur_list.incomplete_noad = null;
    cur_list.math_style = style;
    cur_list.math_main_style = main_style;
    cur_list.math_parent_style = parent_style;
    cur_list.math_scale = internal_math_scale_par;
    tex_aux_new_save_level_math(group);
    update_tex_math_left_class(unset_noad_class);
    update_tex_math_right_class(unset_noad_class);
}

static void tex_aux_enter_inline_math(int style, int where)
{
    (void) where;
    tex_aux_push_math(math_inline_group, style, style);
    update_tex_family(0, unused_math_family);
    if (every_math_par) {
        tex_begin_token_list(every_math_par, every_math_text);
    }
}

static void tex_aux_enter_display_math(halfword cmd, int where);

/*tex

    We get into math mode from horizontal mode when a |$| (i.e., a |math_shift| character) is
    scanned. We must check to see whether this |$| is immediately followed by another, in case
    display math mode is called for.

*/

void tex_run_math_initialize(void)
{
    switch(cur_cmd) {
        case math_shift_cmd:
            /*tex |get_x_token| would fail on |\ifmmode|! */
            lmt_nest_state.math_mode = 1;
            tex_get_token();
            lmt_nest_state.math_mode = 0;
            if (cur_cmd == math_shift_cmd && cur_list.mode > nomode) {
                tex_aux_enter_display_math(math_shift_cmd, 1);
            } else {
                tex_back_input(cur_tok);
                tex_aux_enter_inline_math(text_style, 1);
            }
            break;
        case math_shift_cs_cmd:
            if (cur_chr == begin_math_mode_code) {
                tex_aux_enter_inline_math(tex_scan_math_style_identifier(0, 0), 2);
            } else if (cur_chr == begin_display_math_code && cur_list.mode > nomode) {
                tex_aux_enter_display_math(begin_display_math_code, 2);
            } else if (cur_chr == begin_inline_math_code) {
                tex_aux_enter_inline_math(text_style, 3);
            } else {
                tex_you_cant_error("math shift 1");
            }
            break;
        default:
            tex_you_cant_error("math shift 2");
            break;
    }
}

/*tex

    We get into ordinary math mode from display math mode when |\eqno| or |\leqno| appears. In such
    cases |cur_chr| will be 0 or~1, respectively; the value of |cur_chr| is placed onto |save_stack|
    for safe keeping. When \TEX\ is in display math mode, |cur_group = math_shift_group|, so it is
    not necessary for the |start_eq_no| procedure to test for this condition.

*/

void tex_run_math_equation_number(void) {
    if (cur_group == math_display_group) {
        saved_equation_number_initialize();
        saved_equation_number_location = cur_chr;
        lmt_save_state.save_stack_data.ptr += saved_equation_number_n_of_records;
        tex_aux_enter_inline_math(text_style, 4);
    } else {
        tex_off_save();
    }
}

/*tex

    Subformulas of math formulas cause a new level of math mode to be entered, on the semantic nest
    as well as the save stack. These subformulas arise in several ways: (1)~A left brace by itself
    indicates the beginning of a subformula that will be put into a box, thereby freezing its glue
    and preventing line breaks. (2)~A subscript or superscript is treated as a subformula if it is
    not a single character; the same applies to the nucleus of things like |\underline|. (3)~The
    |\left| primitive initiates a subformula that will be terminated by a matching |\right|. The
    group codes placed on |save_stack| in these three cases are |math_group|, |math_group|, and
    |math_left_group|, respectively.

    Here is the code that handles case (1); the other cases are not quite as trivial, so we shall
    consider them later.

*/

void tex_run_math_left_brace(void)
{
    if (math_grouping_mode_par) {
        /*tex This is an experiment. Some tracing has to be adapted probably. */
        tex_new_save_level(math_simple_group);
        update_tex_internal_math_style(cur_mode == mmode ? cur_list.math_style : -1);
        update_tex_internal_math_scale(cur_mode == mmode ? cur_list.math_scale : -1);
    } else {
        halfword q = tex_new_node(math_char_node, 0);
        halfword n = tex_new_node(simple_noad, ordinary_noad_subtype);
        tex_tail_append(n);
        noad_nucleus(n) = q;
        tex_back_input(cur_tok);
        tex_aux_scan_math(q, cur_list.math_style, 0, 0, 0, 0, unset_noad_class, unset_noad_class);
    }
}

/*tex

    If the inline directions of |\pardir| and |\mathdir| are opposite, then this function will
    return true. Discovering that fact is somewhat odd because it needs traversal of the
    |save_stack|. The occurance of displayed equations is weird enough that this is probably still
    better than having yet another field in the |input_stack| structures.

    None of this makes much sense if the inline direction of either one of |\pardir| or |\mathdir|
    is vertical, but in that case the current math machinery is ill suited anyway so I do not
    bother to test that. We now just return the direction.

*/

static int tex_aux_pre_math_par_direction(void)
{
    return tex_located_save_value(internal_integer_location(par_direction_code));
}

/*tex

    When we enter display math mode, we need to call |line_break| to process the partial paragraph
    that has just been interrupted by the display. Then we can set the proper values of
    |display_width| and |display_indent| and |pre_display_size|.

    In 2025 \LUATEX\ there is a patch for display lists that have only dir changes while before we
    only checked for par nodes;. This because text direction nodes get injected automatically. This
    relates to display math being abused for determining the length of the preceding line as well
    as equation numbers. In \LUAMETATEX\ we don't have the side effect of (empty) equation numbers.

    We also have indentation to deal with (two models) and then need to clean up a list, and not
    just some last node (I'm not sure if \LUATEX\ does that right now but will check it). So, in
    \LUAMETATEX\ we control this by some parameter.

    Keep in mind that we can't test interferences because in \CONTEXT\ we don't use display math
    this way. There can also me more kind of nodes. We never use these features. For testing
    purposes we have a parameter that controls this.

*/

static void tex_aux_enter_display_math(halfword cmd, int where)
{
    (void) where;
    if (math_display_mode_par > 0) {
        tex_aux_push_math(math_inline_group, display_style, display_style);
        cur_list.math_mode = cmd;
        cur_list.mode = inline_mmode; /* new */
        update_tex_family(0, unused_math_family);
        switch (math_display_mode_par) {
            case 1: 
                if (every_display_par) {
                    tex_begin_token_list(every_display_par, every_display_text);
                }
                break;
            case 2: 
                if (every_math_par) {
                    tex_begin_token_list(every_display_par, every_math_text);
                }
                break;
        }
    } else {
        /*tex new or partial |pre_display_size| */
        scaled size;
        /*tex new |display_width| */
        scaled width;
        /*tex new |display_indent| */
        scaled indent;
        /*tex
            Deal with |\noindent$$| or |$${ }$$| or the 2nd of |$${ }$$| |$${ }$$|.
        */
        if (cur_list.head == cur_list.tail) {
            /* temp nodes */
            tex_pop_nest();
            size = - max_dimension;
        } else if (empty_paragraph_mode_par && ! node_next(cur_list.tail) && tex_is_effectively_empty(node_next(cur_list.head), empty_paragraph_mode_par)) {
            /*tex
                |resume_after_display| inserts a |par_node|, but if there is another display
                immediately following, we have to get rid of that node.
            */
            tex_flush_node_list(node_next(cur_list.head));
            node_next(cur_list.head) = null;
            cur_list.tail = cur_list.head;
            tex_pop_nest(); /* pops only head */
            size = - max_dimension;
        } else {
            tex_line_break(math_display_group, math_par_context, 1);
         // size = tex_actual_box_width(lmt_linebreak_state.just_box, tex_x_over_n(tex_get_font_em_width(cur_font_par), scaling_factor) * math_pre_display_gap_factor_par);
            size = tex_actual_box_width(lmt_linebreak_state.just_box, scaledround((tex_get_font_em_width(cur_font_par) / scaling_factor_double) * math_pre_display_gap_factor_par));
        }
        /*tex
            Now we are in vertical mode, working on the list that will contain the display. A displayed
            equation is considered to be three lines long, so we calculate the length and offset of line
            number |prev_graf + 2|.
        */
        if (par_shape_par) {
            /*tex scope of paragraph shape specification */
            int n = tex_get_specification_count(par_shape_par);
            if (n > 0) {
                if (cur_list.prev_graf + 2 < n) {
                    n = cur_list.prev_graf + 2;
                }
                indent = tex_get_specification_indent(par_shape_par, n) ;
                width = tex_get_specification_width(par_shape_par, n);
                indent = swap_parshape_indent(pre_display_direction_par, indent, width);
            } else {
                width = hsize_par;
                indent = 0;
            }
        } else if ((hang_indent_par != 0) && (((hang_after_par >= 0) && (cur_list.prev_graf + 2 > hang_after_par)) || (cur_list.prev_graf + 1 < -hang_after_par))) {
            halfword hangindent = swap_hang_indent(pre_display_direction_par, hang_indent_par);
            width = hsize_par - abs(hangindent);
            indent = hangindent > 0 ? hangindent : 0;
        } else {
            width = hsize_par;
            indent = 0;
        }
        tex_aux_push_math(math_display_group, display_style, display_style);
        cur_list.mode = mmode;
        update_tex_family(0, unused_math_family);
        update_tex_pre_display_size(size);
        update_tex_display_width(width);
        update_tex_display_indent(indent);
        update_tex_pre_display_direction(tex_aux_pre_math_par_direction());
        if (every_display_par) {
            tex_begin_token_list(every_display_par, every_display_text);
        }
        if (lmt_nest_state.nest_data.ptr == 1) {
            tex_build_page(before_display_page_context, 0);
        }
    }
}

/*tex

    The next routine parses all variations of a delimiter code. The |extcode| tells what syntax form
    to use (\TEX\ or \LUATEX) , the |doclass| tells whether or not read a math class also (for
    |\delimiter| c.s.). The class is passed on for conversion to |\mathchar|.

*/

static delcodeval tex_aux_scan_extdef_del_code(int extcode, int doclass)
{
    delcodeval d = tex_no_del_code();
    switch (extcode) {
        case tex_mathcode:
            /*tex This is the easiest: |\delcode|,*/
            {
                halfword v = tex_scan_integer(0, NULL, NULL);
                /*tex |MFCCFCC| or |FCCFCC| */
                if (doclass) {
                    d.small.class_value = (short) (v / 0x1000000);
                    v = (v & 0xFFFFFF);
                }
                if (v > 0xFFFFFF) {
                    tex_handle_error(
                        normal_error_type,
                        "Invalid delimiter code",
                        "I'm going to use 0 instead of that illegal code value."
                    );
                    v = 0;
                }
                d.small.family_value = (short) (v / 0x100000);
                d.small.character_value = (v % 0x100000) / 0x1000;
                d.large.family_value = (short) ((v & 0xFFF) / 0x100);
                d.large.character_value = (v % 0x100);
                /* */
                d.small.character_value = math_character_part(d.small.character_value);
                d.large.character_value = math_character_part(d.large.character_value);
            }
            break;
        case umath_mathcode:
            /*tex |\Udelcode|: |<0-7><0-0xFF><0-0x10FFFF>| or |<0-0xFF><0-0x10FFFF>| */
            {
                if (doclass) {
                    d.small.class_value = (short) tex_scan_math_class_number(0);
                }
                d.small.family_value = (short) tex_scan_math_family_number();
                d.small.character_value = tex_scan_math_char_number();
                if (d.small.family_value < 0 || d.small.family_value > max_math_family_index) {
                    tex_handle_error(
                        normal_error_type,
                        "Invalid delimiter family",
                        "I'm going to use family 0 instead."
                    );
                    d.small.family_value = 0;
                    d.small.character_value = 0;
                }
            }
            break;
        default:
            /*tex Something's gone wrong! */
            tex_confusion("unknown extcode, case 1");
            break;
    }
    d.large.class_value = d.small.class_value;
    return d;
}

void tex_scan_extdef_del_code(int level, int extcode)
{
    delcodeval d;
    int chr = tex_scan_char_number(0);
    tex_scan_optional_equals();
    d = tex_aux_scan_extdef_del_code(extcode, 0);
    tex_set_del_code(chr, d, (quarterword) level);
}

mathdictval tex_scan_mathdict(void)
{
    mathdictval d = tex_no_dict_code(); /* use this one directly */
    d.properties = (unsigned short) tex_scan_math_properties_number();
    d.group = (unsigned short) tex_scan_math_group_number();
    d.index = (unsigned int) tex_scan_math_index_number();
    return d;
}

/*tex

    The \LUATEX\ number def variant is gone so we no longer accent |"<6bits><6bits><20bits>| where
    the largest numeric value is $2^32-1$, and the top of bit 21 can't be used as it contains
    invalid USV's and |scan_integer| won't accept families 128-255 because these use bit 32 (so
    plenty of reasons to drop it).

*/

mathcodeval tex_scan_mathchar(int extcode)
{
    mathcodeval d = tex_no_math_code(); /* use this one directly */
    switch (extcode) {
        case tex_mathcode:
            /*tex |"<4bits><4bits><8bits>| */
            {
                halfword v = tex_scan_integer(0, NULL, NULL);
                if (v >= 0) {
                    if (v > 0xFFFF) {
                        v = 0xFFFF;
                    }
                    d.class_value = (short) math_old_class_part(v);
                    d.family_value = (short) math_old_family_part(v);
                    d.character_value = math_old_character_part(v);
                }
            }
            break;
        case umath_mathcode:
            /*tex |"<6bits>"<6bits>"<20bits>| */
            {
                d.class_value = (short) tex_scan_math_class_number(0);
                d.family_value = (short) tex_scan_math_family_number();
                d.character_value = tex_scan_math_char_number();
            }
            break;
        default:
            tex_confusion("unknown extcode, case 2");
            break;
    }
    if (d.class_value < 0 || d.character_value > max_math_character_code || d.class_value > max_math_class_code || d.family_value > max_math_family_index) {
        tex_handle_error(
            normal_error_type,
            "Invalid math code",
            "I'm going to use 0 instead of that illegal code value."
        );
        d.class_value = 0;
        d.family_value = 0;
        d.character_value = 0;
    }
    return d;
}

halfword tex_new_math_spec(mathcodeval m, quarterword code)
{
    halfword s = tex_new_node(math_spec_node, code);
    math_spec_class(s) = (singleword) m.class_value;
    math_spec_family(s) = (singleword) m.family_value;
    math_spec_character(s) = m.character_value;
    return s;
}

halfword tex_new_math_dict_spec(mathdictval d, mathcodeval m, quarterword code)
{
    halfword s = tex_new_node(math_spec_node, code);
    math_spec_class(s) = (singleword) m.class_value;
    math_spec_family(s) = (singleword) m.family_value;
    math_spec_character(s) = m.character_value;
    math_spec_properties(s) = (quarterword) d.properties;
    math_spec_group(s) = (quarterword) d.group;
    math_spec_index(s) = d.index;
    return s;
}

mathcodeval tex_get_math_spec(halfword s)
{
    mathcodeval m = tex_no_math_code();
    if (s) {
        m.class_value = math_spec_class(s);
        m.family_value = math_spec_family(s);
        m.character_value = math_spec_character(s);
    }
    return m;
}

mathdictval tex_get_math_dict(halfword s)
{
    mathdictval d = tex_no_dict_code();
    if (s) {
        d.properties = math_spec_properties(s);
        d.group = math_spec_group(s);
        d.index = math_spec_index(s);
    }
    return d;
}

halfword tex_scan_math_spec(int optional_equal)
{
    mathcodeval m;
    if (optional_equal) {
        tex_scan_optional_equals();
    }
    m = tex_scan_mathchar(umath_mathcode);
    return tex_new_math_spec(m, mathspec_mathcode);
}

void tex_scan_extdef_math_code(int level, int extcode)
{
    mathcodeval d;
    int chr = tex_scan_char_number(0);
    tex_scan_optional_equals();
    d = tex_scan_mathchar(extcode);
    tex_set_math_code(chr, d, (quarterword) level);
}

/*tex This reads in a delcode when actually a mathcode is needed. */

mathcodeval tex_scan_delimiter_as_mathchar(int extcode)
{
    delcodeval dval = tex_aux_scan_extdef_del_code(extcode, 1);
    return dval.small;
}

/*tex

    Recall that the |nucleus|, |subscr|, and |supscr| fields in a noad are broken down into subfields
    called |type| and either |math_list| or |(math_fam, math_character)|. The job of |scan_math| is
    to figure out what to place in one of these principal fields; it looks at the subformula that
    comes next in the input, and places an encoding of that subformula into a given word of |mem|.

    already prepared: every [component, degree, radical, over, under, accent, prime, subscript,
    superscript]

    toks      : every_subscript_par
    toks_text : every_subscipt_text or every_math_text (for tracing)

*/

/*tex
    For some reason |$\char44$| gives an undefined |$| when we made that character active in math.
*/

static void tex_aux_report_active(int where, const char *what, int code, int character)
{
    tex_begin_diagnostic();
    tex_print_format("[active: location %i, %s, code %i, char %i]",where, what, code, character);
    tex_end_diagnostic();
}

static void tex_aux_append_math_char(mathcodeval mval, mathdictval dval, int automatic);

int tex_check_active_math_char(int character)
{
    halfword code = tex_get_am_code(character);
    if (code) {
        switch (code) {
            case alignment_tab_cmd:
            case superscript_cmd:
            case subscript_cmd:
            case letter_cmd:
            case other_char_cmd:
            case active_char_cmd:
                cur_cmd = code;
                cur_chr = character;
                cur_tok = token_val(cur_cmd, cur_chr);
                if (tracing_commands_par >= 4) {
                    switch (code) {
                        case alignment_tab_cmd:
                        case superscript_cmd:
                        case subscript_cmd:
                            tex_aux_report_active(4, "control", code, character);
                            break;
                        case letter_cmd:
                        case other_char_cmd:
                            tex_aux_report_active(4, "inject", code, character);
                            break;
                        case active_char_cmd:
                            tex_aux_report_active(4, "active", code, character);
                            break;
                    }
                }
                return 1;
            default:
                if (tracing_commands_par >= 4) {
                    tex_aux_report_active(4, "ignore", code, character);
                }
                return 1;
        }
    } else {
        return 0;
    }
}

int tex_pass_active_math_char(int character)
{
    halfword code = tex_get_am_code(character);
    if (code) {
        return 1;
    } else {
        return 0;
    }
}

static int tex_aux_scan_active_math_char(mathcodeval *mval, int where)
{
    halfword character = mval->character_value;
    halfword code = tex_get_am_code(character);
    if (code) {
        switch (code) {
            case alignment_tab_cmd:
            case superscript_cmd:
            case subscript_cmd:
                cur_cmd = code;
                cur_chr = character;
                cur_tok = token_val(cur_cmd, cur_chr);
                tex_back_input(cur_tok);
                if (tracing_commands_par >= 4) {
                    tex_aux_report_active(where, "control", code, character);
                }
                return 1;
            case letter_cmd:
            case other_char_cmd:
                cur_cmd = code;
                cur_chr = character;
                cur_tok = token_val(cur_cmd, cur_chr);
                if (tracing_commands_par >= 4) {
                    tex_aux_report_active(where, "inject", code, character);
                }
                return 0;
            case active_char_cmd:
                /*tex
                    We reset the code so that we don't get a loop, which means that the macro that
                    gets invoked has to set the amcode again if needed.
                */
                tex_set_am_code(character, other_char_cmd, cur_level);
                goto COMMON;
            //  cur_cs = tex_active_to_cs(character, 1);
            //  cur_cmd = eq_type(cur_cs);
            //  cur_chr = eq_value(cur_cs);
            //  tex_x_token();
            //  tex_back_input(cur_tok);
            //  if (tracing_commands_par >= 4) {
            //      tex_aux_report_active(where, "active", code, character);
            //  }
            //  return 1;
            default:
                if (tracing_commands_par >= 4) {
                    tex_aux_report_active(where, "ignore", code, character);
                }
                return 1;
        }
    } else if (mval->class_value == active_math_class_value) {
        /*tex We might eventually drop this feature in favor of the amcode. */
      COMMON:
        cur_cs = tex_active_to_cs(character, 1);
        cur_cmd = eq_type(cur_cs);
        cur_chr = eq_value(cur_cs);
        tex_x_token();
        tex_back_input(cur_tok);
        if (tracing_commands_par >= 4) {
            tex_aux_report_active(where, "active", code, character);
        }
        return 1;
    } else {
     // if (tracing_commands_par >= 4) {
     //     tex_aux_report_active(where, "keep", code, mval->character_value);
     // }
        return 0;
    }
}

static int tex_aux_scan_math(halfword target, halfword style, int usetextfont, halfword toks, halfword toks_text, int nocomponent, halfword cls, halfword all)
{
    mathcodeval mval = tex_no_math_code();
    mathdictval dval = tex_no_dict_code();
    lmt_math_state.last_atom = cls;
  RESTART:
    do {
        tex_get_x_token();
    } while (cur_cmd == spacer_cmd || cur_cmd == relax_cmd);
//  RESWITCH:
    switch (cur_cmd) {
        case char_number_cmd:
            /* The |\glyph| variant is accepted but no keywords here. */
            cur_chr = tex_scan_char_number(0);
            // fall through
        case letter_cmd:
        case other_char_cmd:
        case char_given_cmd:
            mval = tex_get_math_code(cur_chr);
            if (tex_aux_scan_active_math_char(&mval, 1)) {
                goto RESTART; /* rescan pushed back token */
            } else {
                dval = tex_fake_math_dict(mval.character_value);
                break;
            }
    //  case char_number_cmd:
    //      /* The |\glyph| variant is accepted but no keywords here. */
    //      cur_chr = tex_scan_char_number();
    //      cur_cmd = char_given_cmd;
    //      goto RESWITCH;
        case math_char_number_cmd:
            switch (cur_chr) {
                case math_char_number_code:
                    mval = tex_scan_mathchar(tex_mathcode);
                 // dval = tex_fake_math_dict(mval.character_value);
                    break;
                case math_xchar_number_code:
                    mval = tex_scan_mathchar(umath_mathcode);
                    dval = tex_fake_math_dict(mval.character_value);
                    break;
                case math_char_ignore_code:
                    break;
                default:
                    tex_confusion("scan math char, case 1");
                    break;
            }
            break;
        case mathspec_cmd:
            mval = tex_get_math_spec(cur_chr);
            dval = tex_get_math_dict(cur_chr);
            break;
        case delimiter_number_cmd:
            switch (cur_chr) {
                case math_delimiter_code:
                    mval = tex_scan_delimiter_as_mathchar(tex_mathcode);
                    break;
                case math_udelimiter_code:
                    mval = tex_scan_delimiter_as_mathchar(umath_mathcode);
                    break;
                default:
                    tex_confusion("scan math char, case 2");
                    break;
            }
            break;
		case math_component_cmd:
			if (nocomponent) {
                goto DEFAULT;
            } else {
                saved_math_group_initialize();
                saved_math_group_all_class = unset_noad_class;
                saved_math_group_pointer = target;
			    lmt_save_state.save_stack_data.ptr += saved_math_group_n_of_records;
			    tex_aux_push_math(math_component_group, style, -1);
                if (usetextfont) {
                    tex_set_math_text_font(style, usetextfont);
                }
    		    tex_aux_math_math_component(cur_list.tail, 0);
                tex_finish_math_group();
    			return 1;
		    }
        case left_brace_cmd:
            goto SCAN_SUBFORMULA;
        default:
            /*tex
                The pointer |p| is placed on |save_stack| while a complex subformula is being
                scanned.
            */
          DEFAULT:
            tex_back_input(cur_tok);
            tex_scan_left_brace();
          SCAN_SUBFORMULA:
            saved_math_group_initialize();
            saved_math_group_all_class = all;
            saved_math_group_pointer = target;
            lmt_save_state.save_stack_data.ptr += saved_math_group_n_of_records;
            tex_aux_push_math(math_group, style, -1);
            toks = every_math_atom_par;
            toks_text = every_math_atom_text;
            if (toks) {
                tex_begin_token_list(toks, (quarterword) toks_text);
            }
            if (usetextfont) {
                tex_set_math_text_font(style, usetextfont);
            }
            return 1;
    }
    node_type(target) = math_char_node; /* The sizes can best match! */
    if (glyph_options_par & glyph_option_no_italic_correction) {
        math_kernel_node_set_option(target, math_kernel_no_italic_correction);
    }
    if (glyph_options_par & glyph_option_no_left_kern) {
        math_kernel_node_set_option(target, math_kernel_no_left_pair_kern);
    }
    if (glyph_options_par & glyph_option_no_right_kern) {
        math_kernel_node_set_option(target, math_kernel_no_right_pair_kern);
    }
    tex_aux_set_math_char(target, &mval, &dval);
    return 0;
}

/*tex

    The |append_math_char| procedure creates a new noad appropriate to a given math code, and
    appends it to the current mlist. However, if the math code is sufficiently large, the |cur_chr|
    is treated as an active character and nothing is appended.

*/

static void tex_aux_append_math_accent(mathcodeval mval, mathdictval dval)
{
    halfword accent = tex_new_node(accent_noad, bothflexible_accent_subtype);
    quarterword subtype = ordinary_noad_subtype;
    tex_tail_append(accent);
    if (! (mval.character_value == 0 && mval.family_value == 0)) {
        halfword q = tex_new_node(math_char_node, 0);
        subtype = tex_aux_set_math_char(q, &mval, &dval);
        accent_top_character(accent) = q;
    }
    {
        halfword q = tex_new_node(math_char_node, subtype);
        noad_nucleus(accent) = q;
        tex_aux_scan_math(q, tex_math_style_variant(cur_list.math_style, math_parameter_accent_variant), 0, 0, 0, 0, unset_noad_class, unset_noad_class);
    }
}

/*tex
    Fences are actually constructs and middle sort of interferes here: we keep a sort of flat fence
    sequence so middle ends a group and opens a new one.

*/

static void tex_aux_append_math_fence(halfword fence, quarterword mathclass)
{
    switch (mathclass) {
        case open_noad_subtype:
            {
                tex_aux_push_math(math_fence_group, cur_list.math_style, -1);
                node_subtype(fence) = left_fence_side;
                node_next(cur_list.head) = fence;
                cur_list.tail = fence;
                cur_list.delimiter = fence;
            }
            break;
        case close_noad_subtype:
            {
                halfword q = tex_aux_finish_math_list(fence);
                halfword n = tex_new_node(simple_noad, fenced_noad_subtype);
                halfword l = tex_new_node(sub_mlist_node, 0);
                tex_aux_unsave_math();
                tex_tail_append(n);
                node_subtype(fence) = right_fence_side;
                noad_nucleus(n) = l;
                noad_options(n) |= noad_option_unpack_list;
                kernel_math_list(noad_nucleus(n)) = q;
            }
            break;
        case middle_noad_subtype:
            {
                halfword q = tex_aux_finish_math_list(fence);
                tex_aux_unsave_math();
                tex_aux_push_math(math_fence_group, cur_list.math_style, -1);
                node_subtype(fence) = middle_fence_side;
                node_next(cur_list.head) = q;
                cur_list.tail = fence;
                cur_list.delimiter = fence;
            }
            break;
    }
}

static void tex_aux_append_math_fence_val(mathcodeval mval, mathdictval dval, quarterword mathclass)
{
    halfword fence = tex_new_node(fence_noad, middle_fence_side);
    halfword delimiter = tex_new_node(delimiter_node, mval.class_value);
    (void) dval; /* maybe todo */
    fence_delimiter(fence) = delimiter;
    delimiter_small_family(delimiter) = mval.family_value;
    delimiter_small_character(delimiter) = mval.character_value;
    delimiter_large_family(delimiter) = mval.family_value;
    delimiter_large_character(delimiter) = mval.character_value;
    tex_set_noad_classes(fence, mval.class_value);
    /* todo : share the next three with the regular fences */
    noad_options(fence) |= noad_option_no_check;
    if (mathclass == middle_noad_subtype && cur_group != math_fence_group) {
        tex_aux_append_math_fence_val(tex_no_math_code(), tex_no_dict_code(), open_noad_subtype);
    }
    tex_aux_append_math_fence(fence, mathclass);
}

static void tex_trace_continuation_atom(const char *s)
{
    if (tracing_math_par >= 2) {
        tex_begin_diagnostic();
        tex_print_format("[math: continuation atom %s]", s);
        tex_end_diagnostic();
    }
}

halfword tex_new_math_continuation_atom(halfword node, halfword attr)
{
    if (! node) {
        /*tex We create a continuation atom. */
        halfword list = tex_new_node(sub_mlist_node, 0);
        node = tex_new_node(simple_noad, ordinary_noad_subtype);
        noad_nucleus(node) = list;
        tex_trace_continuation_atom("added");
    } else if (! tex_math_scripts_allowed(node)) {
        tex_trace_continuation_atom("not allowed");
        return node;
    } else {
        tex_trace_continuation_atom("updated");
    }
    if (math_double_script_mode_par >= 0) {
        /* todo: a flag that we keep classes */
        int options = (math_double_script_mode_par >> 24) & 0xFF;
        node_subtype(node) = (math_double_script_mode_par >> 16) & 0xFF;
        noad_class_left(node) = (math_double_script_mode_par >> 8) & 0xFF;
        noad_class_right(node) = (math_double_script_mode_par >> 0) & 0xFF;
        if (options & inherit_class_double_atom_option) {
            noad_options(node) |= noad_option_inherit_class;
        }
        if (options & discard_shape_kern_double_atom_option) {
            noad_options(node) |= noad_option_discard_shape_kern;
        }
        if (options & realign_scripts_double_atom_option) {
            noad_options(node) |= noad_option_realign_scripts;
        }
        if (options & reorder_double_pre_script_atom_option) {
            noad_options(node) |= noad_option_reorder_pre_scripts;
        }
    }
    noad_options(node) |= noad_option_continuation;
    if (attr) {
        tex_attach_attribute_list_copy(node, attr);
    }
    return node;
}

static halfword tex_math_double_atom(int followup)
{
    halfword tail = tex_new_math_continuation_atom(null, cur_list.tail); /*tex We make a new one. */
    tex_math_set_scripts_options(tail);
    if (followup && ! has_noad_option_continuation(cur_list.tail)) {
        noad_options(cur_list.tail) |= noad_option_continuation_head;
        noad_options(cur_list.tail) |= noad_option_continuation_kernel;
     // printf("SET DA %i\n",cur_list.tail);
    }
    tex_tail_append(tail);
    return tail;
}

static void tex_aux_append_math_char(mathcodeval mval, mathdictval dval, int automatic)
{
    if (tex_aux_scan_active_math_char(&mval, 2)) {
        return; /* rescan pushed back token */
    } else {
        if (automatic && tex_math_has_class_option(mval.class_value, auto_inject_class_option)) {
            switch (mval.class_value) {
                case accent_noad_subtype:
                    tex_aux_append_math_accent(mval, dval);
                    return;
                case open_noad_subtype:
                case close_noad_subtype:
                case middle_noad_subtype:
                    tex_aux_append_math_fence_val(mval, dval, mval.class_value);
                    return;
            }
        }
        {
            halfword q = tex_new_node(math_char_node, 0);
            halfword tail = cur_list.tail;
            if (mval.class_value == prime_noad_subtype && tex_math_scripts_allowed(tail)) {
                tex_aux_set_math_char(q, &mval, &dval);
                if (noad_prime(tail)) {
                    switch (node_type(noad_prime(tail))) {
                        case math_char_node:
                            {
                                halfword n = tex_new_node(sub_mlist_node, 0);
                                halfword a = tex_new_node(simple_noad, ordinary_noad_subtype);
                                halfword b = tex_new_node(simple_noad, ordinary_noad_subtype);
                                noad_nucleus(a) = noad_prime(tail);
                                noad_nucleus(b) = q;
                                tex_couple_nodes(a, b);
                                kernel_math_list(n) = a;
                                noad_prime(tail) = n;
                                break;
                            }
                        case sub_mlist_node:
                            {
                                halfword n = tex_new_node(simple_noad, ordinary_noad_subtype);
                                noad_nucleus(n) = q;
                                tex_couple_nodes(tex_tail_of_node_list(kernel_math_list(noad_prime(tail))),n);
                                break;
                            }
                        default:
                            {
                                /* todo: message that we discard */
                                tex_flush_node_list(noad_prime(tail));
                                noad_prime(tail) = q;
                                break;
                            }
                    }
                } else {
                    noad_prime(tail) = q;
                    if (! noad_script_order(tail)) {
                        noad_script_order(tail) = script_primescript_first;
                    }
                }
            } else {
                halfword p = tex_new_node(simple_noad, ordinary_noad_subtype);
                noad_nucleus(p) = q;
                if (glyph_options_par & glyph_option_no_italic_correction) {
                    math_kernel_node_set_option(q, math_kernel_no_italic_correction);
                }
                node_subtype(p) = tex_aux_set_math_char(q, &mval, &dval);
                tex_math_set_scripts_options(p);
                tex_tail_append(p);
            }
        }
    }
}

/*tex

    The |append_math_char_in_text| procedure creates a new node representing a math char in text
    code, and appends it to the current list. However, if the math code is sufficiently large, the
    |cur_chr| is treated as an active character and nothing is appended.

*/

static void tex_aux_append_math_char_in_text(mathcodeval mval, mathdictval dval)
{
    (void) dval;
    if (tex_aux_scan_active_math_char(&mval, 3)) {
        return; /* rescan pushed back token */
    } else {
        halfword p = tex_new_char_node(glyph_character_subtype, tex_fam_fnt(mval.family_value, text_size), mval.character_value, 1); /* todo: data */
        tex_tail_append(p);
    }
}

void tex_run_math_letter(void)
{
    tex_aux_append_math_char(tex_get_math_code(cur_chr), tex_fake_math_dict(cur_chr), 1);
}

void tex_run_math_char_number(void) {
    /*tex
        Both |\char| and |\glyph| get the same treatment. Scanning can change |cur_chr| so we do
        that first. We no longer check for active here!
    */
    mathcodeval mval = tex_no_math_code();
    mathdictval dval = tex_no_dict_code();
    cur_chr = tex_scan_char_number(0);
    mval.character_value = cur_chr;
    mval.family_value = (short) cur_fam_par;
 // tex_aux_append_math_char(tex_get_math_code(cur_chr), tex_fake_math_dict(cur_chr));
    tex_aux_append_math_char(mval, dval, 1);
}

void tex_run_math_math_spec(void)
{
    tex_aux_append_math_char(tex_get_math_spec(cur_chr), tex_get_math_dict(cur_chr), 1);
}

void tex_run_text_math_spec(void)
{
    tex_aux_append_math_char_in_text(tex_get_math_spec(cur_chr), tex_get_math_dict(cur_chr));
}

int tex_scan_math_cmd_val(mathcodeval *mval, mathdictval *dval)
{
    do {
        tex_get_x_token();
    } while (cur_cmd == spacer_cmd);
    switch (cur_cmd) {
        case mathspec_cmd:
            *mval = tex_get_math_spec(cur_chr);
            break;
        case math_char_number_cmd:
            switch (cur_chr) {
                case math_char_number_code:
                    *mval = tex_scan_mathchar(tex_mathcode);
                    break;
                case math_xchar_number_code:
                    *mval = tex_scan_mathchar(umath_mathcode);
                    *dval = tex_fake_math_dict(mval->character_value);
                    break;
                case math_dictionary_number_code:
                    *dval = tex_scan_mathdict();
                    *mval = tex_scan_mathchar(umath_mathcode);
                    break;
                case math_char_ignore_code:
                    break;
                default:
                    /* no message yet */
                    return 0;
            }
            break;
        case delimiter_number_cmd:
            switch (cur_chr) {
                case math_delimiter_code:
                    *mval = tex_scan_delimiter_as_mathchar(tex_mathcode);
                    break;
                case math_udelimiter_code:
                    *mval = tex_scan_delimiter_as_mathchar(umath_mathcode);
                    break;
                default:
                    /* no message yet */
                    return 0;
            }
            break;
        /*tex
            This is/was an experiment but could work out ambiguous in some cases so when I bring it
            back it will be under more strict control. So, for instance a register would make us
            enter the default branch but a direct number the other case. In the meantime we no longer
            use the direct char approach (for delimiters mostly) so we can comment it.
        */
      // case letter_cmd:
      // case other_char_cmd:
      //     mval->character_value = cur_chr;
      //     break;
        default:
            /*tex
                We could do a fast |tex_scan_something_internal| here but this branch is not that
                critical.
            */
            {
                halfword n = 0;
                tex_back_input(cur_tok);
                n = tex_scan_integer(0, NULL, NULL);
                *mval = tex_mathchar_from_integer(n, umath_mathcode);
            }
            break;
    }
    return 1;
}

int tex_scan_math_code_val(halfword code, mathcodeval *mval, mathdictval *dval)
{
    switch (code) {
        case math_char_number_code:
            *mval = tex_scan_mathchar(tex_mathcode);
            break;
        case math_xchar_number_code:
            *mval = tex_scan_mathchar(umath_mathcode);
            *dval = tex_fake_math_dict(mval->character_value);
            break;
        case math_dictionary_number_code:
            *dval = tex_scan_mathdict();
            *mval = tex_scan_mathchar(umath_mathcode);
            break;
        case math_class_number_code:
            {
                halfword family = cur_fam_par;
                halfword mathclass  = tex_scan_math_class_number(0);
                tex_scan_math_cmd_val(mval, dval);
                mval->class_value = (short) mathclass;
                mval->family_value = (short) family;
            }
            break;
        case math_char_ignore_code:
            break;
        default:
            /* no message yet */
            tex_back_input(cur_tok);
            return 0;
    }
    return 1;
}

void tex_run_text_math_char_number(void) {
    mathcodeval mval = tex_no_math_code();
    mathdictval dval = tex_no_dict_code();
    if (tex_scan_math_code_val(cur_chr, &mval, &dval)) {
        tex_aux_append_math_char_in_text(mval, dval);
    }
}

void tex_run_math_math_char_number(void) {
    mathcodeval mval = tex_no_math_code();
    mathdictval dval = tex_no_dict_code();
    if (tex_scan_math_code_val(cur_chr, &mval, &dval)) {
        tex_aux_append_math_char(mval, dval, 1);
    }
}

void tex_run_math_delimiter_number(void) {
    switch (cur_chr) {
        case math_delimiter_code:
            tex_aux_append_math_char(tex_scan_delimiter_as_mathchar(tex_mathcode), tex_no_dict_code(), 0);
            break;
        case math_udelimiter_code:
            tex_aux_append_math_char(tex_scan_delimiter_as_mathchar(umath_mathcode), tex_no_dict_code(), 0);
            break;
    }
}

/*tex
    In original \TEX\ the subtype overlaps the class. Here we are more strict: a subtype is the
    main class as in original \TEX\ but we also have overloads: main, left and right. The subtype
    drives the rendering, the others the spacing etc.
*/

static void tex_aux_math_math_component(halfword target, int append)
{
    quarterword subtype = unset_noad_class;
    quarterword allclass = unset_noad_class;
    halfword style = cur_list.math_style;
    int usetextfont = math_atom_no_font_option;
    tex_reset_noad_classes(target);
    switch (cur_chr) {
        case math_component_ordinary_code:
            subtype = ordinary_noad_subtype;
            break;
        case math_component_operator_code:
            subtype = operator_noad_subtype;
            break;
        case math_component_binary_code:
            subtype = binary_noad_subtype;
            break;
        case math_component_relation_code:
            subtype = relation_noad_subtype;
            break;
        case math_component_open_code:
            subtype = open_noad_subtype;
            break;
        case math_component_close_code:
            subtype = close_noad_subtype;
            break;
        case math_component_punctuation_code:
            subtype = punctuation_noad_subtype;
            break;
        case math_component_variable_code:
            subtype = variable_noad_subtype;
            break;
        case math_component_inner_code:
            subtype = inner_noad_subtype;
            break;
        case math_component_under_code:
            subtype = under_noad_subtype;
            style = tex_math_style_variant(style, math_parameter_under_line_variant);
            break;
        case math_component_over_code:
            subtype = over_noad_subtype;
            style = tex_math_style_variant(style, math_parameter_over_line_variant);
            break;
        case math_component_fraction_code:
            subtype = fraction_noad_subtype;
            break;
        case math_component_radical_code:
            subtype = radical_noad_subtype;
            break;
        case math_component_middle_code:
            subtype = middle_noad_subtype;
            break;
        case math_component_accent_code:
            subtype = accent_noad_subtype;
            break;
        case math_component_fenced_code:
            subtype = fenced_noad_subtype;
            break;
        case math_component_ghost_code:
            subtype = ghost_noad_subtype;
            break;
        case math_component_atom_code:
            {
                halfword attrlist = null;
                while (1) {
                    switch (tex_scan_character("aclmnoprstuvACLMNOPRSTUV", 0, 1, 0)) {
                        case 'a': case 'A':
                            switch (tex_scan_character("ltLT", 0, 0, 0)) {
                                case 'l': case 'L':
                                    if (tex_scan_mandate_keyword("all", 2)) {
                                        allclass = (quarterword) tex_scan_math_class_number(0);
                                        if (! valid_math_class_code(allclass)) {
                                            allclass = unset_noad_class;
                                        }
                                    }
                                    break;
                                case 't': case 'T':
                                    if (tex_scan_mandate_keyword("attr", 2)) {
                                        attrlist = tex_scan_attribute(attrlist);
                                    }
                                    break;
                                default:
                                    tex_aux_show_keyword_error("attr|all");
                                    goto DONE;
                            }
                            break;
                        case 'c': case 'C':
                            switch (tex_scan_character("aloLAO", 0, 0, 0)) {
                                case 'a': case 'A':
                                    if (tex_scan_mandate_keyword("carryover", 2)) {
                                        noad_options(target) |= noad_option_carry_over_classes;
                                    }
                                    break;
                                case 'l': case 'L':
                                    if (tex_scan_mandate_keyword("class", 2)) {
                                        subtype = (quarterword) tex_scan_math_class_number(0);
                                        if (! valid_math_class_code(subtype)) {
                                            subtype = ordinary_noad_subtype;
                                        }
                                        set_noad_main_class(target, subtype);
                                    }
                                    break;
                                case 'o': case 'O':
                                    if (tex_scan_mandate_keyword("continuation", 2)) {
                                        noad_options(target) |= noad_option_continuation;
                                    }
                                    break;
                                default:
                                    tex_aux_show_keyword_error("class|continuation");
                                    goto DONE;
                            }
                            break;
                        case 'l': case 'L':
                            switch (tex_scan_character("eiEI", 0, 0, 0)) {
                                case 'e': case 'E':
                                    if (tex_scan_mandate_keyword("leftclass", 2)) {
                                        halfword c = tex_scan_math_class_number(0);
                                        if (! valid_math_class_code(c)) {
                                            c = ordinary_noad_subtype;
                                        }
                                        set_noad_left_class(target, c);
                                    }
                                    break;
                                case 'i': case 'I':
                                    if (tex_scan_mandate_keyword("limits", 2)) {
                                        noad_options(target) |= noad_option_limits;
                                    }
                                    break;
                                default:
                                    tex_aux_show_keyword_error("leftclass|limits");
                                    goto DONE;
                            }
                            break;
                        case 'm': case 'M':
                            if (tex_scan_mandate_keyword("mathfont", 1)) {
                                usetextfont = math_atom_math_font_option;
                            }
                            break;
                        case 'n': case 'N':
                            /*tex A bit over the top, three steps but a push back is still worse. We can scan for 'no'. */
                            if (tex_scan_character("oO", 0, 0, 0)) {
                                switch (tex_scan_character("loLO", 0, 0, 0)) {
                                    case 'l': case 'L':
                                        if (tex_scan_mandate_keyword("nolimits", 3)) {
                                            noad_options(target) |= noad_option_no_limits;
                                        }
                                        break;
                                    case 'o': case 'O':
                                        if (tex_scan_mandate_keyword("nooverflow", 3)) {
                                            noad_options(target) |= noad_option_no_overflow;
                                        }
                                        break;
                                    default:
                                        tex_aux_show_keyword_error("nolimits|nooverflow");
                                        goto DONE;
                                }
                            }
                            break;
                        case 'o': case 'O':
                            /* no names, just numbers, we might also do that with other noads */
                            if (tex_scan_mandate_keyword("options", 1)) {
                                noad_options(target) = tex_scan_integer(0, NULL, NULL);
                            }
                            break;
                        case 'p': case 'P':
                            if (tex_scan_mandate_keyword("phantom", 1)) {
                                noad_options(target) |= noad_option_phantom;
                            }
                            break;
                        case 'r': case 'R':
                            if (tex_scan_mandate_keyword("rightclass", 1)) {
                                halfword c = tex_scan_math_class_number(0);
                                if (! valid_math_class_code(c)) {
                                    c = ordinary_noad_subtype;
                                }
                                set_noad_right_class(target, c);
                            }
                            break;
                        case 's': case 'S':
                            switch (tex_scan_character("ioIO", 0, 0, 0)) {
                                case 'i': case 'I':
                                    if (tex_scan_mandate_keyword("single", 2)) {
                                        noad_options(target) |= noad_option_single;
                                    }
                                    break;
                                case 'o': case 'O':
                                    if (tex_scan_mandate_keyword("source", 2)) {
                                        noad_source(target) = tex_scan_integer(0, NULL, NULL);
                                    }
                                    break;
                                default:
                                    tex_aux_show_keyword_error("single|source");
                                    goto DONE;
                            }
                            break;
                       case 't': case 'T':
                            if (tex_scan_mandate_keyword("textfont", 1)) {
                                usetextfont = math_atom_text_font_option;
                            }
                            break;
                        case 'u': case 'U':
                            /*tex A bit over the top, three steps but a push back is still worse. We can scan for 'un'. */
                            if (tex_scan_character("nN", 0, 0, 0)) {
                                switch (tex_scan_character("prPR", 0, 0, 0)) {
                                    case 'p': case 'P':
                                        if (tex_scan_mandate_keyword("unpack", 3)) {
                                            noad_options(target) |= noad_option_unpack_list;
                                        }
                                        break;
                                    case 'r': case 'R':
                                        if (tex_scan_mandate_keyword("unroll", 3)) {
                                            noad_options(target) |= noad_option_unroll_list;
                                        }
                                        break;
                                    default:
                                        tex_aux_show_keyword_error("unpack|unroll");
                                        goto DONE;
                                }
                            }
                            break;
                        case 'v': case 'V':
                            if (tex_scan_mandate_keyword("void", 1)) {
                                noad_options(target) |= noad_option_void;
                            }
                            break;
                        default:
                            goto DONE;
                    }
                }
              DONE:
                if (attrlist) {
                    tex_attach_attribute_list_attribute(target, attrlist);
                }
                if (subtype == unset_noad_class) {
                    if (get_noad_left_class(target) != unset_noad_class && get_noad_right_class(target) != unset_noad_class) {
                        subtype = ordinary_noad_subtype;
                    } else {
                        /* mandate, maybe we will just force a keyword */
                        subtype = (quarterword) tex_scan_math_class_number(0);
                    }
                }
            }
            break;
    }
    if (! valid_math_class_code(subtype)) {
        subtype = ordinary_noad_subtype;
    }
    /*tex
        Now we can scan for the content:
    */
    {
        halfword content = tex_new_node(math_char_node, 0);
        noad_nucleus(target) = content;
        node_subtype(target) = subtype;
        if (append) {
            tex_tail_append(target);
        }
        tex_aux_scan_math(content, style, usetextfont, 0, 0, 0, subtype, allclass);
    }
}

void tex_run_math_math_component(void)
{
    halfword n = tex_new_node(simple_noad, ordinary_noad_subtype);
    tex_math_set_scripts_options(n);
    tex_aux_math_math_component(n, 1);
}

int tex_is_math_disc(halfword n)
{
    return
        n && node_type(n) == hlist_node && box_list(n) && node_type(box_list(n)) == disc_node &&
        disc_class(box_list(n)) != unset_disc_class && ! node_next(box_list(n));
}

halfword tex_math_make_disc(halfword d)
{
    halfword q = tex_new_node(sub_mlist_node, 0);
    halfword n = tex_new_node(simple_noad, (quarterword) disc_class(d));
    kernel_math_list(q) = d;
    noad_nucleus(n) = q;
    noad_options(n) = noad_option_unpack_list;
    return n;
}

/*tex
    Easiest is to permit all modifiers and just ignore those that make no sense. We then can
    stepwise support whatever modifier we like later on.
*/

void tex_run_math_modifier(void)
{
    halfword tail = cur_list.tail;
    if (cur_list.head != tail) {
        switch (node_type(tail)) {
            case simple_noad:
                switch (cur_chr) {
                    case adapt_to_left_modifier_code:
                        noad_options(tail) = unset_option(noad_options(tail), noad_option_adapt_to_right_size);
                        noad_options(tail) |= noad_option_adapt_to_left_size;
                        break;
                    case adapt_to_right_modifier_code:
                        noad_options(tail) = unset_option(noad_options(tail), noad_option_adapt_to_left_size);
                        noad_options(tail) |= noad_option_adapt_to_right_size;
                        break;
                    /* todo: actually this one can also be used for other types */
                    case axis_modifier_code:
                        noad_options(tail) |= noad_option_axis;
                        break;
                    case no_axis_modifier_code:
                        noad_options(tail) |= noad_option_no_axis;
                        break;
                    case phantom_modifier_code:
                        noad_options(tail) |= noad_option_phantom;
                        break;
                    case void_modifier_code:
                        noad_options(tail) |= noad_option_void;
                        break;
                    case source_modifier_code:
                        if (tex_scan_keyword("nucleus")) {
                            noad_options(tail) |= noad_option_source_on_nucleus;
                        }
                        noad_source(tail) = tex_scan_integer(0, NULL, NULL);
                        break;
                    case openup_height_modifier_code:
                        noad_options(tail) |= noad_option_openup_height;
                        noad_height(tail) = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
                        break;
                    case openup_depth_modifier_code:
                        noad_options(tail) |= noad_option_openup_depth;
                        noad_depth(tail) = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
                        break;
                    case display_limits_modifier_code:
                        noad_options(tail) = unset_option(noad_options(tail), noad_option_limits | noad_option_no_limits);
                        break;
                    case limits_modifier_code:
                        noad_options(tail) = unset_option(noad_options(tail), noad_option_no_limits);
                        noad_options(tail) |= noad_option_limits;
                        break;
                    case no_limits_modifier_code:
                        noad_options(tail) = unset_option(noad_options(tail), noad_option_limits);
                        noad_options(tail) |= noad_option_no_limits;
                        break;
                }
            default:
                switch (node_type(tail)) {
                    case accent_noad:
                        switch (cur_chr) {
                            case source_modifier_code:
                                if (tex_scan_keyword("nucleus")) {
                                    noad_options(tail) |= noad_option_source_on_nucleus;
                                }
                                noad_source(tail) = tex_scan_integer(0, NULL, NULL);
                                break;
                        }

                }
                break;
        }
    }
}

/*tex

     Delimiter fields of noads are filled in by the |scan_delimiter| routine. The first parameter
     of this procedure is the |mem| address where the delimiter is to be placed; the second tells
     if this delimiter follows |\radical| or not.

*/

static void tex_aux_scan_delimiter(halfword target, int code, int mathclass)
{
    delcodeval dval = tex_no_del_code();
    mathcodeval mval = tex_no_math_code();
    switch (code) {
        case no_mathcode:
            /* can be integrated */
            do {
                tex_get_x_token();
            } while (cur_cmd == spacer_cmd || cur_cmd == relax_cmd);
            switch (cur_cmd) {
                case letter_cmd:
                case other_char_cmd:
                    dval = tex_get_del_code(cur_chr);
                    if (tex_has_del_code(dval)) {
                        goto REALDELIMITER;
                    } else {
                        mval = tex_get_math_code(cur_chr);
                        goto FAKEDELIMITER;
                    }
                case delimiter_number_cmd:
                    switch (cur_chr) {
                        case math_delimiter_code:
                            /*tex |\delimiter| */
                            dval = tex_aux_scan_extdef_del_code(tex_mathcode, 1);
                            break;
                        case math_udelimiter_code:
                            /*tex |\Udelimiter| */
                            dval = tex_aux_scan_extdef_del_code(umath_mathcode, 1);
                            break;
                        default:
                            tex_confusion("scan delimiter, case 1");
                            break;
                    }
                    goto REALDELIMITER;
                case mathspec_cmd:
                    mval = tex_get_math_spec(cur_chr);
                    goto FAKEDELIMITER;
                case math_char_number_cmd:
                    switch (cur_chr) {
                        case math_char_number_code:
                            mval = tex_scan_mathchar(tex_mathcode);
                            break;
                        case math_xchar_number_code:
                            mval = tex_scan_mathchar(umath_mathcode);
                            break;
                        case math_char_ignore_code:
                            break;
                        default:
                            tex_confusion("scan math char, case 1");
                            break;
                    }
                    goto FAKEDELIMITER;
            }
            break;
        case tex_mathcode:
            /*tex |\radical| */
            dval = tex_aux_scan_extdef_del_code(tex_mathcode, 1);
            goto REALDELIMITER;
        case umath_mathcode:
            /*tex |\Uradical| */
            dval = tex_aux_scan_extdef_del_code(umath_mathcode, 0);
            goto REALDELIMITER;
        default:
            tex_confusion("scan delimiter, case 2");
            goto REALDELIMITER;
    }
  FAKEDELIMITER:
    if (mathclass != unset_noad_class) {
        mval.class_value = (short) mathclass;
    }
    dval.small = mval;
    dval.large = mval;
  REALDELIMITER:
    if (! target) {
        return;
    } else if (tex_has_del_code(dval)) {
        mathdictval dict = tex_fake_math_dict(dval.small.character_value);
        node_subtype(target) = dval.small.class_value;
        delimiter_small_family(target) = dval.small.family_value;
        delimiter_small_character(target) = dval.small.character_value;
        delimiter_large_family(target) = dval.large.family_value;
        delimiter_large_character(target) = dval.large.character_value;
        delimiter_math_properties(target) = dict.properties;
        delimiter_math_group(target) = dict.group;
        delimiter_math_index(target) = dict.index;
    } else {
        tex_back_input(cur_tok);
        tex_handle_error(
            normal_error_type,
            "Missing delimiter (. inserted)",
            "I was expecting to see something like '(' or '\\{' or '\\}' here. Acceptable\n"
            "delimiters are characters whose \\delcode is nonnegative, or you can use\n"
            "'\\delimiter <delimiter code>'."
        );
        node_subtype(target) = unset_noad_class;
        delimiter_small_family(target) = 0;
        delimiter_small_character(target) = 0;
        delimiter_large_family(target) = 0;
        delimiter_large_character(target) = 0;
    }
    return;
}

void tex_run_math_radical(void)
{
    halfword code = cur_chr;
    fullword options = 0;
    halfword radical = tex_new_node(radical_noad, (quarterword) code);
    halfword style = yet_unset_math_style;
    halfword variant = 0; /* quad, harmless */
    halfword attrlist = null;
    halfword symbolattrlist = null;
    halfword top = null;
    halfword bottom = null;
    tex_tail_append_callback(radical); /* old/new experiment */
    /* only kewords to UI ones? */
    while (1) {
        switch (tex_scan_character("abdehlmnrstuwABDEHLMNRSTUW", 0, 1, 0)) {
            case 0:
                goto DONE;
            case 'a': case 'A':
                if (tex_scan_mandate_keyword("attr", 1)) {
                    attrlist = tex_scan_attribute(attrlist);
                }
                break;
            case 'b': case 'B':
                if (tex_scan_mandate_keyword("bottom", 1)) {
                    bottom = 1;
                }
                break;
            case 'd': case 'D':
                if (tex_scan_mandate_keyword("depth", 1)) {
                    radical_depth(radical) = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
                }
                break;
            case 'e': case 'E':
                if (tex_scan_mandate_keyword("exact", 1)) {
                    options = options | noad_option_exact;
                }
                break;
            case 'h': case 'H':
                if (tex_scan_mandate_keyword("height", 1)) {
                    radical_height(radical) = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
                }
                break;
            case 'l': case 'L':
                if (tex_scan_mandate_keyword("left", 1)) {
                     options = options | noad_option_left;
                }
                break;
            case 'm': case 'M':
                if (tex_scan_mandate_keyword("middle", 1)) {
                    options = options | noad_option_middle;
                }
                break;
            case 'n': case 'N':
                switch (tex_scan_character("oO", 0, 0, 0)) {
                    case 'o': case 'O':
                        switch (tex_scan_character("orOR", 0, 0, 0)) {
                            case 'r': case 'R':
                                if (tex_scan_mandate_keyword("norule", 3)) {
                                    options |= noad_option_no_rule;
                                }
                                break;
                            case 'o': case 'O':
                                if (tex_scan_mandate_keyword("nooverflow", 3)) {
                                    options |= noad_option_no_overflow;
                                }
                                break;
                            default:
                                tex_aux_show_keyword_error("norule|nooverflow");
                                goto DONE;
                        }
                        break;
                    default:
                        tex_aux_show_keyword_error("norule|nooverflow");
                        goto DONE;
                }
                break;
            case 'r': case 'R':
                switch (tex_scan_character("ieIE", 0, 0, 0)) {
                    case 'i': case 'I':
                        if (tex_scan_mandate_keyword("right", 2)) {
                            options = options | noad_option_right;
                        }
                        break;
                    case 'e': case 'E':
                        if (tex_scan_mandate_keyword("reflected", 2)) {
                            options |= noad_option_reflected;
                        }
                        break;
                    default:
                        tex_aux_show_keyword_error("right|reflected");
                        goto DONE;
                }
                break;
            case 's': case 'S':
                switch (tex_scan_character("hiotyHIOTY", 0, 0, 0)) {
                    case 'h': case 'H':
                        if (tex_scan_mandate_keyword("shrink", 2)) {
                             options = options | noad_option_shrink;
                        }
                        break;
                    case 'i': case 'I':
                        if (tex_scan_mandate_keyword("size", 2)) {
                            radical_size(radical) = tex_scan_integer(0, NULL, NULL);
                        }
                        break;
                    case 't': case 'T':
                        switch (tex_scan_character("ryRY", 0, 0, 0)) {
                            case 'r': case 'R':
                                if (tex_scan_mandate_keyword("stretch", 3)) {
                                     options = options | noad_option_stretch;
                                }
                                break;
                            case 'y': case 'Y':
                                if (tex_scan_mandate_keyword("style", 3)) {
                                    switch (code) {
                                        case normal_radical_subtype:
                                        case radical_radical_subtype:
                                        case root_radical_subtype:
                                        case rooted_radical_subtype:
                                        case delimited_radical_subtype:
                                            style = tex_scan_math_style_identifier(1, 0); /* tolerant */
                                            if (style < 0) {
                                                style = yet_unset_math_style;
                                            }
                                            break;
                                        default:
                                            /* ignore */
                                            break;
                                    }
                                }
                                break;
                            default:
                                tex_aux_show_keyword_error("style|stretch");
                                goto DONE;
                        }
                        break;
                    case 'o': case 'O':
                        if (tex_scan_mandate_keyword("source", 2)) {
                            noad_source(radical) = tex_scan_integer(0, NULL, NULL);
                        }
                        break;
                    case 'y': case 'Y':
                        if (tex_scan_mandate_keyword("symbolattr", 2)) {
                            symbolattrlist = tex_scan_extra_attribute(symbolattrlist);
                        }
                        break;
                    default:
                        tex_aux_show_keyword_error("style|source|stretch|shrink|symbolattr");
                        goto DONE;
                }
                break;
            case 't': case 'T':
                if (tex_scan_mandate_keyword("top", 1)) {
                    top = 1;
                }
                break;
            case 'u': case 'U':
                if (tex_scan_mandate_keyword("usecallback", 1)) {
                    options = options | noad_option_use_callback;
                }
                break;
            case 'w': case 'W':
                if (tex_scan_mandate_keyword("width", 1)) {
                    noad_width(radical) = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
                }
                break;
            default:
                goto DONE;
        }
    }
  DONE:
    if (style == yet_unset_math_style) {
        switch (code) {
            case normal_radical_subtype:
            case radical_radical_subtype:
            case root_radical_subtype:
                variant = math_parameter_radical_variant;
                break;
            case under_delimiter_radical_subtype:
                variant = math_parameter_under_delimiter_variant;
                break;
            case over_delimiter_radical_subtype:
                variant = math_parameter_over_delimiter_variant;
                break;
            case delimiter_under_radical_subtype:
                variant = math_parameter_delimiter_under_variant;
                break;
            case delimiter_over_radical_subtype:
                variant = math_parameter_delimiter_over_variant;
                break;
            case delimited_radical_subtype:
                variant = math_parameter_radical_variant; /* math_parameter_delimited_variant */
                break;
            case h_extensible_radical_subtype:
                variant = math_parameter_h_extensible_variant;
                break;
        }
        style = variant ? tex_math_style_variant(cur_list.math_style, variant) : cur_list.math_style;
    }
    if (attrlist) {
        tex_attach_attribute_list_attribute(radical, attrlist);
    }
    if (symbolattrlist) {
        add_attribute_reference(symbolattrlist);
        noad_extra_attr(radical) = symbolattrlist;
    }
    noad_options(radical) = options;
    set_noad_style(radical, style);
    {
        switch (code) {
            case normal_radical_subtype:
                {
                    halfword left = tex_new_node(delimiter_node, 0);
                    radical_left_delimiter(radical) = left;
                    tex_aux_scan_delimiter(left, tex_mathcode, unset_noad_class);
                }
                break;
            case radical_radical_subtype:
            case root_radical_subtype:
            case rooted_radical_subtype:
            case delimited_radical_subtype:
                {
                    halfword left = tex_new_node(delimiter_node, 0);
                    radical_left_delimiter(radical) = left;
                    tex_aux_scan_delimiter(left, umath_mathcode, unset_noad_class);
                }
                switch (code) {
                    case rooted_radical_subtype:
                    case delimited_radical_subtype:
                        {
                            halfword right = tex_new_node(delimiter_node, 0);
                            radical_right_delimiter(radical) = right;
                            tex_aux_scan_delimiter(right, umath_mathcode, unset_noad_class);
                        }
                }
                break;
            case under_delimiter_radical_subtype:
            case over_delimiter_radical_subtype:
            case delimiter_under_radical_subtype:
            case delimiter_over_radical_subtype:
            case h_extensible_radical_subtype:
                {
                    halfword left = tex_new_node(delimiter_node, 0);
                    radical_left_delimiter(radical) = left;
                    tex_aux_scan_delimiter(left, umath_mathcode, unset_noad_class);
                }
                break;
            default:
                tex_confusion("scan math radical");
                break;
        }
        if (top) {
            top = tex_new_node(delimiter_node, 0);
            radical_top_delimiter(radical) = top;
            tex_aux_scan_delimiter(top, umath_mathcode, unset_noad_class);
        }
        if (bottom) {
            bottom = tex_new_node(delimiter_node, 0);
            radical_bottom_delimiter(radical) = bottom;
            tex_aux_scan_delimiter(bottom, umath_mathcode, unset_noad_class);
        }
    }
    switch (code) {
        case h_extensible_radical_subtype:
            /*tex type will change */
            {
                halfword q = tex_new_node(sub_box_node, 0);
                noad_nucleus(radical) = q;
                break;
            }
        case root_radical_subtype:
        case rooted_radical_subtype:
            {
                /*tex Here the |style| option is not that usefull as we remap anyway. */
                saved_radical_initialize();
                saved_radical_degree_done = 0;
                saved_radical_style =  style;
                lmt_save_state.save_stack_data.ptr += saved_radical_n_of_records;
                tex_aux_push_math(math_radical_group, tex_math_style_variant(style, math_parameter_degree_variant), -1);
                tex_scan_left_brace();
                break;
            }
        default :
            {
                halfword q = tex_new_node(math_char_node, 0);
                noad_nucleus(radical) = q;
                tex_aux_scan_math(q, tex_math_style_variant(style, variant ? variant : math_parameter_radical_variant), 0, 0, 0, 0, unset_noad_class, unset_noad_class);
                break;
            }
    }
}

void tex_finish_math_radical(void)
{
    tex_aux_unsave_math();
    {
        halfword whatever = tex_new_node(sub_mlist_node, 0);
        halfword content = tex_aux_finish_math_list(null);
        halfword radical = cur_list.tail;
        kernel_math_list(whatever) = content;
        switch (saved_radical_current_component()) {
            case 0:
                {
                    halfword style = saved_radical_current_style();
                    radical_degree(radical) = whatever;
                    saved_radical_update_component();
                    tex_aux_push_math(math_radical_group, tex_math_style_variant(style, math_parameter_radical_variant), -1);
                    tex_scan_left_brace();
                }
                break;
            case 1:
                {
                    noad_nucleus(radical) = whatever;
                    lmt_save_state.save_stack_data.ptr -= saved_radical_n_of_records;
                }
                break;
            default:
                tex_confusion("scan radical");
                break;
        }
    }
}

void tex_run_math_accent(void)
{
    mathcodeval t = tex_no_math_code();
    mathcodeval b = tex_no_math_code();
    mathcodeval o = tex_no_math_code();
    halfword code = cur_chr;
    halfword accent = tex_new_node(accent_noad, bothflexible_accent_subtype);
    quarterword subtype = ordinary_noad_subtype;
    halfword mathclass = accent_noad_subtype;
    halfword attrlist = null;
    halfword symbolattrlist = null;
    if (cur_cmd == accent_cmd) {
        tex_handle_error(
            normal_error_type,
            "Please use \\mathaccent for accents in math mode",
            "I'm changing \\accent to \\mathaccent here; wish me luck. (Accents are not the\n"
            "same in formulas as they are in text.)" );
    }
    tex_tail_append_callback(accent);
    switch (code) {
        case math_accent_code:
            /*tex |\mathaccent| */
            t = tex_scan_mathchar(tex_mathcode);
            break;
        case math_uaccent_code:
            /*tex |\Umathaccent| */
            while (1) {
                switch (tex_scan_character("abcefknostuABCEFKNOSTU", 0, 1, 0)) {
                    case 'a': case 'A':
                        switch (tex_scan_character("txTX", 0, 0, 0)) {
                            case 't': case 'T':
                                if (tex_scan_mandate_keyword("attr", 2)) {
                                    attrlist = tex_scan_attribute(attrlist);
                                }
                                break;
                         // case 'x': case 'X':
                         //     if (tex_scan_mandate_keyword("axis", 2)) {
                         //         noad_options(accent) |= noad_option_axis;
                         //     }
                         //     break;
                            default:
                         //     tex_aux_show_keyword_error("attr|axis");
                                tex_aux_show_keyword_error("attr");
                                goto DONE;
                        }
                        break;
                    case 'b': case 'B':
                        switch (tex_scan_character("aoAo", 0, 0, 0)) {
                            case 'a': case 'A':
                                if (tex_scan_mandate_keyword("base", 2)) {
                                    noad_options(accent) |= noad_option_auto_base;
                                }
                                break;
                            case 'o': case 'O':
                                /*tex bottom [fixed] <char> */
                                /*tex both [fixed] <char> [fixed] <char> */
                                if (tex_scan_character("t", 0, 0, 0)) {
                                     switch (tex_scan_character("htHT", 0, 0, 0)) {
                                         case 'h': case 'H':
                                            /*tex top bottom */
                                            if (tex_scan_keyword("fixed")) {
                                                node_subtype(accent) = fixedtop_accent_subtype;
                                            }
                                            t = tex_scan_mathchar(umath_mathcode);
                                            if (tex_scan_keyword("fixed")) {
                                                node_subtype(accent) = fixedboth_accent_subtype;
                                            }
                                            b = tex_scan_mathchar(umath_mathcode);
                                            goto DONE;
                                         case 't': case 'T':
                                             if (tex_scan_mandate_keyword("bottom", 4)) {
                                                /*tex bottom */
                                                if (tex_scan_keyword("fixed")) {
                                                    node_subtype(accent) = fixedbottom_accent_subtype;
                                                }
                                                b = tex_scan_mathchar(umath_mathcode);
                                             }
                                            goto DONE;
                                        default:
                                            tex_aux_show_keyword_error("both|bottom");
                                            goto DONE;
                                     }
                                }
                                goto DONE;
                            default:
                                tex_aux_show_keyword_error("base|both|bottom");
                                goto DONE;
                        }
                        break;
                    case 'c': case 'C':
                        switch (tex_scan_character("elEL", 0, 0, 0)) {
                            case 'e': case 'E':
                                if (tex_scan_mandate_keyword("center", 2)) {
                                    noad_options(accent) |= noad_option_center;
                                }
                                break;
                            case 'l': case 'L':
                                if (tex_scan_mandate_keyword("class", 2)) {
                                    halfword c = (quarterword) tex_scan_math_class_number(0);
                                    if (valid_math_class_code(c)) {
                                        mathclass = c;
                                    }
                                }
                                break;
                            default:
                                tex_aux_show_keyword_error("center|class");
                                goto DONE;
                        }
                        break;
                    case 'e': case 'E':
                        if (tex_scan_mandate_keyword("exact", 1)) {
                            noad_options(accent) |= noad_option_exact;
                        }
                        break;
                    case 'f': case 'F':
                        switch (tex_scan_character("frFR", 0, 0, 0)) {
                            case 'r': case 'R':
                                if (tex_scan_mandate_keyword("fraction", 2)) {
                                    accent_fraction(accent) = tex_scan_integer(0, NULL, NULL);
                                }
                                break;
                            case 'f': case 'F':
                                /*tex fixed <char> */
                                if (tex_scan_mandate_keyword("fixed", 2)) {
                                    node_subtype(accent) = fixedtop_accent_subtype;
                                    t = tex_scan_mathchar(umath_mathcode);
                                }
                                goto DONE;
                            default:
                                tex_aux_show_keyword_error("fraction|fixed");
                                goto DONE;
                        }
                    case 'k': case 'K':
                        if (tex_scan_mandate_keyword("keepbase", 1)) {
                            noad_options(accent) |= noad_option_keep_base;
                        }
                        break;
                    case 'n': case 'N':
                        if (tex_scan_mandate_keyword("nooverflow", 1)) {
                            /*tex
                                Actually there never is an overflow but for consistency we do
                                accept this key. Maybe in the future it will be used.
                            */
                            noad_options(accent) |= noad_option_no_overflow;
                        }
                        break;
                    case 'o': case 'O':
                        /*tex overlay [fixed] <char> */
                        if (tex_scan_mandate_keyword("overlay", 1)) {
                            if (tex_scan_keyword("fixed")) {
                                node_subtype(accent) = fixedtop_accent_subtype;
                            }
                            o = tex_scan_mathchar(umath_mathcode);
                        }
                        goto DONE;
                    case 's': case 'S':
                        switch (tex_scan_character("hiotyHIOTY", 0, 0, 0)) {
                            case 'h': case 'H':
                                if (tex_scan_mandate_keyword("shrink", 2)) {
                                    noad_options(accent) |= noad_option_shrink;
                                }
                                break;
                            case 'i': case 'I':
                                if (tex_scan_mandate_keyword("single", 2)) {
                                    noad_options(accent) |= noad_option_single;
                                }
                                break;
                            case 'o': case 'O':
                                if (tex_scan_mandate_keyword("source", 2)) {
                                    noad_source(accent) = tex_scan_integer(0, NULL, NULL);
                                }
                                break;
                            case 't': case 'T':
                                if (tex_scan_mandate_keyword("stretch", 2)) {
                                    noad_options(accent) |= noad_option_stretch;
                                }
                                break;
                            case 'y': case 'Y':
                                if (tex_scan_mandate_keyword("symbolattr", 2)) {
                                    symbolattrlist = tex_scan_extra_attribute(symbolattrlist);
                                }
                                break;
                            default:
                                tex_aux_show_keyword_error("source|stretch|shrink|single|symbolattr");
                                goto DONE;
                        }
                        break;
                    case 't': case 'T':
                        /*tex top [fixed] <char> */
                        if (tex_scan_mandate_keyword("top", 1)) {
                            if (tex_scan_keyword("fixed")) {
                                node_subtype(accent) = fixedtop_accent_subtype;
                            }
                            t = tex_scan_mathchar(umath_mathcode);
                        }
                        goto DONE;
                    case 'u': case 'U':
                        if (tex_scan_mandate_keyword("usecallback", 1)) {
                            noad_options(accent) |= noad_option_use_callback;
                        }
                        break;
                    default:
                        /*tex top <char> */
                        t = tex_scan_mathchar(umath_mathcode);
                        goto DONE;
                }
            }
        default:
            tex_confusion("scan math accent");
    }
  DONE:
    if (attrlist) {
        tex_attach_attribute_list_attribute(accent, attrlist);
    }
    if (symbolattrlist) {
        add_attribute_reference(symbolattrlist);
        noad_extra_attr(accent) = symbolattrlist;
    }
    if (! (t.character_value == 0 && t.family_value == 0)) {
        halfword n = tex_new_node(math_char_node, 0);
        subtype = tex_aux_set_math_char(n, &t, NULL);
        accent_top_character(accent) = n;
    }
    if (! (b.character_value == 0 && b.family_value == 0)) {
        halfword n = tex_new_node(math_char_node, 0);
        subtype = tex_aux_set_math_char(n, &b, NULL);
        accent_bottom_character(accent) = n;
    }
    if (! (o.character_value == 0 && o.family_value == 0)) {
        halfword n = tex_new_node(math_char_node, 0);
        subtype = tex_aux_set_math_char(n, &o, NULL);
        accent_middle_character(accent) = n;
    }
    {
        halfword n = tex_new_node(math_char_node, subtype);
        noad_nucleus(accent) = n;
        tex_aux_scan_math(n, tex_math_style_variant(cur_list.math_style, math_parameter_accent_variant), 0, 0, 0, 0, unset_noad_class, unset_noad_class);
    }
    set_noad_main_class(accent, mathclass);
}

/*tex

    The routine that scans the four mlists of a |\mathchoice| is very much like the routine that
    builds discretionary nodes. Finally, the |\mathchoice| primitive creates a |choice_node|,
    which has special subfields |display_mlist|, |text_mlist|, |script_mlist|, and
    |script_script_mlist| pointing to the mlists for each style.

*/

void tex_run_math_choice(void) {
    switch (cur_chr) {
        case math_choice_code:
            /*tex |\mathchoice| */
            {
                halfword n = tex_new_node(choice_node, normal_choice_subtype);
                tex_tail_append(n);
                saved_choice_initialize();
                saved_choice_count = math_display_choice;
                lmt_save_state.save_stack_data.ptr += saved_choice_n_of_records;
                tex_aux_push_math(math_choice_group, display_style, -1);
                tex_scan_left_brace();
                break;
            }
        case math_discretionary_code:
            /*tex |\mathdiscretionary| */
            {
                halfword n = tex_new_node(choice_node, discretionary_choice_subtype);
                choice_class(n) = unset_noad_class;
                while (1) {
                    switch (tex_scan_character("cC", 0, 1, 0)) {
                        case 0:
                            goto DONE;
                        case 'c': case 'C':
                            if (tex_scan_mandate_keyword("class", 1)) {
                                choice_class(n) = tex_scan_math_class_number(0);
                            }
                            break;
                        default:
                            goto DONE;
                    }
                }
              DONE:
                tex_tail_append(n);
                saved_choice_initialize();
                saved_choice_count = math_pre_break_choice;
                lmt_save_state.save_stack_data.ptr += saved_choice_n_of_records;
                tex_aux_push_math(math_choice_group, cur_list.math_style, -1);
                tex_scan_left_brace();
                break;
            }
        case math_stack_code:
            /*tex |\mathstack| */
            {
             // halfword m = tex_new_node(sub_mlist_node, 0); /* was for some reason a math_char_node */
                halfword m = tex_new_node(math_char_node, 0);
                halfword n = tex_new_node(simple_noad, ordinary_noad_subtype);
                halfword s = tex_math_style_variant(cur_list.math_style, math_parameter_stack_variant);
                tex_tail_append(n);
                noad_nucleus(n) = m;
                tex_scan_left_brace();
                saved_math_group_initialize();
                saved_math_group_pointer = m;
                saved_math_group_all_class = unset_noad_class;
                lmt_save_state.save_stack_data.ptr += saved_math_group_n_of_records;
                tex_aux_push_math(math_stack_group, s, -1);
                break;
            }
    }
}

int tex_current_math_style(void)
{
    return is_m_mode(cur_list.mode) ? cur_list.math_style : -1;
}

int tex_current_math_main_style(void)
{
    return is_m_mode(cur_list.mode) ? cur_list.math_main_style : -1;
}

int tex_current_math_parent_style(void)
{
    return is_m_mode(cur_list.mode) ? cur_list.math_parent_style : -1;
}

void tex_finish_math_choice(void)
{
    tex_aux_unsave_math();
    {
        halfword content = tex_aux_finish_math_list(null);
        halfword choice = saved_choice_current_component();
        if (choice >= 0) {
            int style = cur_list.math_style;
            switch (node_subtype(cur_list.tail)) {
                case normal_choice_subtype:
                    switch (choice) {
                        case math_display_choice:
                            choice_display_mlist(cur_list.tail) = content;
                            style = text_style;
                            break;
                        case math_text_choice:
                            choice_text_mlist(cur_list.tail) = content;
                            style = script_style;
                            break;
                        case math_script_choice:
                            choice_script_mlist(cur_list.tail) = content;
                            style = script_script_style;
                            break;
                        case math_script_script_choice:
                            choice_script_script_mlist(cur_list.tail) = content;
                            lmt_save_state.save_stack_data.ptr -= saved_choice_n_of_records;
                            return;
                    }
                    break;
                case discretionary_choice_subtype:
                    switch (choice) {
                        case math_pre_break_choice:
                            choice_pre_break(cur_list.tail) = content;
                            style = display_style;
                            break;
                        case math_post_break_choice:
                            choice_post_break(cur_list.tail) = content;
                            style = text_style;
                            break;
                        case math_no_break_choice:
                            choice_no_break(cur_list.tail) = content;
                            style = script_style;
                            lmt_save_state.save_stack_data.ptr -= saved_choice_n_of_records;
                            return;
                    }
                    break;
            }
            saved_choice_update_component();
            tex_aux_push_math(math_choice_group, style, -1);
            tex_scan_left_brace();
        } else {
            tex_confusion("scan build choices");
        }
    }
}

void tex_finish_math_fraction(void)
{
    tex_aux_unsave_math();
    {
        halfword content = tex_aux_finish_math_list(null);
        halfword over = saved_fraction_current_component();
        if (over >= 0) {
            halfword autostyle = saved_fraction_current_autostyle();
            halfword userstyle = saved_fraction_current_userstyle();
            halfword fraction = cur_list.tail;
            set_noad_style(fraction, userstyle);
            switch (over) {
                case math_numerator_above:
                    kernel_math_list(fraction_numerator(fraction)) = content;
                    break;
                case math_denominator_above:
                    kernel_math_list(fraction_denominator(fraction)) = content;
                    lmt_save_state.save_stack_data.ptr -= saved_fraction_n_of_records;
                    return;
            }
            saved_fraction_update_component();
            tex_aux_push_math(math_fraction_group, autostyle, -1);
            tex_scan_left_brace();
        } else {
            tex_confusion("scan build fraction");
        }
    }
}

void tex_finish_math_operator(void)
{
    tex_aux_unsave_math();
    {
        halfword content = tex_aux_finish_math_list(null);
        halfword over = saved_operator_current_component();
        if (over >= 0) {
            halfword fenced = cur_list.tail;
            switch (over) {
                case math_limits_top:
                    if (content) {
                        halfword top = tex_new_node(sub_mlist_node, 0);
                        fence_delimiter_top(fenced) = top;
                        kernel_math_list(top) = content;
                    }
                    break;
                case math_limits_bottom:
                    if (content) {
                        halfword bottom = tex_new_node(sub_mlist_node, 0);
                        fence_delimiter_bottom(fenced) = bottom;
                        kernel_math_list(bottom) = content;
                    }
                    lmt_save_state.save_stack_data.ptr -= saved_operator_n_of_records;
                    return;
            }
            saved_operator_update_component();
            tex_aux_push_math(math_operator_group, tex_math_style_variant(cur_list.math_style, math_parameter_subscript_variant), -1);
            tex_scan_left_brace();
        } else {
            tex_confusion("math operator");
        }
    }
}

/*tex

    Subscripts and superscripts are attached to the previous nucleus by the action procedure called
    |sub_sup|.

*/

void tex_run_math_script(void)
{
    int code = cur_chr;
    halfword tail = cur_list.tail;
    switch (cur_cmd) {
        case subscript_cmd:
            code = math_sub_script_code;
            break;
        case superscript_cmd:
            code = math_super_script_code;
            break;
    }
    switch (code) {
        case math_no_script_space_code:
            {
                halfword glue = tex_new_glue_node(zero_glue, conditional_math_glue);
                tex_tail_append(glue);
                tex_add_glue_option(glue, glue_option_no_auto_break);
            }
            return;
        case math_no_ruling_space_code:
            {
                halfword glue = tex_new_glue_node(zero_glue, rulebased_math_glue);
                tex_tail_append(glue);
                tex_add_glue_option(glue, glue_option_no_auto_break);
            }
            return;
        case math_sub_script_code:
            tex_get_token();
            if (cur_tok == underscore_token || cur_cmd == subscript_cmd) {
                tex_get_token();
                if (cur_tok == underscore_token || cur_cmd == subscript_cmd) {
                    tex_get_token();
                    if (cur_tok == underscore_token || cur_cmd == subscript_cmd) {
                        code = math_indexed_sub_pre_script_code; /* ____ */
                    } else {
                        tex_back_input(cur_tok);
                        code = math_sub_pre_script_code; /* ___ */
                    }
                } else {
                    tex_back_input(cur_tok);
                    code = math_indexed_sub_script_code; /* __ */
                }
            } else {
                tex_back_input(cur_tok); /* _ */
            }
            break;
        case math_super_script_code:
            tex_get_token();
            if (cur_tok == circumflex_token || cur_cmd == superscript_cmd) {
                tex_get_token();
                if (cur_tok == circumflex_token || cur_cmd == superscript_cmd) {
                    tex_get_token();
                    if (cur_tok == circumflex_token || cur_cmd == superscript_cmd) {
                        code = math_indexed_super_pre_script_code; /* ^^^^ */
                    } else {
                        tex_back_input(cur_tok);
                        code = math_super_pre_script_code; /* ^^^ */
                    }
                } else {
                    tex_back_input(cur_tok);
                    code = math_indexed_super_script_code; /* ^^ */
                }
            } else {
                tex_back_input(cur_tok); /* ^ */
            }
            break;
        case math_no_script_code:
            if (tex_math_scripts_allowed(cur_list.tail)) {
                noad_options(cur_list.tail) |= noad_option_no_more_scripts;
            }
            return;
    }
    if (tail == cur_list.head) {
        tail = tex_math_double_atom(0);
    } else if (tex_math_no_more_scripts(tail)) {
        tail = tex_math_double_atom(1);
    } else if (! tex_math_scripts_allowed(tail)) {
        tail = tex_math_double_atom(0);
    }
    switch (code) {
        case math_sub_script_code:
        case math_no_sub_script_code:
        case math_indexed_sub_script_code:
            {
                if (noad_subscr(tail)) {
                    tail = tex_math_double_atom(1);
                    if (math_double_script_mode_par < 0) {
                        tex_handle_error(
                            normal_error_type,
                            "Double subscript",
                            "I treat 'x_1_2' essentially like 'x_1{}_2'."
                        );
                    }
                }
                switch (code) {
                    case math_no_sub_script_code:
                        noad_options(tail) |= noad_option_no_sub_script;
                        break;
                    case math_indexed_sub_script_code:
                        noad_options(tail) |= noad_option_indexed_sub_script;
                        break;
                }
                {
                    halfword n = tex_new_node(math_char_node, 0);
                    noad_subscr(tail) = n;
                    tex_aux_scan_math(n, tex_math_style_variant(cur_list.math_style, math_parameter_subscript_variant), 0, 0, 0, 1, unset_noad_class, unset_noad_class);
                    if (! noad_script_order(tail)) {
                        noad_script_order(tail) = script_subscript_first;
                    }
                }
                break;
            }
        case math_sub_pre_script_code:
        case math_no_sub_pre_script_code:
        case math_indexed_sub_pre_script_code:
            {
                if (noad_subprescr(tail)) {
                    int limitation = node_type(tail) == fraction_noad; /*tex See remark at node definition. */
                    tail = tex_math_double_atom(1);
                    if (math_double_script_mode_par < 0) {
                        tex_handle_error(
                            normal_error_type,
                            limitation ? "Fractions take no pre subscript directly" : "Double pre subscript",
                            "I just ignore it; consider wrapping this element."
                        );
                    }
                }
                switch (code) {
                    case math_no_sub_pre_script_code:
                        noad_options(tail) |= noad_option_no_sub_pre_script;
                        break;
                    case math_indexed_sub_pre_script_code:
                        noad_options(tail) |= noad_option_indexed_sub_pre_script;
                        break;
                }
                {
                    halfword n = tex_new_node(math_char_node, 0);
                    noad_subprescr(tail) = n;
                    tex_aux_scan_math(n, tex_math_style_variant(cur_list.math_style, math_parameter_subscript_variant), 0, 0, 0, 1, unset_noad_class, unset_noad_class);
                }
                break;
            }
        case math_super_script_code:
        case math_no_super_script_code:
        case math_indexed_super_script_code:
            {
                if (noad_supscr(tail)) {
                    tail = tex_math_double_atom(1);
                    if (math_double_script_mode_par < 0) {
                        tex_handle_error(
                            normal_error_type,
                            "Double superscript",
                            "I treat 'x^1^2' essentially like 'x^1{}^2'."
                        );
                    }
                }
                switch (code) {
                    case math_no_super_script_code:
                        noad_options(tail) |= noad_option_no_super_script;
                        break;
                    case math_indexed_super_script_code:
                        noad_options(tail) |= noad_option_indexed_super_script;
                        break;
                }
                {
                    halfword n = tex_new_node(math_char_node, 0);
                    noad_supscr(tail) = n;
                    if (! noad_script_order(tail)) {
                        noad_script_order(tail) = script_superscript_first;
                    }
                    tex_aux_scan_math(n, tex_math_style_variant(cur_list.math_style, math_parameter_superscript_variant), 0, 0, 0, 1, unset_noad_class, unset_noad_class);
                }
                break;
            }
        case math_super_pre_script_code:
        case math_no_super_pre_script_code:
        case math_indexed_super_pre_script_code:
            {
                if (noad_supprescr(tail)) {
                    int limitation = node_type(tail) == fraction_noad; /*tex See remark at node definition. */
                    tail = tex_math_double_atom(1);
                    if (math_double_script_mode_par < 0) {
                        tex_handle_error(
                            normal_error_type,
                            limitation ? "Fractions take no pre superscript directly" : "Double pre superscript",
                            "I just ignore it; consider wrapping this element."
                        );
                    }
                }
                switch (code) {
                    case math_no_super_script_code:
                        noad_options(tail) |= noad_option_no_super_pre_script;
                        break;
                    case math_indexed_super_pre_script_code:
                        noad_options(tail) |= noad_option_indexed_super_pre_script;
                        break;
                }
                {
                    halfword n = tex_new_node(math_char_node, 0);
                    noad_supprescr(tail) = n;
                    tex_aux_scan_math(n, tex_math_style_variant(cur_list.math_style, math_parameter_superscript_variant), 0, 0, 0, 1, unset_noad_class, unset_noad_class);
                }
                break;
            }
        case math_prime_script_code:
            {
                if (noad_prime(tail)) {
                    tail = tex_math_double_atom(1);
                    if (math_double_script_mode_par < 0) {
                        tex_handle_error(
                            normal_error_type,
                            "Double prime script",
                            "I'll add a dummy nucleus."
                        );
                    }
                }
                {
                    halfword n = tex_new_node(math_char_node, 0);
                    noad_prime(tail) = n;
                    if (! noad_script_order(tail)) {
                        noad_script_order(tail) = script_primescript_first;
                    }
                    /* maybe it's own variant */
                    tex_aux_scan_math(n, tex_math_style_variant(cur_list.math_style, math_parameter_superscript_variant), 0, 0, 0, 1, unset_noad_class, unset_noad_class);
                }
                break;
            }
    }
}

/*tex

    An operation like |\over| causes the current mlist to go into a state of suspended animation:
    |incomplete_noad| points to a |fraction_noad| that contains the mlist-so-far as its numerator,
    while the denominator is yet to come. Finally when the mlist is finished, the denominator will
    go into the incomplete fraction noad, and that noad will become the whole formula, unless it is
    surrounded by |\left| and |\right| delimiters.

    We can probably replace the |incomplete_noad_par| trickery because we can now look back in the
    list using the |node_prev| field. But not now.

*/

void tex_run_math_fraction(void)
{
    /*tex The type of generalized fraction we are scanning: */
    halfword code = cur_chr;
    if (cur_list.incomplete_noad) {
        /*tex Recovery code. */
        switch (code) {
            case math_above_delimited_code:
            case math_over_delimited_code:
            case math_atop_delimited_code:
            case math_u_above_delimited_code:
            case math_u_over_delimited_code:
            case math_u_atop_delimited_code:
            case math_u_skewed_delimited_code:
            case math_u_stretched_delimited_code:
                tex_aux_scan_delimiter(null, no_mathcode, unset_noad_class);
                tex_aux_scan_delimiter(null, no_mathcode, unset_noad_class);
                break;
        }
        switch (code) {
            case math_above_code:
            case math_above_delimited_code:
            case math_u_above_code:
            case math_u_above_delimited_code:
                tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
                break;
        }
        /*tex This is somewhat weird, this error here. */
        tex_handle_error(
            normal_error_type,
            "Ambiguous; you need another { and }",
            "I'm ignoring this fraction specification, since I don't know whether a\n"
            "construction like 'x \\over y \\over z' means '{x \\over y} \\over z' or\n"
            "'x \\over {y \\over z}'."
        );
    } else {
        halfword fraction = tex_new_node(fraction_noad, 0);
        halfword numerator = tex_new_node(sub_mlist_node, 0);
        halfword denominator = null;
        halfword autostyle = tex_math_style_variant(cur_list.math_style, math_parameter_fraction_variant);
        halfword userstyle = -1; // todo: yet_unset_math_style;
        halfword attrlist = null;
        halfword symbolattrlist = null;
        fullword options = 0;
        halfword mathclass = fraction_noad_subtype;
        halfword rulethickness = preset_rule_thickness;
        int ruledone = 0;
        fraction_h_factor(fraction) = scaling_factor;
        fraction_v_factor(fraction) = scaling_factor;
        switch (code) {
            case math_above_code:
            case math_above_delimited_code:
                node_subtype(fraction) = above_fraction_subtype;
                goto NEXTSTEP1;
            case math_over_code:
            case math_over_delimited_code:
                node_subtype(fraction) = over_fraction_subtype;
                goto NEXTSTEP1;
            case math_atop_code:
            case math_atop_delimited_code:
                node_subtype(fraction) = atop_fraction_subtype;
              NEXTSTEP1:
                {
                    cur_list.incomplete_noad = fraction;
                    fraction_numerator(fraction) = numerator;
                    kernel_math_list(numerator) = node_next(cur_list.head);
                    node_next(cur_list.head) = null;
                    cur_list.tail = cur_list.head;
                    cur_list.math_style = autostyle;
                    break;
                }
            case math_u_above_code:
            case math_u_above_delimited_code:
                node_subtype(fraction) = above_fraction_subtype;
                goto NEXTSTEP2;
            case math_u_over_code:
            case math_u_over_delimited_code:
                node_subtype(fraction) = over_fraction_subtype;
                goto NEXTSTEP2;
            case math_u_atop_code:
            case math_u_atop_delimited_code:
                node_subtype(fraction) = atop_fraction_subtype;
                goto NEXTSTEP2;
            case math_u_skewed_code:
            case math_u_skewed_delimited_code:
                node_subtype(fraction) = skewed_fraction_subtype;
                goto NEXTSTEP2;
            case math_u_stretched_code:
            case math_u_stretched_delimited_code:
                node_subtype(fraction) = stretched_fraction_subtype;
              NEXTSTEP2:
                {
                    cur_list.incomplete_noad = null;
                    denominator = tex_new_node(sub_mlist_node, 0);
                    tex_tail_append_callback(fraction); /* old/new experiment */
                    fraction_numerator(fraction) = numerator;
                    fraction_denominator(fraction) = denominator;
                    break;
                }
        }
        switch (code) {
            case math_u_skewed_code:
            case math_u_skewed_delimited_code:
            case math_u_stretched_code:
            case math_u_stretched_delimited_code:
                {
                    halfword q = tex_new_node(delimiter_node, 0);
                    fraction_middle_delimiter(fraction) = q;
                    tex_aux_scan_delimiter(q, no_mathcode, unset_noad_class);
                    break;
                }
        }
        switch (code) {
            case math_above_delimited_code:
            case math_over_delimited_code:
            case math_atop_delimited_code:
            case math_u_above_delimited_code:
            case math_u_over_delimited_code:
            case math_u_atop_delimited_code:
            case math_u_skewed_delimited_code:
            case math_u_stretched_delimited_code:
                {
                    halfword left = tex_new_node(delimiter_node, 0);
                    halfword right = tex_new_node(delimiter_node, 0);
                    fraction_left_delimiter(fraction) = left;
                    fraction_right_delimiter(fraction) = right;
                    tex_aux_scan_delimiter(left, no_mathcode, open_noad_subtype);
                    tex_aux_scan_delimiter(right, no_mathcode, close_noad_subtype);
                    break;
                }
        }
        switch (code) {
            /*tex We can't have keyword here because of compatibility reasons. */
            case math_above_code:
            case math_above_delimited_code:
                rulethickness = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
                break;
            case math_over_code:
            case math_over_delimited_code:
                rulethickness = preset_rule_thickness;
                break;
            case math_atop_code:
            case math_atop_delimited_code:
                break;
            /*tex
                But here we can! For practical reasons we accept the rule related options
                and in principle we cold do with one command.
            */
            case math_u_above_code:
            case math_u_above_delimited_code:
                goto OPTIONS;
            case math_u_atop_code:
            case math_u_atop_delimited_code:
            case math_u_over_code:
            case math_u_over_delimited_code:
                ruledone = 1;
                goto OPTIONS;
            case math_u_stretched_code:
            case math_u_stretched_delimited_code:
            case math_u_skewed_code:
            case math_u_skewed_delimited_code:
                ruledone = 1;
              OPTIONS:
                while (1) {
                    switch (tex_scan_character("acefhnpstuvACEFHNPSTUV", 0, 1, 0)) {
                        case 'a': case 'A':
                            if (tex_scan_mandate_keyword("attr", 1)) {
                                attrlist = tex_scan_attribute(attrlist);
                            }
                            break;
                        case 'c': case 'C':
                            if (tex_scan_mandate_keyword("class", 1)) {
                                halfword c = (quarterword) tex_scan_math_class_number(0);
                                if (valid_math_class_code(c)) {
                                    mathclass = c;
                                }
                            }
                            break;
                        case 'e': case 'E':
                            /* not used */
                            if (tex_scan_mandate_keyword("exact", 1)) {
                                options |= noad_option_exact;
                            }
                            break;
                        case 'f': case 'F':
                            if (tex_scan_mandate_keyword("font", 1)) {
                                ruledone = 1;
                                options |= noad_option_prefer_font_thickness;
                            }
                            break;
                        case 'h': case 'H':
                            if (tex_scan_mandate_keyword("hfactor", 1)) {
                                fraction_h_factor(fraction) = tex_scan_integer(0, NULL, NULL);
                            }
                            break;
                        case 'n': case 'N':
                            /*tex A bit over the top, three steps but a push back is still worse. */
                            if (tex_scan_character("oO", 0, 0, 0)) {
                                switch (tex_scan_character("aoAO", 0, 0, 0)) {
                                    case 'a': case 'A':
                                        if (tex_scan_mandate_keyword("noaxis", 3)) {
                                            options |= noad_option_no_axis;
                                        }
                                        break;
                                    case 'o': case 'O':
                                        if (tex_scan_mandate_keyword("nooverflow", 3)) {
                                            options |= noad_option_no_overflow;
                                        }
                                        break;
                                    default:
                                        tex_aux_show_keyword_error("noaxis|nooverflow");
                                        goto DONE;
                                }
                            }
                            break;
                        case 'p': case 'P':
                            /* not used */
                            if (tex_scan_mandate_keyword("proportional", 1)) {
                                options |= noad_option_proportional;
                            }
                            break;
                        case 's': case 'S':
                            switch (tex_scan_character("toyTOY", 0, 0, 0)) {
                                case 't': case 'T':
                                    if (tex_scan_mandate_keyword("style", 2)) {
                                        halfword style = tex_scan_math_style_identifier(1, 0); /* tolerant */
                                        if (denominator && style >= 0) {
                                            userstyle = style;
                                        } else {
                                            /* just ignore */
                                        }
                                    }
                                    break;
                                case 'o': case 'O':
                                    if (tex_scan_mandate_keyword("source", 2)) {
                                        noad_source(fraction) = tex_scan_integer(0, NULL, NULL);
                                    }
                                    break;
                                case 'y': case 'Y':
                                    if (tex_scan_mandate_keyword("symbolattr", 2)) {
                                        symbolattrlist = tex_scan_extra_attribute(symbolattrlist);
                                    }
                                    break;
                                default:
                                    tex_aux_show_keyword_error("style|source|symbolattr");
                                    goto DONE;
                            }
                            break;
                        case 't': case 'T':
                            if (tex_scan_mandate_keyword("thickness", 1)) {
                                ruledone = 1;
                                rulethickness = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
                            }
                            break;
                        case 'u': case 'U':
                            if (tex_scan_mandate_keyword("usecallback", 1)) {
                                ruledone = 1;
                                options |= noad_option_use_callback;
                            }
                            break;
                        case 'v': case 'V':
                            if (tex_scan_mandate_keyword("vfactor", 1)) {
                                fraction_v_factor(fraction) = tex_scan_integer(0, NULL, NULL);
                            }
                            break;
                        default:
                            goto DONE;
                    }
                }
              DONE:
                if (! ruledone) {
                    rulethickness = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
                }
                break;
        }
        fraction_rule_thickness(fraction) = rulethickness;
        noad_options(fraction) = options;
        set_noad_main_class(fraction, mathclass);
        if (attrlist) {
            tex_attach_attribute_list_attribute(fraction, attrlist);
        }
        if (symbolattrlist) {
            add_attribute_reference(symbolattrlist);
            noad_extra_attr(fraction) = symbolattrlist;
        }
        if (denominator) {
            /*tex
                In this case we need to pick up two math groups, and after some playing around using
                a variant of choices made most sense.
            */
            saved_fraction_initialize();
            saved_fraction_variant = math_numerator_above;
            saved_fraction_autostyle = autostyle;
            saved_fraction_userstyle = userstyle;
            lmt_save_state.save_stack_data.ptr += saved_fraction_n_of_records;
            cur_list.math_flatten = 0;
            tex_aux_push_math(math_fraction_group, autostyle, -1);
            tex_scan_left_brace();
        } else {
            /*tex
                This is the pre/post variant. Actually, this variant is the reason why math scanning
                code is somewhat complex, this |incomplete_noad| stuff.
            */
        }
    }
}

/*tex

    At the end of a math formula or subformula, the |finish_math_list| routine is called upon to
    return a halfword to the newly completed mlist, and to pop the nest back to the enclosing
    semantic level. The parameter to |finish_math_list|, if not null, points to a |fence_noad| that
    ends the current mlist; this |fence_noad| has not yet been appended.

*/

static halfword tex_aux_finish_math_list(halfword p)
{
    halfword q = null;
    /*tex So now we're ready to proceed. */
    if (cur_list.incomplete_noad) {
        halfword denominator = fraction_denominator(cur_list.incomplete_noad);
        if (denominator) {
            node_type(denominator) = sub_mlist_node;
        } else {
            denominator = tex_new_node(sub_mlist_node, 0);
            fraction_denominator(cur_list.incomplete_noad) = denominator;
            q = denominator;
        }
        kernel_math_list(denominator) = node_next(cur_list.head);
        if (p) {
            halfword numerator = fraction_numerator(cur_list.incomplete_noad);
            q = kernel_math_list(numerator);
            if ((node_type(q) != fence_noad) || (node_subtype(q) != left_fence_side) || (! cur_list.delimiter)) {
                tex_confusion("right fence");
            }
            kernel_math_list(numerator) = node_next(cur_list.delimiter);
            node_next(cur_list.delimiter) = cur_list.incomplete_noad;
            node_next(cur_list.incomplete_noad) = p;
        } else {
            q = cur_list.incomplete_noad;
        }
    } else {
        node_next(cur_list.tail) = p;
        q = node_next(cur_list.head);
    }
    tex_pop_nest();
    return q;
}

/*tex
    Here traditional \TEX\ does some flattening but it can interfrere. It is for instance needed
    in order to find the skew of an accented character which happens at the outer level but that
    bit of code now does that recursively. I need to check why the accent was flattened so we
    keep the original code here for testing.

    A \CONTEXT\ test case: |$\tilde{x}'$| i.e.\ primes!
*/

static void tex_aux_flatten_math_list(halfword parent)
{
    halfword p = kernel_math_list(parent);
    if (p && ! node_next(p)) {
        switch (node_type(p)) {
            case simple_noad:
                {
                    // how about the options and class
                    if (! noad_has_following_scripts(p) && tex_math_has_class_option(node_subtype(p), flatten_class_option) && ! noad_source(p)) {
                        halfword n = noad_nucleus(p);
                        halfword s = parent;
                        node_type(s) = node_type(n);
                        tex_math_copy_char_data(s, n, 1);
                        tex_attach_attribute_list_copy(s, n);
                        tex_flush_node(p);
                    }
                    break;
                }
            case accent_noad:
                {
                    halfword tail = cur_list.tail;
                    if (saved_math_group_pointer == noad_nucleus(tail) && node_type(tail) == simple_noad) {
                        switch (node_subtype(tail)) {
                            case ordinary_noad_subtype:
                                tex_couple_nodes(node_prev(tail), p);
                                noad_nucleus(tail) = null;
                                noad_subscr(tail) = null;
                                noad_supscr(tail) = null;
                                noad_prime(tail) = null;
                                tex_attach_attribute_list_copy(p, tail);
                                tex_flush_node(tail);
                                cur_list.tail = p;
                                break;
                        }
                    }
                    break;
                }
        }
    }
}

/*tex

    Now at last we're ready to see what happens when a right brace occurs in a math formula. Two
    special cases are simplified here: braces are effectively removed when they surround a single
    Ord without sub- and/or superscripts, or when they surround an accent that is the nucleus of
    an Ord atom.

*/

void tex_finish_math_group(void)
{
    int old_style = cur_list.math_style;
    halfword p, parent;
    quarterword allclass;
    tex_aux_unsave_math();
    lmt_save_state.save_stack_data.ptr -= saved_math_group_n_of_records;
    parent = saved_math_group_pointer;
    allclass = (quarterword) saved_math_group_all_class;
    node_type(parent) = sub_mlist_node; /* can be math_char_node */
    p = tex_aux_finish_math_list(null); /* this incomplete trickery */
    kernel_math_list(parent) = p;
    if (cur_list.math_flatten) {
        tex_aux_flatten_math_list(parent);
    }
    /*tex
        If needed, here we pickup a next \quote {argument}, so we sort of finish a group and reopen
        a new one. It is somewhat curious that we use a character node here.
    */
    if (allclass != unset_noad_class) {
        while (p) {
            if (node_type(p) == simple_noad) {
             // node_subtype(p) = allclass;
                if (get_noad_main_class(p) == unset_noad_class) {
                    set_noad_main_class(p, allclass);
                }
                if (get_noad_left_class(p) == unset_noad_class) {
                    set_noad_left_class(p, allclass);
                }
                if (get_noad_right_class(p) == unset_noad_class) {
                    set_noad_right_class(p, allclass);
                }
            }
            p = node_next(p);
        }
        /* */
    }
    if (node_next(saved_math_group_pointer) > 0) {
        halfword q = tex_new_node(math_char_node, 0); /* hm */
        noad_nucleus(node_next(saved_math_group_pointer)) = q;
        node_next(saved_math_group_pointer) = null;
        saved_math_group_pointer = q;
        tex_aux_scan_math(q, old_style, 0, 0, 0, 0, unset_noad_class, unset_noad_class);
        /*tex restart */
    }
}

/*tex

    We have dealt with all constructions of math mode except |\left| and |\right|, so the picture is
    completed by the following sections of the program. The |middle| feature of \ETEX\ allows one or
    several |\middle| delimiters to appear between |\left| and |\right|.

*/

void tex_run_math_fence(void)
{
    scaled ht = 0;
    scaled dp = 0;
    scaled top = 0;
    scaled bottom = 0;
    fullword options = 0;
    halfword variant = 0;
    halfword mainclass = unset_noad_class;
    halfword leftclass = unset_noad_class;
    halfword rightclass = unset_noad_class;
    halfword source = 0;
    halfword factor = scaling_factor;
    halfword attrlist = null;
    halfword symbolattrlist = null;
    quarterword st = (quarterword) cur_chr;
    halfword style = cur_list.math_style;
    halfword call = tex_tail_fetch_callback();
    if (math_check_fences_par) {
        options |= noad_option_no_check;
    }
    switch (st) {
        case left_operator_side:
        case no_fence_side:
            break;
        case extended_left_fence_side:   /*tex |\Uleft| */
            st = left_fence_side;
            break;
        case extended_middle_fence_side: /*tex |\Umiddle| */
            st = middle_fence_side;
            break;
        case extended_right_fence_side:  /*tex |\Uright| */
            st = right_fence_side;
            break;
        default :
            goto CHECK_PAIRING;
    }
    while (1) {
        /* todo: break down  */
        switch (tex_scan_character("abcdefhlmnprstuvABCDEFHLMNPRSTUV", 0, 1, 0)) {
            case 0:
                goto CHECK_PAIRING;
            case 'a': case 'A':
                switch (tex_scan_character("tuxTUX", 0, 0, 0)) {
                    case 't': case 'T':
                        if (tex_scan_mandate_keyword("attr", 2)) {
                            attrlist = tex_scan_attribute(attrlist);
                        }
                        break;
                    case 'u': case 'U':
                        if (tex_scan_mandate_keyword("auto", 2)) {
                            options |= noad_option_auto;
                        }
                        break;
                    case 'x': case 'X':
                        if (tex_scan_mandate_keyword("axis", 2)) {
                            options |= noad_option_axis;
                        }
                        break;
                    default:
                        tex_aux_show_keyword_error("auto|attr|axis");
                        goto CHECK_PAIRING;
                }
                break;
            case 'b': case 'B':
                if (tex_scan_mandate_keyword("bottom", 1)) {
                    bottom = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
                }
                break;
            case 'c': case 'C':
                if (tex_scan_mandate_keyword("class", 1)) {
                    mainclass = tex_scan_math_class_number(0);
                }
                break;
            case 'd': case 'D':
                if (tex_scan_mandate_keyword("depth", 1)) {
                    dp = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
                }
                break;
            case 'e': case 'E':
                if (tex_scan_mandate_keyword("exact", 1)) {
                    options |= noad_option_exact;
                }
                break;
            case 'f': case 'F':
                if (tex_scan_mandate_keyword("factor", 1)) {
                    factor = tex_scan_integer(0, NULL, NULL);
                }
                break;
            case 'h': case 'H':
                if (tex_scan_mandate_keyword("height", 1)) {
                    ht = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
                }
                break;
            case 'l': case 'L':
                switch (tex_scan_character("eiEI", 0, 0, 0)) {
                    case 'e': case 'E':
                        if (tex_scan_mandate_keyword("leftclass", 2)) {
                            halfword c = tex_scan_math_class_number(0);
                         // if (! valid_math_class_code(c)) {
                            if (valid_math_class_code(c)) {
                                leftclass = c;
                            }
                        }
                        break;
                    case 'i': case 'I':
                        if (tex_scan_mandate_keyword("limits", 2)) {
                            options = unset_option(options, noad_option_no_limits);
                            options |= noad_option_limits;
                        }
                        break;
                    default:
                        tex_aux_show_keyword_error("leftclass|limits");
                        goto CHECK_PAIRING;
                }
                break;
            case 'n': case 'N':
                switch (tex_scan_character("oO", 0, 0, 0)) {
                    case 'o': case 'O':
                        switch (tex_scan_character("acloACLO", 0, 0, 0)) {
                            case 'a': case 'A':
                                if (tex_scan_mandate_keyword("noaxis", 3)) {
                                    options |= noad_option_no_axis;
                                }
                                break;
                            case 'c': case 'C':
                                if (tex_scan_mandate_keyword("nocheck", 3)) {
                                    options |= noad_option_no_check;
                                }
                                break;
                            case 'l': case 'L':
                                if (tex_scan_mandate_keyword("nolimits", 3)) {
                                    options = unset_option(options, noad_option_limits);
                                    options |= noad_option_no_limits;
                                }
                                break;
                            case 'o': case 'O':
                                if (tex_scan_mandate_keyword("nooverflow", 3)) {
                                    options |= noad_option_no_overflow;
                                }
                                break;
                            default:
                                tex_aux_show_keyword_error("noaxis|nolimits|nocheck|nooverflow");
                                goto CHECK_PAIRING;
                        }
                        break;
                    default:
                        goto CHECK_PAIRING;
                }
                break;
            case 'm': case 'M':
                if (tex_scan_mandate_keyword("middle", 1)) {
                    options |= noad_option_auto_middle;
                }
                break;
            case 'p': case 'P':
                if (tex_scan_mandate_keyword("phantom", 1)) {
                    options |= noad_option_phantom;
                }
                break;
            case 'r': case 'R':
                if (tex_scan_mandate_keyword("rightclass", 1)) {
                    halfword c = tex_scan_math_class_number(0);
                    if (valid_math_class_code(c)) {
                        rightclass = c;
                    }
                }
                break;
            case 's': case 'S':
                switch (tex_scan_character("cioyCIOY", 0, 0, 0)) {
                    case 'c': case 'C':
                        if (tex_scan_mandate_keyword("scale", 2)) {
                            options |= noad_option_scale;
                        }
                        break;
                    case 'i': case 'I':
                        if (tex_scan_mandate_keyword("single", 2)) {
                            options |= noad_option_single;
                        }
                        break;
                    case 'o': case 'O':
                        if (tex_scan_mandate_keyword("source", 2)) {
                            source = tex_scan_integer(0, NULL, NULL);
                        }
                        break;
                    case 'y': case 'Y':
                        if (tex_scan_mandate_keyword("symbolattr", 2)) {
                            symbolattrlist = tex_scan_extra_attribute(symbolattrlist);
                        }
                        break;
                    default:
                        tex_aux_show_keyword_error("scale|source|single|size|symbolattr");
                        goto CHECK_PAIRING;
                }
                break;
            case 't': case 'T':
                if (tex_scan_mandate_keyword("top", 1)) {
                    top = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
                }
                break;
            case 'u': case 'U':
                if (tex_scan_mandate_keyword("usecallback", 1)) {
                    options |= noad_option_use_callback;
                }
                break;
            case 'v': case 'V':
                switch (tex_scan_character("aoAO", 0, 0, 0)) {
                    case 'a': case 'A':
                        if (tex_scan_mandate_keyword("variant", 2)) {
                            variant = tex_scan_integer(0, NULL, NULL);
                        }
                        break;
                    case 'o': case 'O':
                        if (tex_scan_mandate_keyword("void", 2)) {
                            options |= noad_option_void;
                        }
                        break;
                    default:
                        tex_aux_show_keyword_error("void|variant");
                        goto CHECK_PAIRING;
                }
                break;
            default:
                goto CHECK_PAIRING;
        }
    }
  CHECK_PAIRING:
    switch (st) {
        case no_fence_side:
        case left_fence_side:
            break;
        case left_operator_side:
            {
                /* becomes a class option */
                int indisplay = style == display_style || style == cramped_display_style;
                /* options |= noad_option_no_check; */ /*tex Best just expect a dummy right. */
                if (! (has_option(options, noad_option_limits) || has_option(options, noad_option_no_limits))) {
                    /* otherwise we don't enter the placement function */
                    options |= indisplay ? noad_option_limits : noad_option_no_limits;
                }
            }
            break;
        default:
            if (cur_group != math_fence_group) {
                tex_aux_append_math_fence_val(tex_no_math_code(), tex_no_dict_code(), open_noad_subtype);
            }
            switch (cur_group) {
                case math_fence_group:
                    break;
                case math_inline_group:
                case math_display_group:
                case math_equation_number_group:
                    tex_aux_scan_delimiter(null, no_mathcode, unset_noad_class);
                    if (st == middle_fence_side) {
                        tex_handle_error(
                            normal_error_type,
                            "Extra \\middle",
                            "I'm ignoring a \\middle that had no matching \\left."
                        );
                    } else {
                        tex_handle_error(
                            normal_error_type,
                            "Extra \\right",
                            "I'm ignoring a \\right that had no matching \\left."
                        );
                    }
                    break;
                default:
                    tex_off_save();
            }
    }
    /*tex
        Now we only have a no, left, middle or right case left.
    */
    {
        halfword fence = tex_new_node(fence_noad, st);
        halfword delimiter = tex_new_node(delimiter_node, 0);
        halfword autoclass = unset_noad_class;
        fence_delimiter(fence) = delimiter;
        fence_delimiter_variant(fence) = variant;
        noad_height(fence) = ht;
        noad_depth(fence) = dp;
        noad_options(fence) = options;
        tex_set_noad_classes(fence, mainclass);
        if (leftclass != unset_noad_class) {
            set_noad_left_class(fence, leftclass);
        }
        if (rightclass != unset_noad_class) {
            set_noad_right_class(fence, rightclass);
        }
        noad_italic(fence) = 0;
        noad_source(fence) = source;
        /* */
        fence_top_overshoot(fence) = top;
        fence_bottom_overshoot(fence) = bottom;
        /* */
        if (symbolattrlist) {
            add_attribute_reference(symbolattrlist);
            noad_extra_attr(fence) = symbolattrlist;
        }
        /*tex
            By setting this here, we can get rid of the hard coded values in |mlist_to_hlist| which
            sort of interfere (or at least confuse) things there. When set, the |leftclass| and
            |rightclass| settings win anyway.
        */
        if (mainclass == unset_noad_class) {
            mainclass = node_subtype(delimiter);
            if (mainclass == unset_noad_class || mainclass == ordinary_noad_subtype) {
                switch (st) {
                    case left_fence_side:
                        mainclass = open_noad_subtype;
                        break;
                    case middle_fence_side:
                        mainclass = middle_noad_subtype;
                        break;
                    case right_fence_side:
                        mainclass = close_noad_subtype;
                        break;
                }
            }
            set_noad_main_class(fence, mainclass);
        }
        /* */
        switch (st) {
            case left_fence_side:
                autoclass = open_noad_subtype;
                break;
            case middle_fence_side:
                autoclass = middle_noad_subtype; /* we need a way to overload this */
                break;
            case right_fence_side:
                autoclass = close_noad_subtype;
                break;
        }
        /* */
        tex_aux_scan_delimiter(delimiter, no_mathcode, autoclass);
        /* */
        if (attrlist) {
            tex_attach_attribute_list_attribute(fence, attrlist);
            tex_attach_attribute_list_attribute(delimiter, attrlist);
        }
        switch (st) {
            case left_fence_side:
                tex_aux_append_math_fence(fence, open_noad_subtype);
                fence_nesting_factor(fence) = factor;
                break;
            case middle_fence_side:
                tex_aux_append_math_fence(fence, middle_noad_subtype);
                break;
            case right_fence_side:
                tex_aux_append_math_fence(fence, close_noad_subtype);
                fence_nesting_factor(fence) = factor;
                break;
            case left_operator_side:
                {
                    tex_aux_push_math(math_fence_group, style, -1);
                    node_next(cur_list.head) = fence;
                    cur_list.tail = fence;
                    cur_list.delimiter = fence;
                    saved_operator_initialize();
                    saved_operator_variant = math_limits_top;
                    lmt_save_state.save_stack_data.ptr += saved_operator_n_of_records;
                    tex_aux_push_math(math_operator_group, tex_math_style_variant(style, math_parameter_superscript_variant), -1);
                    tex_scan_left_brace();
                }
                break;
            case no_fence_side:
                {
                    halfword n = tex_new_node(simple_noad, fenced_noad_subtype);
                    halfword l = tex_new_node(sub_mlist_node, 0);
                    if (attrlist) {
                        tex_attach_attribute_list_attribute(n, attrlist);
                        tex_attach_attribute_list_attribute(l, attrlist);
                    }
                    tex_tail_append(n);
                    set_noad_main_class(n, mainclass); /*tex Really needed here! */
                    noad_nucleus(n) = l;
                    kernel_math_list(noad_nucleus(n)) = fence;
                }
                break;
            default:
                tex_confusion("left right fence");
                break;
        }
        if (! tex_tail_apply_callback(fence, call)) {
            tex_confusion("messed up fence");
        }
    }
}

/*tex

    \TEX\ gets to the following part of the program when the first |$| ending a display has been
    scanned.

*/

static void tex_aux_check_second_math_shift(void)
{
    tex_get_x_token();
    if (cur_cmd != math_shift_cmd) {
        tex_back_input(cur_tok);
        tex_handle_error(
            normal_error_type,
            "Display math should end with $$",
            "The '$' that I just saw supposedly matches a previous '$$'. So I shall assume\n"
            "that you typed '$$' both times."
        );
    }
}

static void tex_aux_check_display_math_end(void)
{
    switch (cur_chr) {
        case end_display_math_code:
        case end_math_mode_code:
            return;
    }
    tex_handle_error(
        normal_error_type,
        "Display math should end with \\Ustopdisplaymath or \\Ustopmathmode",
        "I shall assume that you typed that."
    );
}

static void tex_aux_check_inline_math_end(void)
{
    switch (cur_chr) {
        case end_inline_math_code:
        case end_math_mode_code:
            return;
    }
    tex_handle_error(
        normal_error_type,
        "Inline math should end with \\Ustopmath or \\Ustopmathmode",
        "I shall assume that you typed that."
    );
}

static void tex_aux_resume_after_display(void)
{
    switch (cur_group) {
        case math_display_group:
            tex_aux_unsave_math();
            cur_list.prev_graf += 3;
            tex_push_nest();
            cur_list.mode = hmode;
            cur_list.space_factor = default_space_factor;
            /*tex
                This needs to be intercepted in the display math start! Todo! Why do we have this
                anyway.
            */
            tex_tail_append(tex_new_par_node(parameter_par_subtype));
            tex_get_x_token();
            if (cur_cmd != spacer_cmd) {
                tex_back_input(cur_tok);
            }
            if (lmt_nest_state.nest_data.ptr == 1) {
                tex_build_page(after_display_page_context, 0);
            }
            break;
        default:
            tex_confusion("finishing display math");
            break;
    }
}

/*tex

    The more fuzy part of math mode processing occurs when a displayed formula is being centered and
    placed with an optional equation number. At this time we are in vertical mode (or internal
    vertical mode).

    \starttabulate
    \NC \type {p} \NC points to the mlist for the formula \NC \NR
    \NC \type {a} \NC is either |null| or it points to a box containing the equation number \NC \NR
    \NC \type {l} \NC is true if there was an |\leqno| (so |a| is a horizontal box) \NC \NR
    \stoptabulate

    Per 2022 we ditched display mode in \CONTEXT\ LMTX\ so the code related to display math is now
    completely frozen, if only because testing has become unreasonable. There is anyway not much more
    to do here.

*/

static void tex_aux_inject_display_skip(quarterword param, quarterword subtype)
{
    if (param > 0) {
        switch (display_skip_mode_par) {
            case display_skip_default :
            case display_skip_always :
                break;
            case display_skip_non_zero:
                if (tex_glue_is_zero(glue_parameter(param))) {
                    return;
                } else {
                    break;
                }
            case display_skip_ignore:
                return;
            default:
                /*tex > 3 reserved for future use */
                break;
        }
        tex_tail_append(tex_new_param_glue_node(param, subtype));
    }
}

static void tex_aux_finish_displayed_math(int atleft, halfword eqnumber, halfword equation)
{
    /*tex box containing the equation */
    halfword equation_box;
    /*tex width of the equation */
    scaled equation_width;
    /*tex width of the line */
    scaled line_width;
    /*tex width of equation number */
    scaled number_width;
    /*tex width of equation number plus space to separate from equation */
    scaled number_plus_gap_width;
    /*tex move the line right this much */
    scaled indent;
    /*tex displacement of equation in the line */
    scaled displacement;
    /*tex glue parameter codes for before and after */
    quarterword glue_above, glue_below;
    /*tex glue parameter subtypes for before and after */
    quarterword subtype_above, subtype_below;
    /*tex for equation numbers */
    scaled eqno_width;
    /*tex true if the math and surrounding (par) dirs are different */
    int swap_dir = math_direction_par != pre_display_direction_par;
    if (eqnumber && swap_dir) {
        atleft = ! atleft;
    }
    /* */
    lmt_packaging_state.post_adjust_tail = post_adjust_head;
    lmt_packaging_state.pre_adjust_tail = pre_adjust_head;
    lmt_packaging_state.post_migrate_tail = post_migrate_head;
    lmt_packaging_state.pre_migrate_tail = pre_migrate_head;
    /* */
    equation_box = tex_hpack(equation, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
    node_subtype(equation_box) = equation_list;
    tex_attach_current_attribute_list(equation_box);
    equation = box_list(equation_box);
    /* */
    equation_width = box_width(equation_box);
    line_width = display_width_par;
    indent = display_indent_par;
    if (eqnumber) {
        number_width = box_width(eqnumber);
        eqno_width = number_width;
        number_plus_gap_width = number_width + tex_round_xn_over_d(math_eqno_gap_step_par, tex_get_math_quad_style(text_style), scaling_factor);
        node_subtype(eqnumber) = equation_number_list;
        /*tex tex_attach_current_attribute_list(eqno_box); */
    } else {
        number_width = 0;
        eqno_width = 0;
        number_plus_gap_width = 0;
    }
    if (equation_width + number_plus_gap_width > line_width) {
        /*tex

            The user can force the equation number to go on a separate line by causing its width to
            be zero.

        */
        if ((number_width != 0) && ((equation_width - lmt_packaging_state.total_shrink[normal_glue_order] + number_plus_gap_width <= line_width)
                || (lmt_packaging_state.total_shrink[fi_glue_order] != 0)
                || (lmt_packaging_state.total_shrink[fil_glue_order] != 0)
                || (lmt_packaging_state.total_shrink[fill_glue_order] != 0)
                || (lmt_packaging_state.total_shrink[filll_glue_order] != 0))) {
            box_list(equation_box) = null;
            tex_flush_node(equation_box);
            equation_box = tex_hpack(equation, line_width - number_plus_gap_width, packing_exactly, direction_unknown, holding_none_option, box_limit_none);
            node_subtype(equation_box) = equation_list;
            tex_attach_current_attribute_list(equation_box);
        } else {
            number_width = 0;
            if (equation_width > line_width) {
                box_list(equation_box) = null;
                tex_flush_node(equation_box);
                equation_box = tex_hpack(equation, line_width, packing_exactly, direction_unknown, holding_none_option, box_limit_none);
                node_subtype(equation_box) = equation_list;
                tex_attach_current_attribute_list(equation_box);
            }
        }
        equation_width = box_width(equation_box);
    }
    /*tex

        We try first to center the display without regard to the existence of the equation number.
        If that would make it too close (where \quote {too close} means that the space between
        display and equation number is less than the width of the equation number), we either
        center it in the remaining space or move it as far from the equation number as possible.
        The latter alternative is taken only if the display begins with glue, since we assume that
        the user put glue there to control the spacing precisely.

    */
    displacement = tex_half_scaled(line_width - equation_width);
    if ((number_width > 0) && (displacement < 2 * number_width)) {
        /*tex too close */
        displacement = tex_half_scaled(line_width - equation_width - number_width);
        /*
        if (p && !is_char_node(p) && node_type(p) == glue_node)
            d = 0;
        */ /* kind of weird this, so why not just */
        if (equation && node_type(equation) == glue_node) {
            displacement = 0;
        }
    }
    tex_tail_append(tex_new_penalty_node(pre_display_penalty_par, before_display_penalty_subtype));
    if ((displacement + indent <= pre_display_size_par) || ((cur_list.math_dir == dir_lefttoright) &&   atleft)
                                                        || ((cur_list.math_dir == dir_righttoleft) && ! atleft)) {
        /*tex not enough clearance */
        glue_above = above_display_skip_code;
        subtype_above = above_display_skip_glue;
        glue_below = below_display_skip_code;
        subtype_below = below_display_skip_glue;
    } else {
        glue_above = above_display_short_skip_code;
        subtype_above = above_display_short_skip_glue;
        glue_below = below_display_short_skip_code;
        subtype_below = below_display_short_skip_glue;
    }
    /*tex

        If the equation number is set on a line by itself, either before or after the formula, we
        append an infinite penalty so that no page break will separate the display from its number;
        and we use the same size and displacement for all three potential lines of the display,
        even though |\parshape| may specify them differently; |\leqno| on a forced single line due
        to |width=0|; it follows that |type(a) = hlist_node|.

    */
    if (eqnumber && atleft && (number_width == 0)) {
     /* if (math_direction_par == dir_lefttoright) { */
            box_shift_amount(eqnumber) = 0;
     /* } else { */
     /* } */
        tex_append_to_vlist(eqnumber, lua_key_index(equation_number), NULL);
        tex_tail_append(tex_new_penalty_node(infinite_penalty, equation_number_penalty_subtype));
    } else {
        tex_aux_inject_display_skip(glue_above, subtype_above);
    }
    if (number_width != 0) {
        /*tex
            This has been brought in sync with \LUATEX\ 2025 but I can't test it right now. I'll
            check it when there is a use case.
        */
        scaled shift = line_width - equation_width - number_width - displacement;
        halfword move = tex_new_kern_node(shift, explicit_kern_subtype);
        if (atleft) {
            scaled amount;
            if (math_direction_par == dir_lefttoright) {
                /*tex TRT + TLT + \eqno : (swap_dir=true,  math_direction_par=TLT, l=true) */
                /*tex TLT + TLT + \leqno: (swap_dir=false, math_direction_par=TLT, l=true) */
                amount = shift + number_width;
            } else {
                /*tex TLT + TRT + \eqno : (swap_dir=true,  math_direction_par=TRT, l=true) */
                /*tex TRT + TRT + \leqno: (swap_dir=false, math_direction_par=TRT, l=true) */
                amount = line_width - equation_width;
            }
            {
                halfword kern = tex_new_kern_node(amount, explicit_kern_subtype);
                tex_try_couple_nodes(eqnumber, move);
                tex_try_couple_nodes(move, equation_box);
                tex_try_couple_nodes(equation_box, kern);
                equation_box = eqnumber;
            }
        } else {
            scaled amount;
            if (swap_dir) {
                if (math_direction_par == dir_lefttoright) {
                    /*tex TRT + TLT + \leqno: (swap_dir=true,  math_direction_par=TLT, l=false) */
                    amount = line_width - equation_width;
                } else {
                    /*tex TLT + TRT + \leqno: (swap_dir=true,  math_direction_par=TRT, l=false) */
                    amount = shift + equation_width;
                }
            } else {
                if (math_direction_par == dir_lefttoright) {
                    /*tex TLT + TLT + \eqno : (swap_dir=false, math_direction_par=TLT, l=false) */
                    amount = displacement;
                } else {
                    /*tex TRT + TRT + \eqno : (swap_dir=false, math_direction_par=TRT, l=false) */
                    amount = shift + number_width;
                }
            }
            {
                halfword kern = tex_new_kern_node(amount, explicit_kern_subtype);
                tex_try_couple_nodes(kern, equation_box);
                tex_try_couple_nodes(equation_box, move);
                tex_try_couple_nodes(move, eqnumber);
                equation_box = kern;
            }
        }
        equation_box = tex_hpack(equation_box, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
        node_subtype(equation_box) = equation_list; /* new */
        tex_attach_current_attribute_list(equation_box);
        box_shift_amount(equation_box) = indent;
    } else {
        box_shift_amount(equation_box) = indent + displacement;
    }
    /* */
    if (pre_adjust_head != lmt_packaging_state.pre_adjust_tail) {
        tex_inject_adjust_list(pre_adjust_head, pre_append_adjust_context, 1, lmt_linebreak_state.just_box, NULL);
    }
    lmt_packaging_state.pre_adjust_tail = null;
    /* Pre-migrate content (callback). */
    if (pre_migrate_head != lmt_packaging_state.pre_migrate_tail) {
        tex_append_list(pre_migrate_head, lmt_packaging_state.pre_migrate_tail);
     // if (! lmt_page_builder_state.output_active) {
     //     lmt_append_line_filter_callback(pre_migrate_append_line_context, 0);
     // }
    }
    /* */
    tex_append_to_vlist(equation_box, lua_key_index(equation), NULL); /* eqbox has the formula */
    if (eqnumber && number_width == 0 && ! atleft) {
        tex_tail_append(tex_new_penalty_node(infinite_penalty, equation_number_penalty_subtype));
     /* if (math_direction_par == dir_lefttoright) { */
            box_shift_amount(eqnumber) = indent + line_width - eqno_width ;
     /* } else { */
     /* } */
        tex_append_to_vlist(eqnumber, lua_key_index(equation_number), NULL);
        glue_below = 0; /* shouldn't this be an option */
    }
    /* */
    if (post_migrate_head != lmt_packaging_state.post_migrate_tail) {
        tex_append_list(post_migrate_head, lmt_packaging_state.post_migrate_tail);
        // if (! lmt_page_builder_state.output_active) {
        //     lmt_append_line_filter_callback(post_migrate_append_line_context, 0);
        // }
    }
    lmt_packaging_state.post_migrate_tail = null;
    if (lmt_packaging_state.post_adjust_tail) {
        if (post_adjust_head != lmt_packaging_state.post_adjust_tail) {
            tex_inject_adjust_list(post_adjust_head, post_append_adjust_context, 1, null, NULL);
        }
        lmt_packaging_state.post_adjust_tail = null;
    }
    lmt_packaging_state.except = 0;
    /* */
    tex_tail_append(tex_new_penalty_node(post_display_penalty_par, after_display_penalty_subtype));
    tex_aux_inject_display_skip(glue_below, subtype_below);
    tex_aux_resume_after_display();
}

/*tex

    A |math_node|, which occurs only in horizontal lists, appears before and after mathematical
    formulas. The |subtype| field is |before| before the formula and |after| after it. There is a
    |surround| field, which represents the amount of surrounding space inserted by |\mathsurround|.

    As an outcome of the math upgrading sub project that Mikael Sundqvist and I undertook end 2021
    and beginning 2022 Mikael suggested penalties surrounding inline formulas so there you have it:
    |\preinlinepanelty| and |\postinlinepanelty|.

*/

/* make proper mappers (see 5967 in texmlist) */

static inline int tex_aux_class_from_glyph(halfword n)
{
    return node_subtype(n) - (node_subtype(n) > glyph_math_extra_subtype ? glyph_math_extra_subtype : glyph_math_ordinary_subtype);
}

static inline int tex_aux_class_from_list(halfword n)
{
    switch (node_subtype(n)) {
        case math_fraction_list:
            return fraction_noad_subtype;
        case math_accent_list:
            return accent_noad_subtype;
        case math_radical_list:
            return radical_noad_subtype;
        default:
            return 0;
    }
}

static int tex_aux_short_math(halfword m)
{
 // tex_show_node_list(m,10000,10000);
    if (m) {
        /*tex
            These are the cases we catch, the class option drives succes.

            \starttyping
            kern[] glyph[subtype -> class] vlist[scripts] kern[]
                   hlist[subtype -> class]
                   vlist[subtype -> class]
            \stoptyping

        */
        switch (node_type(m)) {
            case kern_node:
                /*tex Do we need to test for some size here? */
                m = node_next(m);
                break;
            case hlist_node:
            case vlist_node:
                /*tex
                    These are actually more extensive constructs that we don't want to analyze any
                    further (for being single characters).
                */
                if (! node_next(m) && tex_math_has_class_option(tex_aux_class_from_list(m), short_inline_class_option)) {
                    scaled threshold = short_inline_math_threshold_par;
                    if (threshold > 0 && box_width(m) <= threshold) {
                        return 1;
                    }
                }
                return 0;
        }
        /*tex We don't have a list so we check for a list now. */
        if (m) {
            switch (node_type(m)) {
                case glyph_node:
                    if (tex_math_has_class_option(tex_aux_class_from_glyph(m), short_inline_class_option)) {
                        m = node_next(m);
                        break;
                    } else {
                        return 0;
                    }
                default:
                    return 0;
                }
        } else {
            return 0;
        }
        /*tex We accept optional sub, super or combined scripts. */
        if (m) {
            switch (node_type(m)) {
                case vlist_node:
                case hlist_node:
                    switch (node_subtype(m)) {
                        case math_sup_list:
                        case math_sub_list:
                        case math_prime_list:
                        case math_pre_post_sup_list:
                        case math_pre_post_sub_list:
                        case math_scripts_list:
                           m = node_next(m);
                           break;
                    }
                /* default */
            }
        }
        /*tex We ignore trailing kerns (for now). We could test for size. */
        if (m && node_type(m) == kern_node) {
            m = node_next(m);
        }
        return ! m;
    }
    return 0;
}

void tex_run_math_shift(void)
{
    switch (cur_group) {
        case math_inline_group:
        case math_display_group:
        case math_equation_number_group:
            {
                /*tex box containing equation number */
                halfword eqnumber = null;
                /*tex Use |\leqno| instead of |\eqno|, we default to right. */
                int atleft = 0;
                /*tex |mmode| or |-mmode| */
                int mode = cur_list.mode;
                int mathmode = cur_list.math_mode;
                /*tex this pops the nest, the formula */
                halfword mathlist = tex_aux_finish_math_list(null);
                int mathleft = cur_list.math_begin;
                int mathright = cur_list.math_end;
                if (cur_cmd == math_shift_cs_cmd) {
                    switch (cur_chr) {
                        case begin_inline_math_code:
                        case begin_display_math_code:
                        case begin_math_mode_code:
                            tex_you_cant_error(NULL);
                            break;
                    }
                }
                if (cur_list.mode == -mode) { // todo: symbolic
                    /*tex end of equation number */
                  AGAIN:
                    switch (cur_cmd) {
                        case math_shift_cmd:
                            tex_aux_check_second_math_shift();
                            break;
                        case end_paragraph_cmd:
                            tex_get_x_token();
                            goto AGAIN;
                        default:
                            tex_aux_check_display_math_end();
                            break;
                    }
                    tex_run_mlist_to_hlist(mathlist, 0, text_style, unset_noad_class, unset_noad_class);
                    eqnumber = tex_hpack(node_next(temp_head), 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
                    tex_attach_current_attribute_list(eqnumber);
                    tex_aux_unsave_math();
                    /*tex now |cur_group = math_shift_group| */
                    lmt_save_state.save_stack_data.ptr -= saved_equation_number_n_of_records;
                 // if (saved_type(saved_equation_number_location_entry) == equation_number_location_save_type) {
                        atleft = saved_equation_number_location == left_location_code;
                        mode = cur_list.mode;
                        mathlist = tex_aux_finish_math_list(null);
                 // } else {
                 //     tex_confusion("after math");
                 // }
                }
                if (mode == inline_mmode) {
             // if (mode < 0) {
                    /*tex

                        The |unsave| is done after everything else here; hence an appearance of |\mathsurround|
                        inside of |$...$| affects the spacing at these particular |$|'s. This is consistent
                        with the conventions of |$$ ... $$|, since |\abovedisplayskip| inside a display affects
                        the space above that display.

                    */
                    halfword beginmath = tex_new_node(math_node, begin_inline_math);
                    halfword endmath = tex_new_node(math_node, end_inline_math);
                    halfword shortmath = 0;
                    if (math_options_par & math_option_no_snapping) {
                        math_options(beginmath) |= math_option_no_snapping;
                        math_options(endmath) |= math_option_no_snapping;
                    }
                    if (math_options_par & math_option_snapping) {
                        math_options(beginmath) |= math_option_snapping;
                        math_options(endmath) |= math_option_snapping;
                    }
// if (math_options_par & math_option_cramped) {
//     math_options(beginmath) |= math_option_cramped;
//     math_options(endmath) |= math_option_cramped;
// }
                    switch (cur_list.math_main_style) {
                        case display_style:
                        case cramped_display_style:
                            math_options(beginmath) |= math_option_display;
                            math_options(endmath) |= math_option_display;
                            break;
                    }
                    switch (cur_list.math_main_style) {
                        case cramped_display_style:
                        case cramped_text_style:
                        case cramped_script_style:
                        case cramped_script_script_style:
                            math_options(beginmath) |= math_option_cramped;
                            math_options(endmath) |= math_option_cramped;
                            break;
                    }
                    if (mathmode) {
                        switch (cur_cmd) {
                            case math_shift_cs_cmd:
                                if (cur_chr != end_display_math_code && cur_chr != end_math_mode_code) {
                                    tex_aux_check_second_math_shift();
                                }
                                break;
                            case math_shift_cmd:
                                tex_aux_check_second_math_shift();
                                break;
                        }
                    } else if (cur_cmd == math_shift_cs_cmd) {
                        tex_aux_check_inline_math_end();
                    }
                    tex_tail_append(beginmath);
                    if (pre_inline_penalty_par != max_integer) {
                        math_penalty(beginmath) = pre_inline_penalty_par;
                    }
                    /*tex begin mathskip code */
                    switch (math_skip_mode_par) {
                        case math_skip_surround_when_zero:
                            if (tex_glue_is_zero(math_skip_par)) {
                                math_surround(beginmath) = math_surround_par;
                            } else {
                                tex_copy_glue_values(beginmath, math_skip_par);
                            }
                            break ;
                        case math_skip_always_left:
                        case math_skip_always_both:
                        case math_skip_only_when_skip:
                            tex_copy_glue_values(beginmath, math_skip_par);
                            break ;
                        case math_skip_always_right:
                        case math_skip_ignore:
                            break ;
                        case math_skip_always_surround:
                        default:
                            math_surround(beginmath) = math_surround_par;
                            break;
                    }
                    /*tex end mathskip code */
                    if (cur_list.math_dir) {
                        tex_tail_append(tex_new_dir(normal_dir_subtype, math_direction_par));
                    }
                    tex_run_mlist_to_hlist(mathlist, cur_list.mode > nomode, is_valid_math_style(cur_list.math_main_style) ?  cur_list.math_main_style : text_style, cur_list.math_begin, cur_list.math_end);
                    shortmath = tex_aux_short_math(node_next(temp_head));
                    tex_try_couple_nodes(cur_list.tail, node_next(temp_head));
                    cur_list.tail = tex_tail_of_node_list(cur_list.tail);
                    if (cur_list.math_dir) {
                        tex_tail_append(tex_new_dir(cancel_dir_subtype, math_direction_par));
                    }
                    cur_list.math_dir = 0;
                    tex_tail_append(endmath);
                    /* */
                    if (post_inline_penalty_par != max_integer) {
                        math_penalty(endmath) = post_inline_penalty_par;
                    }
                    /*tex begin mathskip code */
                    switch (math_skip_mode_par) {
                        case math_skip_surround_when_zero :
                            if (tex_glue_is_zero(math_skip_par)) {
                                math_surround(endmath) = math_surround_par;
                            } else {
                                tex_copy_glue_values(endmath, math_skip_par);
                                math_surround(endmath) = 0;
                            }
                            break;
                        case math_skip_always_right:
                        case math_skip_always_both:
                        case math_skip_only_when_skip:
                            tex_copy_glue_values(endmath, math_skip_par);
                            break;
                        case math_skip_always_left:
                        case math_skip_ignore:
                            break;
                        case math_skip_always_surround:
                        default:
                            math_surround(endmath) = math_surround_par;
                            break;
                    }
                    /*tex end mathskip code */
                    if (shortmath) {
                        if (pre_short_inline_penalty_par != max_integer) {
                            math_penalty(beginmath) = pre_short_inline_penalty_par;
                        }
                        if (post_short_inline_penalty_par != max_integer) {
                            math_penalty(endmath) = post_short_inline_penalty_par;
                        }
                        tex_add_math_option(beginmath, math_option_short);
                        tex_add_math_option(endmath, math_option_short);
                    }
                    cur_list.space_factor = default_space_factor;
                    mathleft = cur_list.math_begin;
                    mathright = cur_list.math_end;
                    math_tolerance(beginmath) = math_tolerance_par;
                    math_pre_tolerance(beginmath) = math_pre_tolerance_par;
                    math_tolerance(endmath) = tolerance_par;
                    math_pre_tolerance(endmath) = pre_tolerance_par;
                    tex_aux_unsave_math();
                } else {
                    if (! eqnumber) {
                        if (cur_cmd == math_shift_cmd) {
                            tex_aux_check_second_math_shift();
                        } else {
                            tex_aux_check_display_math_end();
                        }
                    }
                    tex_run_mlist_to_hlist(mathlist, 0, display_style, cur_list.math_begin, cur_list.math_end);
                    mathleft = cur_list.math_begin;
                    mathright = cur_list.math_end;
                    tex_aux_finish_displayed_math(atleft, eqnumber, node_next(temp_head));
                }
                /* local */
                update_tex_math_left_class(mathleft);
                update_tex_math_right_class(mathright);
                /* global */
                lmt_math_state.last_left = mathleft;
                lmt_math_state.last_right = mathright;
            }
            break;
        default:
            tex_off_save();
            break;
    }
}

/*tex

    When |\halign| appears in a display, the alignment routines operate essentially as they do in
    vertical mode. Then the following program is activated, with |p| and |q| pointing to the
    beginning and end of the resulting list, and with |aux_save| holding the |prev_depth| value.

*/

void tex_finish_display_alignment(halfword head, halfword tail, halfword prevdepth)
{
    tex_handle_assignments();
  AGAIN:
    switch (cur_cmd) {
        case math_shift_cmd:
            tex_aux_check_second_math_shift();
            break;
        case end_paragraph_cmd:
            tex_get_x_token();
            goto AGAIN;
        default:
            tex_aux_check_display_math_end();
            break;
    }
    tex_pop_nest();
    tex_tail_append(tex_new_penalty_node(pre_display_penalty_par, before_display_penalty_subtype));
    tex_aux_inject_display_skip(above_display_skip_code, above_display_skip_glue);
    node_next(cur_list.tail) = head;
    if (head && tail) {
        cur_list.tail = tail;
    }
    tex_tail_append(tex_new_penalty_node(post_display_penalty_par, after_display_penalty_subtype));
    tex_aux_inject_display_skip(below_display_skip_code, below_display_skip_glue);
    cur_list.prev_depth = prevdepth;
    tex_aux_resume_after_display();
}

/*

    Turning macros into functions brought the |mingw64| bin down from 2548224 to 2511360 bytes but
    not the \LINUX\ one, so I guess |mingw| doesn't inline (yet, in 2020).

*/

static void tex_aux_define_inl_math_parameters(int size, int param, scaled value, int level)
{
    switch (size) {
        case script_size:
            tex_def_math_parameter(script_style, param, value, level, indirect_math_regular, 1);
            tex_def_math_parameter(cramped_script_style, param, value, level, indirect_math_regular, 1);
            break;
        case script_script_size:
            tex_def_math_parameter(script_script_style, param, value, level, indirect_math_regular, 1);
            tex_def_math_parameter(cramped_script_script_style, param, value, level, indirect_math_regular, 1);
            break;
        default:
            tex_def_math_parameter(text_style, param, value, level, indirect_math_regular, 1);
            tex_def_math_parameter(cramped_text_style, param, value, level, indirect_math_regular, 1);
            break;
    }
}

static void tex_aux_define_dis_math_parameters(int size, int param, scaled value, int level)
{
    if (size == text_size) {
        tex_def_math_parameter(display_style, param, value, level, indirect_math_regular, 1);
        tex_def_math_parameter(cramped_display_style, param, value, level, indirect_math_regular, 1);
    }
}

/*tex
    In principle we could save some storage in the format file with:

    \starttyping
    static void tex_aux_define_all_math_parameters(int size, int param, scaled value, int level)
    {
        tex_def_math_parameter(all_math_styles, param, value, level, indirect_math_regular);
    }
    \stoptyping

    and then do this (we also need to move |all_math_styles| up in the enum to keep ranges compact):

    \starttyping
    if (! sa_get_item_8(lmt_math_state.par_head, (param + (math_parameter_max_range * style)), &item1, &item2)) {
        sa_get_item_8(lmt_math_state.par_head, (param + (math_parameter_max_range * all_math_styles)), &item1, &item2);
    }
    \stoptyping

    but in practice we actually get a bit larger file.
*/

static void tex_aux_define_all_math_parameters(int size, int param, scaled value, int level)
{
    switch (size) {
        case script_size:
            tex_def_math_parameter(script_style, param, value, level, indirect_math_regular, 1);
            tex_def_math_parameter(cramped_script_style, param, value, level, indirect_math_regular, 1);
            break;
        case script_script_size:
            tex_def_math_parameter(script_script_style, param, value, level, indirect_math_regular, 1);
            tex_def_math_parameter(cramped_script_script_style, param, value, level, indirect_math_regular, 1);
            break;
        default:
            tex_def_math_parameter(text_style, param, value, level, indirect_math_regular, 1);
            tex_def_math_parameter(cramped_text_style, param, value, level, indirect_math_regular, 1);
            tex_def_math_parameter(display_style, param, value, level, indirect_math_regular, 1);
            tex_def_math_parameter(cramped_display_style, param, value, level, indirect_math_regular, 1);
            break;
    }
}

/*tex

    Here are the math parameters that are font-dependent. Before an mlist is converted to an hlist,
    \TEX\ makes sure that the fonts in family~2 have enough parameters to be math symbol fonts, and
    that the fonts in family~3 have enough parameters to be math extension fonts. The math-symbol
    parameters are referred to by using the following macros, which take a size code as their
    parameter; for example, |num1 (cur_size)| gives the value of the |num1| parameter for the
    current size.

    The math extension parameters have similar macros, but the size code is omitted (since it is
    always |cur_size| when we refer to such parameters).

*/

# define total_mathsy_parameters 22
# define total_mathex_parameters 13

# define mathsy(A,B) font_parameter(tex_fam_fnt(2,A),B)
# define mathex(A,B) font_parameter(tex_fam_fnt(3,A),B)

# define math_x_height(A)          mathsy(A,5)  /*tex height of |x| */
# define math_quad(A)              mathsy(A,6)  /*tex |18mu| */
# define num1(A)                   mathsy(A,8)  /*tex numerator shift-up in display styles */
# define num2(A)                   mathsy(A,9)  /*tex numerator shift-up in non-display, non-|\atop| */
# define num3(A)                   mathsy(A,10) /*tex numerator shift-up in non-display |\atop| */
# define denom1(A)                 mathsy(A,11) /*tex denominator shift-down in display styles */
# define denom2(A)                 mathsy(A,12) /*tex denominator shift-down in non-display styles */
# define sup1(A)                   mathsy(A,13) /*tex superscript shift-up in uncramped display style */
# define sup2(A)                   mathsy(A,14) /*tex superscript shift-up in uncramped non-display */
# define sup3(A)                   mathsy(A,15) /*tex superscript shift-up in cramped styles */
# define sub1(A)                   mathsy(A,16) /*tex subscript shift-down if superscript is absent */
# define sub2(A)                   mathsy(A,17) /*tex subscript shift-down if superscript is present */
# define sup_drop(A)               mathsy(A,18) /*tex superscript baseline below top of large box */
# define sub_drop(A)               mathsy(A,19) /*tex subscript baseline below bottom of large box */
# define delim1(A)                 mathsy(A,20) /*tex size of |\atopwithdelims| delimiters in display styles */
# define delim2(A)                 mathsy(A,21) /*tex size of |\atopwithdelims| delimiters in non-displays */
# define axis_height(A)            mathsy(A,22) /*tex height of fraction lines above the baseline */

# define default_rule_thickness(A) mathex(A,8)  /*tex thickness of |\over| bars */
# define big_operator_spacing1(A)  mathex(A,9)  /*tex minimum clearance above a displayed op */
# define big_operator_spacing2(A)  mathex(A,10) /*tex minimum clearance below a displayed op */
# define big_operator_spacing3(A)  mathex(A,11) /*tex minimum baselineskip above displayed op */
# define big_operator_spacing4(A)  mathex(A,12) /*tex minimum baselineskip below displayed op */
# define big_operator_spacing5(A)  mathex(A,13) /*tex padding above and below displayed limits */

# define math_parameter(a,b) ((font_math_parameter_count(a) >= b) ? font_math_parameter(a,b) : undefined_math_parameter)

static inline scaled tex_aux_get_font_math_parameter(scaled scale, halfword f, int id)
{
    scaled v = math_parameter(f, id);
    if (v == undefined_math_parameter) {
        return v;
    } else {
        return v ? scaledround(0.001 * scale * v) : 0;
    }
}

static inline scaled tex_aux_get_font_math_quantity(scaled scale, halfword v)
{
    return v ? scaledround(0.001 * scale * v) : 0;
}

/*tex
    The next function is called when we define a family, but first we define a few helpers
    for identifying traditional math fonts. Watch the hard codes family check (gone now)!
*/

void tex_fixup_math_parameters(int fam, int size, int f, int level)
{
    scaled scale = tex_get_math_font_scale(f, size);

    if (tracing_math_par > 1) {
        tex_begin_diagnostic();
        tex_print_format("[math: fixing up font, family %i, size %i, font %i, level %i]", fam, size, f, level);
        tex_end_diagnostic();
    }

    /*tex These apply to all: */
//    tex_get_parameter(cur_font_par,  scaled_slant_per_point_code + 1)

    tex_aux_define_all_math_parameters(size, math_parameter_quad,     tex_aux_get_font_math_quantity (scale, font_size(f)),                      level);
    tex_aux_define_all_math_parameters(size, math_parameter_exheight, tex_aux_get_font_math_quantity (scale, font_parameter(f, ex_height_code)), level);
    tex_aux_define_all_math_parameters(size, math_parameter_axis,     tex_aux_get_font_math_parameter(scale, f, AxisHeight),                     level);

    tex_aux_define_all_math_parameters(size, math_parameter_accent_base_height,               tex_aux_get_font_math_parameter(scale, f, AccentBaseHeight),                  level);
    tex_aux_define_all_math_parameters(size, math_parameter_accent_base_depth,                tex_aux_get_font_math_parameter(scale, f, AccentBaseDepth),                   level); /* engine, reserved */
    tex_aux_define_all_math_parameters(size, math_parameter_flattened_accent_base_height,     tex_aux_get_font_math_parameter(scale, f, FlattenedAccentBaseHeight),         level);
    tex_aux_define_all_math_parameters(size, math_parameter_flattened_accent_base_depth,      tex_aux_get_font_math_parameter(scale, f, FlattenedAccentBaseDepth),          level); /* engine, reserved */
    tex_aux_define_all_math_parameters(size, math_parameter_overbar_kern,                     tex_aux_get_font_math_parameter(scale, f, OverbarExtraAscender),              level);
    tex_aux_define_all_math_parameters(size, math_parameter_overbar_rule,                     tex_aux_get_font_math_parameter(scale, f, OverbarRuleThickness),              level);
    tex_aux_define_all_math_parameters(size, math_parameter_overbar_vgap,                     tex_aux_get_font_math_parameter(scale, f, OverbarVerticalGap),                level);
    tex_aux_define_all_math_parameters(size, math_parameter_underbar_kern,                    tex_aux_get_font_math_parameter(scale, f, UnderbarExtraDescender),            level);
    tex_aux_define_all_math_parameters(size, math_parameter_underbar_rule,                    tex_aux_get_font_math_parameter(scale, f, UnderbarRuleThickness ),            level);
    tex_aux_define_all_math_parameters(size, math_parameter_underbar_vgap,                    tex_aux_get_font_math_parameter(scale, f, UnderbarVerticalGap),               level);
    tex_aux_define_all_math_parameters(size, math_parameter_under_delimiter_vgap,             tex_aux_get_font_math_parameter(scale, f, StretchStackGapAboveMin),           level);
    tex_aux_define_all_math_parameters(size, math_parameter_under_delimiter_bgap,             tex_aux_get_font_math_parameter(scale, f, StretchStackBottomShiftDown),       level);
    tex_aux_define_all_math_parameters(size, math_parameter_over_delimiter_vgap,              tex_aux_get_font_math_parameter(scale, f, StretchStackGapBelowMin),           level);
    tex_aux_define_all_math_parameters(size, math_parameter_over_delimiter_bgap,              tex_aux_get_font_math_parameter(scale, f, StretchStackTopShiftUp),            level);
    tex_aux_define_all_math_parameters(size, math_parameter_radical_kern,                     tex_aux_get_font_math_parameter(scale, f, RadicalExtraAscender),              level);
    tex_aux_define_all_math_parameters(size, math_parameter_radical_rule,                     tex_aux_get_font_math_parameter(scale, f, RadicalRuleThickness),              level);
    tex_aux_define_all_math_parameters(size, math_parameter_radical_degree_before,            tex_aux_get_font_math_parameter(scale, f, RadicalKernBeforeDegree),           level);
    tex_aux_define_all_math_parameters(size, math_parameter_radical_degree_after,             tex_aux_get_font_math_parameter(scale, f, RadicalKernAfterDegree),            level);
    tex_aux_define_all_math_parameters(size, math_parameter_subscript_shift_drop,             tex_aux_get_font_math_parameter(scale, f, SubscriptBaselineDropMin),          level);
    tex_aux_define_all_math_parameters(size, math_parameter_superscript_shift_drop,           tex_aux_get_font_math_parameter(scale, f, SuperscriptBaselineDropMax),        level);
    tex_aux_define_all_math_parameters(size, math_parameter_subscript_shift_down,             tex_aux_get_font_math_parameter(scale, f, SubscriptShiftDown),                level);
    tex_aux_define_all_math_parameters(size, math_parameter_prime_shift_drop,                 tex_aux_get_font_math_parameter(scale, f, PrimeBaselineDropMax),              level); /* engine, default 0 */
    tex_aux_define_all_math_parameters(size, math_parameter_subscript_top_max,                tex_aux_get_font_math_parameter(scale, f, SubscriptTopMax),                   level);
    tex_aux_define_all_math_parameters(size, math_parameter_superscript_bottom_min,           tex_aux_get_font_math_parameter(scale, f, SuperscriptBottomMin),              level);
    tex_aux_define_all_math_parameters(size, math_parameter_superscript_subscript_bottom_max, tex_aux_get_font_math_parameter(scale, f, SuperscriptBottomMaxWithSubscript), level);
    tex_aux_define_all_math_parameters(size, math_parameter_subscript_superscript_vgap,       tex_aux_get_font_math_parameter(scale, f, SubSuperscriptGapMin),              level);
    tex_aux_define_all_math_parameters(size, math_parameter_limit_above_vgap,                 tex_aux_get_font_math_parameter(scale, f, UpperLimitGapMin),                  level);
    tex_aux_define_all_math_parameters(size, math_parameter_limit_above_bgap,                 tex_aux_get_font_math_parameter(scale, f, UpperLimitBaselineRiseMin),         level);
    tex_aux_define_all_math_parameters(size, math_parameter_limit_below_vgap,                 tex_aux_get_font_math_parameter(scale, f, LowerLimitGapMin),                  level);
    tex_aux_define_all_math_parameters(size, math_parameter_limit_below_bgap,                 tex_aux_get_font_math_parameter(scale, f, LowerLimitBaselineDropMin),         level);
    tex_aux_define_all_math_parameters(size, math_parameter_nolimit_sub_factor,               tex_aux_get_font_math_parameter(scale, f, NoLimitSubFactor),                  level); /* engine, default 0 */
    tex_aux_define_all_math_parameters(size, math_parameter_nolimit_sup_factor,               tex_aux_get_font_math_parameter(scale, f, NoLimitSupFactor),                  level); /* engine, default 0 */
    tex_aux_define_all_math_parameters(size, math_parameter_skewed_fraction_hgap,             tex_aux_get_font_math_parameter(scale, f, SkewedFractionHorizontalGap),       level);
    tex_aux_define_all_math_parameters(size, math_parameter_skewed_fraction_vgap,             tex_aux_get_font_math_parameter(scale, f, SkewedFractionVerticalGap),         level);
    tex_aux_define_all_math_parameters(size, math_parameter_space_before_script,              tex_aux_get_font_math_parameter(scale, f, SpaceBeforeScript),                 level); /* engine, default 0 */
    tex_aux_define_all_math_parameters(size, math_parameter_space_between_script,             tex_aux_get_font_math_parameter(scale, f, SpaceBetweenScript),                level); /* engine, default 0 */
    tex_aux_define_all_math_parameters(size, math_parameter_space_after_script,               tex_aux_get_font_math_parameter(scale, f, SpaceAfterScript),                  level);
    tex_aux_define_all_math_parameters(size, math_parameter_connector_overlap_min,            tex_aux_get_font_math_parameter(scale, f, MinConnectorOverlap),               level); /* engine, default 0 */
    tex_aux_define_all_math_parameters(size, math_parameter_superscript_snap,                 tex_aux_get_font_math_parameter(scale, f, SuperscriptSnap),                   level);
    tex_aux_define_all_math_parameters(size, math_parameter_subscript_snap,                   tex_aux_get_font_math_parameter(scale, f, SubscriptSnap),                     level); /* engine, default 0 */
    tex_aux_define_all_math_parameters(size, math_parameter_fraction_rule,                    tex_aux_get_font_math_parameter(scale, f, FractionRuleThickness),             level); /* engine, default 0 */

    tex_aux_define_all_math_parameters(size, math_parameter_prime_space_after,                  tex_aux_get_font_math_parameter(scale, f, PrimeSpaceAfter),                 level); /* engine, default 0 */
    tex_aux_define_all_math_parameters(size, math_parameter_skewed_delimiter_tolerance,         tex_aux_get_font_math_parameter(scale, f, SkewedDelimiterTolerance),        level); /* engine, default 0 */
    tex_aux_define_all_math_parameters(size, math_parameter_accent_top_shift_up,                tex_aux_get_font_math_parameter(scale, f, AccentTopShiftUp),                level); /* engine, undefined */
    tex_aux_define_all_math_parameters(size, math_parameter_accent_bottom_shift_down,           tex_aux_get_font_math_parameter(scale, f, AccentBottomShiftDown),           level); /* engine, undefined */
    tex_aux_define_all_math_parameters(size, math_parameter_accent_top_overshoot,               tex_aux_get_font_math_parameter(scale, f, AccentTopOvershoot),              level); /* engine, default 0 */
    tex_aux_define_all_math_parameters(size, math_parameter_accent_bottom_overshoot,            tex_aux_get_font_math_parameter(scale, f, AccentBottomOvershoot),           level); /* engine, default 0 */
    tex_aux_define_all_math_parameters(size, math_parameter_accent_superscript_drop,            tex_aux_get_font_math_parameter(scale, f, AccentSuperscriptDrop),           level); /* engine, default 0 */
    tex_aux_define_all_math_parameters(size, math_parameter_accent_superscript_percent,         tex_aux_get_font_math_parameter(scale, f, AccentSuperscriptPercent),        level); /* engine, default 0 */
    tex_aux_define_all_math_parameters(size, math_parameter_accent_extend_margin,               tex_aux_get_font_math_parameter(scale, f, AccentExtendMargin),              level); /* engine, undefined */
    tex_aux_define_all_math_parameters(size, math_parameter_flattened_accent_top_shift_up,      tex_aux_get_font_math_parameter(scale, f, FlattenedAccentTopShiftUp),       level); /* engine, undefined */
    tex_aux_define_all_math_parameters(size, math_parameter_flattened_accent_bottom_shift_down, tex_aux_get_font_math_parameter(scale, f, FlattenedAccentBottomShiftDown),  level); /* engine, undefined */
    tex_aux_define_all_math_parameters(size, math_parameter_delimiter_extend_margin,            tex_aux_get_font_math_parameter(scale, f, DelimiterExtendMargin),           level); /* engine, undefined */
    tex_aux_define_all_math_parameters(size, math_parameter_radical_extensible_after,           tex_aux_get_font_math_parameter(scale, f, RadicalKernAfterExtensible),      level); /* engine, undefined */
    tex_aux_define_all_math_parameters(size, math_parameter_radical_extensible_before,          tex_aux_get_font_math_parameter(scale, f, RadicalKernBeforeExtensible),     level); /* engine, undefined */

    /*tex Percentages: */

    tex_aux_define_all_math_parameters(size, math_parameter_prime_raise,          math_parameter(f, PrimeRaisePercent),               level); /* engine, default 0 */
    tex_aux_define_all_math_parameters(size, math_parameter_prime_raise_composed, math_parameter(f, PrimeRaiseComposedPercent),       level); /* engine, default 0 */
    tex_aux_define_all_math_parameters(size, math_parameter_radical_degree_raise, math_parameter(f, RadicalDegreeBottomRaisePercent), level);

    /*tex Not all are official \OPENTYPE: */

    tex_aux_define_all_math_parameters(size, math_parameter_x_scale, scaling_factor, level);
    tex_aux_define_all_math_parameters(size, math_parameter_y_scale, scaling_factor, level);

    /*tex Most are zero and have to be set at by the macro package (if at all):. */

    tex_aux_define_all_math_parameters(size, math_parameter_limit_above_kern,              0, level);
    tex_aux_define_all_math_parameters(size, math_parameter_limit_below_kern,              0, level);
    tex_aux_define_all_math_parameters(size, math_parameter_extra_superscript_shift,       0, level);
    tex_aux_define_all_math_parameters(size, math_parameter_extra_subscript_shift,         0, level);
    tex_aux_define_all_math_parameters(size, math_parameter_extra_superprescript_shift,    0, level);
    tex_aux_define_all_math_parameters(size, math_parameter_extra_subprescript_shift,      0, level);
    tex_aux_define_all_math_parameters(size, math_parameter_rule_height,                   0, level);
    tex_aux_define_all_math_parameters(size, math_parameter_rule_depth,                    0, level);
    tex_aux_define_all_math_parameters(size, math_parameter_extra_superscript_space,       0, level);
    tex_aux_define_all_math_parameters(size, math_parameter_extra_subscript_space,         0, level);
    tex_aux_define_all_math_parameters(size, math_parameter_extra_superprescript_space,    0, level);
    tex_aux_define_all_math_parameters(size, math_parameter_extra_subprescript_space,      0, level);

    /*tex A special one: */

    if (math_parameter(f, SubscriptShiftDownWithSuperscript) != undefined_math_parameter) { /* engine */
        tex_aux_define_all_math_parameters(size, math_parameter_subscript_superscript_shift_down, tex_aux_get_font_math_parameter(scale, f, SubscriptShiftDownWithSuperscript), level);
    } else {
        tex_aux_define_all_math_parameters(size, math_parameter_subscript_superscript_shift_down, tex_aux_get_font_math_parameter(scale, f, SubscriptShiftDown),                level);
    }

    /*tex These differentiate between display and inline: */

    tex_aux_define_dis_math_parameters(size, math_parameter_delimiter_percent,   math_parameter(f, DelimiterDisplayPercent), level); /* engine, undefined */
    tex_aux_define_inl_math_parameters(size, math_parameter_delimiter_percent,   math_parameter(f, DelimiterPercent),        level); /* engine, undefined */

    tex_aux_define_dis_math_parameters(size, math_parameter_operator_size,       tex_aux_get_font_math_parameter(scale, f, DisplayOperatorMinHeight),                 level);
    tex_aux_define_inl_math_parameters(size, math_parameter_radical_vgap,        tex_aux_get_font_math_parameter(scale, f, RadicalVerticalGap),                       level);
    tex_aux_define_dis_math_parameters(size, math_parameter_radical_vgap,        tex_aux_get_font_math_parameter(scale, f, RadicalDisplayStyleVerticalGap),           level);
    tex_aux_define_inl_math_parameters(size, math_parameter_stack_num_up,        tex_aux_get_font_math_parameter(scale, f, StackTopShiftUp),                          level);
    tex_aux_define_dis_math_parameters(size, math_parameter_stack_num_up,        tex_aux_get_font_math_parameter(scale, f, StackTopDisplayStyleShiftUp),              level);
    tex_aux_define_inl_math_parameters(size, math_parameter_stack_denom_down,    tex_aux_get_font_math_parameter(scale, f, StackBottomShiftDown),                     level);
    tex_aux_define_dis_math_parameters(size, math_parameter_stack_denom_down,    tex_aux_get_font_math_parameter(scale, f, StackBottomDisplayStyleShiftDown),         level);
    tex_aux_define_inl_math_parameters(size, math_parameter_stack_vgap,          tex_aux_get_font_math_parameter(scale, f, StackGapMin),                              level);
    tex_aux_define_dis_math_parameters(size, math_parameter_stack_vgap,          tex_aux_get_font_math_parameter(scale, f, StackDisplayStyleGapMin),                  level);
    tex_aux_define_inl_math_parameters(size, math_parameter_fraction_num_vgap,   tex_aux_get_font_math_parameter(scale, f, FractionNumeratorGapMin),                  level);
    tex_aux_define_dis_math_parameters(size, math_parameter_fraction_num_vgap,   tex_aux_get_font_math_parameter(scale, f, FractionNumeratorDisplayStyleGapMin),      level);
    tex_aux_define_inl_math_parameters(size, math_parameter_fraction_num_up,     tex_aux_get_font_math_parameter(scale, f, FractionNumeratorShiftUp),                 level);
    tex_aux_define_dis_math_parameters(size, math_parameter_fraction_num_up,     tex_aux_get_font_math_parameter(scale, f, FractionNumeratorDisplayStyleShiftUp),     level);
    tex_aux_define_inl_math_parameters(size, math_parameter_fraction_denom_vgap, tex_aux_get_font_math_parameter(scale, f, FractionDenominatorGapMin),                level);
    tex_aux_define_dis_math_parameters(size, math_parameter_fraction_denom_vgap, tex_aux_get_font_math_parameter(scale, f, FractionDenominatorDisplayStyleGapMin),    level);
    tex_aux_define_inl_math_parameters(size, math_parameter_fraction_denom_down, tex_aux_get_font_math_parameter(scale, f, FractionDenominatorShiftDown),             level);
    tex_aux_define_dis_math_parameters(size, math_parameter_fraction_denom_down, tex_aux_get_font_math_parameter(scale, f, FractionDenominatorDisplayStyleShiftDown), level);
    tex_aux_define_inl_math_parameters(size, math_parameter_fraction_del_size,   tex_aux_get_font_math_parameter(scale, f, FractionDelimiterSize),                    level); /* engine, undefined */
    tex_aux_define_dis_math_parameters(size, math_parameter_fraction_del_size,   tex_aux_get_font_math_parameter(scale, f, FractionDelimiterDisplayStyleSize),        level); /* engine, undefined */

    tex_aux_define_dis_math_parameters(size, math_parameter_delimiter_shortfall, tex_aux_get_font_math_parameter(scale, f, DelimiterDisplayShortfall),                level); /* engine, undefined */
    tex_aux_define_inl_math_parameters(size, math_parameter_delimiter_shortfall, tex_aux_get_font_math_parameter(scale, f, DelimiterShortfall),                       level); /* engine, undefined */

    /*tex A few more specials: */

    switch (size) {
        case script_size:
            tex_def_math_parameter(script_style,         math_parameter_superscript_shift_up, tex_aux_get_font_math_parameter(scale, f, SuperscriptShiftUp),        level, indirect_math_regular, 1);
            tex_def_math_parameter(cramped_script_style, math_parameter_superscript_shift_up, tex_aux_get_font_math_parameter(scale, f, SuperscriptShiftUpCramped), level, indirect_math_regular, 1);
            tex_def_math_parameter(script_style,         math_parameter_prime_shift_up,       tex_aux_get_font_math_parameter(scale, f, PrimeShiftUp),              level, indirect_math_regular, 1); /* engine, default 0 */
            tex_def_math_parameter(cramped_script_style, math_parameter_prime_shift_up,       tex_aux_get_font_math_parameter(scale, f, PrimeShiftUpCramped),       level, indirect_math_regular, 1); /* engine, default 0 */
            break;
        case script_script_size:
            tex_def_math_parameter(script_script_style,         math_parameter_superscript_shift_up, tex_aux_get_font_math_parameter(scale, f, SuperscriptShiftUp),        level, indirect_math_regular, 1);
            tex_def_math_parameter(cramped_script_script_style, math_parameter_superscript_shift_up, tex_aux_get_font_math_parameter(scale, f, SuperscriptShiftUpCramped), level, indirect_math_regular, 1);
            tex_def_math_parameter(script_script_style,         math_parameter_prime_shift_up,       tex_aux_get_font_math_parameter(scale, f, PrimeShiftUp),              level, indirect_math_regular, 1); /* engine, default 0 */
            tex_def_math_parameter(cramped_script_script_style, math_parameter_prime_shift_up,       tex_aux_get_font_math_parameter(scale, f, PrimeShiftUpCramped),       level, indirect_math_regular, 1); /* engine, default 0 */
            break;
        default:
            tex_def_math_parameter(display_style,         math_parameter_superscript_shift_up, tex_aux_get_font_math_parameter(scale, f, SuperscriptShiftUp),        level, indirect_math_regular, 1);
            tex_def_math_parameter(cramped_display_style, math_parameter_superscript_shift_up, tex_aux_get_font_math_parameter(scale, f, SuperscriptShiftUpCramped), level, indirect_math_regular, 1);
            tex_def_math_parameter(text_style,            math_parameter_superscript_shift_up, tex_aux_get_font_math_parameter(scale, f, SuperscriptShiftUp),        level, indirect_math_regular, 1);
            tex_def_math_parameter(cramped_text_style,    math_parameter_superscript_shift_up, tex_aux_get_font_math_parameter(scale, f, SuperscriptShiftUpCramped), level, indirect_math_regular, 1);
            tex_def_math_parameter(display_style,         math_parameter_prime_shift_up,       tex_aux_get_font_math_parameter(scale, f, PrimeShiftUp),              level, indirect_math_regular, 1); /* engine, default 0 */
            tex_def_math_parameter(cramped_display_style, math_parameter_prime_shift_up,       tex_aux_get_font_math_parameter(scale, f, PrimeShiftUpCramped),       level, indirect_math_regular, 1); /* engine, default 0 */
            tex_def_math_parameter(text_style,            math_parameter_prime_shift_up,       tex_aux_get_font_math_parameter(scale, f, PrimeShiftUp),              level, indirect_math_regular, 1); /* engine, default 0 */
            tex_def_math_parameter(cramped_text_style,    math_parameter_prime_shift_up,       tex_aux_get_font_math_parameter(scale, f, PrimeShiftUpCramped),       level, indirect_math_regular, 1); /* engine, default 0 */
            break;
    }

}

/*tex

    There is some trickery here. The values are actually pointers and in \LUATEX\ the predefined
    muglue ones are small numbers that are way below the normal node values. So, they are kind
    of save signals. However, in \LUAMETATEX\ we use zero based internal codes (because that is
    nicer for the interface.

*/

void tex_set_display_styles(halfword code, halfword value, halfword level, halfword indirect)
{
    tex_def_math_parameter(display_style,         code, value, level, indirect, 0);
    tex_def_math_parameter(cramped_display_style, code, value, level, indirect, 0);
}

void tex_set_text_styles(halfword code, halfword value, halfword level, halfword indirect)
{
    tex_def_math_parameter(text_style,         code, value, level, indirect, 0);
    tex_def_math_parameter(cramped_text_style, code, value, level, indirect, 0);
}

void tex_set_main_styles(halfword code, halfword value, halfword level, halfword indirect)
{
    for (int style = display_style; style <= cramped_text_style; style++) {
        tex_def_math_parameter(style, code, value, level, indirect, 0);
    }
}

void tex_set_script_styles(halfword code, halfword value, halfword level, halfword indirect)
{
    tex_def_math_parameter(script_style,         code, value, level, indirect, 0);
    tex_def_math_parameter(cramped_script_style, code, value, level, indirect, 0);
}

void tex_set_script_script_styles(halfword code, halfword value, halfword level, halfword indirect)
{
    tex_def_math_parameter(script_script_style,         code, value, level, indirect, 0);
    tex_def_math_parameter(cramped_script_script_style, code, value, level, indirect, 0);
}

void tex_set_all_styles(halfword code, halfword value, halfword level, halfword indirect)
{
    for (int style = display_style; style <= cramped_script_script_style; style++) {
        tex_def_math_parameter(style, code, value, level, indirect, 0);
    }
}

void tex_set_uncramped_styles(halfword code, halfword value, halfword level, halfword indirect)
{
    for (int style = display_style; style <= script_script_style; style += 2) {
        tex_def_math_parameter(style, code, value, level, indirect, 0);
    }
}

void tex_set_cramped_styles(halfword code, halfword value, halfword level, halfword indirect)
{
    for (int style = cramped_display_style; style <= cramped_script_script_style; style += 2) {
        tex_def_math_parameter(style, code, value, level, indirect, 0);
    }
}

void tex_set_split_styles(halfword code, halfword value, halfword level, halfword indirect)
{
    tex_set_display_styles      (code, value, level, indirect);
    tex_set_text_styles         (code, value, level, indirect);
    tex_set_script_styles       (code, 0,     level, indirect);
    tex_set_script_script_styles(code, 0,     level, indirect);
}

void tex_set_unsplit_styles(halfword code, halfword value, halfword level, halfword indirect)
{
    tex_set_script_styles       (code, value, level, indirect);
    tex_set_script_script_styles(code, value, level, indirect);
}

void tex_reset_all_styles(halfword level)
{
    for (int code = math_parameter_atom_pairs_first; code <= math_parameter_atom_pairs_last; code++) {
        tex_set_all_styles(code, zero_muskip_code, level, indirect_math_unset);
    }
}

static inline halfword tex_aux_math_class_default(halfword mathclass) {
    return (mathclass << 24) + (mathclass << 16) + (mathclass << 8) + mathclass;
}

static inline void tex_set_math_class_default(halfword mathclass, halfword parent, halfword options)
{
    tex_word_define(0, internal_integer_location(first_math_class_code   + mathclass), tex_aux_math_class_default(parent));
    tex_word_define(0, internal_integer_location(first_math_atom_code    + mathclass), tex_aux_math_class_default(mathclass));
    tex_word_define(0, internal_integer_location(first_math_options_code + mathclass), options);
    tex_word_define(0, internal_integer_location(first_math_parent_code  + mathclass), tex_aux_math_class_default(mathclass));
}

static void tex_aux_set_math_atom_rule(halfword left, halfword right, halfword newleft, halfword newright)
{
    tex_set_all_styles(math_parameter_rules_pair(left, right), (newleft << 16) + newright, level_one, indirect_math_regular);
}

/*tex

    Originally a penalty of 10000 signaled that no penalty has to be included but because we want
    to control penalties in nested sequences (like open and close bound sequences) we need to be
    able to go up (multiply by a factor) or down (divide by a factor). Therefore we need to be able
    to set a penalty to 10000 as a start. so that it will be ignored unless we apply a factor. For
    that reason we now use 10001 instead.

*/

void tex_initialize_math_spacing(void)
{

    for (int mathclass = 0; mathclass <= max_math_class_code; mathclass++) {
        tex_set_math_class_default(mathclass, mathclass, no_class_options);
        /*tex We do this here as there is no real need for yet another initializer. */
        tex_word_define(0, internal_integer_location(first_math_pre_penalty_code  + mathclass), math_default_penalty);
        tex_word_define(0, internal_integer_location(first_math_post_penalty_code + mathclass), math_default_penalty);
        tex_word_define(0, internal_integer_location(first_math_display_pre_penalty_code  + mathclass), math_default_penalty);
        tex_word_define(0, internal_integer_location(first_math_display_post_penalty_code + mathclass), math_default_penalty);
    }

    tex_reset_all_styles(level_one);

    tex_set_math_class_default(ordinary_noad_subtype,    ordinary_noad_subtype,    no_italic_correction_class_option |
                                                                                   check_ligature_class_option |
                                                                                   check_kern_pair_class_option |
                                                                                   flatten_class_option);
    tex_set_math_class_default(operator_noad_subtype,    operator_noad_subtype,    check_ligature_class_option |
                                                                                   check_kern_pair_class_option);
    tex_set_math_class_default(binary_noad_subtype,      binary_noad_subtype,      no_italic_correction_class_option |
                                                                                   check_ligature_class_option |
                                                                                   check_kern_pair_class_option |
                                                                                   flatten_class_option);
    tex_set_math_class_default(relation_noad_subtype,    relation_noad_subtype,    no_italic_correction_class_option |
                                                                                   check_ligature_class_option |
                                                                                   check_kern_pair_class_option |
                                                                                   flatten_class_option |
                                                                                   omit_penalty_class_option);
    tex_set_math_class_default(open_noad_subtype,        open_noad_subtype,        no_italic_correction_class_option |
                                                                                /* open_fence_class_option | */
                                                                                   check_ligature_class_option |
                                                                                   check_kern_pair_class_option);
    tex_set_math_class_default(close_noad_subtype,       close_noad_subtype,       no_italic_correction_class_option |
                                                                                /* close_fence_class_option | */
                                                                                   check_ligature_class_option |
                                                                                   check_kern_pair_class_option);
    tex_set_math_class_default(punctuation_noad_subtype, punctuation_noad_subtype, no_italic_correction_class_option |
                                                                                   check_ligature_class_option |
                                                                                   check_kern_pair_class_option |
                                                                                   flatten_class_option);
    tex_set_math_class_default(variable_noad_subtype,    ordinary_noad_subtype,    no_italic_correction_class_option);
    tex_set_math_class_default(active_noad_subtype,      ordinary_noad_subtype,    no_italic_correction_class_option);
    tex_set_math_class_default(inner_noad_subtype,       inner_noad_subtype,       flatten_class_option);
    tex_set_math_class_default(under_noad_subtype,       ordinary_noad_subtype,    no_class_options);
    tex_set_math_class_default(over_noad_subtype,        ordinary_noad_subtype,    no_class_options);
    tex_set_math_class_default(fraction_noad_subtype,    ordinary_noad_subtype,    no_class_options);
    tex_set_math_class_default(radical_noad_subtype,     ordinary_noad_subtype,    no_class_options);
    tex_set_math_class_default(middle_noad_subtype,      open_noad_subtype,        no_italic_correction_class_option); /* | middle_fence_class_option= */
    tex_set_math_class_default(accent_noad_subtype,      ordinary_noad_subtype,    no_class_options);
    tex_set_math_class_default(fenced_noad_subtype,      inner_noad_subtype   ,    no_class_options);
    tex_set_math_class_default(ghost_noad_subtype,       ordinary_noad_subtype,    no_class_options);
    tex_set_math_class_default(vcenter_noad_subtype,     ordinary_noad_subtype,    no_class_options);
    tex_set_math_class_default(prime_noad_subtype,       ordinary_noad_subtype,    no_class_options);

    tex_aux_set_math_atom_rule(math_begin_class,         binary_noad_subtype,      ordinary_noad_subtype,    ordinary_noad_subtype);
    tex_aux_set_math_atom_rule(binary_noad_subtype,      math_end_class,           ordinary_noad_subtype,    ordinary_noad_subtype);

    tex_aux_set_math_atom_rule(binary_noad_subtype,      binary_noad_subtype,      binary_noad_subtype,      ordinary_noad_subtype);
    tex_aux_set_math_atom_rule(operator_noad_subtype,    binary_noad_subtype,      operator_noad_subtype,    ordinary_noad_subtype);
    tex_aux_set_math_atom_rule(open_noad_subtype,        binary_noad_subtype,      open_noad_subtype,        ordinary_noad_subtype);
    tex_aux_set_math_atom_rule(punctuation_noad_subtype, binary_noad_subtype,      punctuation_noad_subtype, ordinary_noad_subtype);
    tex_aux_set_math_atom_rule(relation_noad_subtype,    binary_noad_subtype,      relation_noad_subtype,    ordinary_noad_subtype);

    tex_aux_set_math_atom_rule(binary_noad_subtype,      close_noad_subtype,       ordinary_noad_subtype,    close_noad_subtype);
    tex_aux_set_math_atom_rule(binary_noad_subtype,      punctuation_noad_subtype, ordinary_noad_subtype,    punctuation_noad_subtype);
    tex_aux_set_math_atom_rule(binary_noad_subtype,      relation_noad_subtype,    ordinary_noad_subtype,    relation_noad_subtype);

    tex_aux_set_math_atom_rule(relation_noad_subtype,    close_noad_subtype,       ordinary_noad_subtype,    close_noad_subtype);
    tex_aux_set_math_atom_rule(relation_noad_subtype,    punctuation_noad_subtype, ordinary_noad_subtype,    punctuation_noad_subtype);

    /* */

//    math_parameter_spacing_pair(ordinary_noad_subtype,ordinary_noad_subtype)

    tex_set_all_styles   (math_parameter_spacing_pair(ordinary_noad_subtype,    operator_noad_subtype),    thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(ordinary_noad_subtype,    binary_noad_subtype),      med_muskip_code,   level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(ordinary_noad_subtype,    relation_noad_subtype),    thick_muskip_code, level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(ordinary_noad_subtype,    inner_noad_subtype),       thin_muskip_code,  level_one, indirect_math_regular);

    tex_set_all_styles   (math_parameter_spacing_pair(operator_noad_subtype,    ordinary_noad_subtype),    thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_spacing_pair(operator_noad_subtype,    operator_noad_subtype),    thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(operator_noad_subtype,    relation_noad_subtype),    thick_muskip_code, level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(operator_noad_subtype,    inner_noad_subtype),       thin_muskip_code,  level_one, indirect_math_regular);

    tex_set_all_styles   (math_parameter_spacing_pair(operator_noad_subtype,    fraction_noad_subtype),    thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_spacing_pair(operator_noad_subtype,    radical_noad_subtype),     thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_spacing_pair(fraction_noad_subtype,    operator_noad_subtype),    thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_spacing_pair(radical_noad_subtype,     operator_noad_subtype),    thin_muskip_code,  level_one, indirect_math_regular);

    tex_set_split_styles (math_parameter_spacing_pair(binary_noad_subtype,      ordinary_noad_subtype),    med_muskip_code,   level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(binary_noad_subtype,      operator_noad_subtype),    med_muskip_code,   level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(binary_noad_subtype,      open_noad_subtype),        med_muskip_code,   level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(binary_noad_subtype,      inner_noad_subtype),       med_muskip_code,   level_one, indirect_math_regular);

    tex_set_split_styles (math_parameter_spacing_pair(binary_noad_subtype,      middle_noad_subtype),      med_muskip_code,   level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(binary_noad_subtype,      fraction_noad_subtype),    med_muskip_code,   level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(binary_noad_subtype,      radical_noad_subtype),     med_muskip_code,   level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(middle_noad_subtype,      binary_noad_subtype),      med_muskip_code,   level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(fraction_noad_subtype,    binary_noad_subtype),      med_muskip_code,   level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(radical_noad_subtype,     binary_noad_subtype),      med_muskip_code,   level_one, indirect_math_regular);

    tex_set_split_styles (math_parameter_spacing_pair(relation_noad_subtype,    ordinary_noad_subtype),    thick_muskip_code, level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(relation_noad_subtype,    operator_noad_subtype),    thick_muskip_code, level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(relation_noad_subtype,    open_noad_subtype),        thick_muskip_code, level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(relation_noad_subtype,    inner_noad_subtype),       thick_muskip_code, level_one, indirect_math_regular);

    tex_set_split_styles (math_parameter_spacing_pair(relation_noad_subtype,    middle_noad_subtype),      thick_muskip_code, level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(relation_noad_subtype,    fraction_noad_subtype),    thick_muskip_code, level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(relation_noad_subtype,    radical_noad_subtype),     thick_muskip_code, level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(middle_noad_subtype,      relation_noad_subtype),    thick_muskip_code, level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(fraction_noad_subtype,    relation_noad_subtype),    thick_muskip_code, level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(radical_noad_subtype,     relation_noad_subtype),    thick_muskip_code, level_one, indirect_math_regular);

    tex_set_all_styles   (math_parameter_spacing_pair(close_noad_subtype,       operator_noad_subtype),    thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(close_noad_subtype,       binary_noad_subtype),      med_muskip_code,   level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(close_noad_subtype,       relation_noad_subtype),    thick_muskip_code, level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(close_noad_subtype,       inner_noad_subtype),       thin_muskip_code,  level_one, indirect_math_regular);

    tex_set_split_styles (math_parameter_spacing_pair(punctuation_noad_subtype, ordinary_noad_subtype),    thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(punctuation_noad_subtype, operator_noad_subtype),    thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(punctuation_noad_subtype, relation_noad_subtype),    thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(punctuation_noad_subtype, open_noad_subtype),        thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(punctuation_noad_subtype, close_noad_subtype),       thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(punctuation_noad_subtype, punctuation_noad_subtype), thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(punctuation_noad_subtype, inner_noad_subtype),       thin_muskip_code,  level_one, indirect_math_regular);

    tex_set_split_styles (math_parameter_spacing_pair(punctuation_noad_subtype, fraction_noad_subtype),    thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(punctuation_noad_subtype, middle_noad_subtype),      thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(punctuation_noad_subtype, radical_noad_subtype),     thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(fraction_noad_subtype,    punctuation_noad_subtype), thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(middle_noad_subtype,      punctuation_noad_subtype), thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(radical_noad_subtype,     punctuation_noad_subtype), thin_muskip_code,  level_one, indirect_math_regular);

    tex_set_split_styles (math_parameter_spacing_pair(inner_noad_subtype,       ordinary_noad_subtype),    thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_spacing_pair(inner_noad_subtype,       operator_noad_subtype),    thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(inner_noad_subtype,       binary_noad_subtype),      med_muskip_code,   level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(inner_noad_subtype,       relation_noad_subtype),    thick_muskip_code, level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(inner_noad_subtype,       open_noad_subtype),        thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(inner_noad_subtype,       punctuation_noad_subtype), thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(inner_noad_subtype,       inner_noad_subtype),       thin_muskip_code,  level_one, indirect_math_regular);

    tex_set_split_styles (math_parameter_spacing_pair(inner_noad_subtype,       middle_noad_subtype),      thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(fraction_noad_subtype,    inner_noad_subtype),       thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(radical_noad_subtype,     inner_noad_subtype),       thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(middle_noad_subtype,      inner_noad_subtype),       thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(fraction_noad_subtype,    inner_noad_subtype),       thin_muskip_code,  level_one, indirect_math_regular);
    tex_set_split_styles (math_parameter_spacing_pair(radical_noad_subtype,     inner_noad_subtype),       thin_muskip_code,  level_one, indirect_math_regular);

    /* */

    tex_set_all_styles   (math_parameter_x_scale, scaling_factor, level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_y_scale, scaling_factor, level_one, indirect_math_regular);

    /* could be initialize_math_defaults */

    tex_set_all_styles   (math_parameter_over_line_variant,       math_variant_presets[math_cramped_style_variant],      level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_under_line_variant,      math_variant_presets[math_normal_style_variant],       level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_over_delimiter_variant,  math_variant_presets[math_small_style_variant],        level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_under_delimiter_variant, math_variant_presets[math_small_style_variant],        level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_delimiter_over_variant,  math_variant_presets[math_normal_style_variant],       level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_delimiter_under_variant, math_variant_presets[math_normal_style_variant],       level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_h_extensible_variant,    math_variant_presets[math_normal_style_variant],       level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_v_extensible_variant,    math_variant_presets[math_normal_style_variant],       level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_fraction_variant,        math_variant_presets[math_cramped_style_variant],      level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_radical_variant,         math_variant_presets[math_cramped_style_variant],      level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_degree_variant,          math_variant_presets[math_double_superscript_variant], level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_accent_variant,          math_variant_presets[math_cramped_style_variant],      level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_top_accent_variant,      math_variant_presets[math_cramped_style_variant],      level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_bottom_accent_variant,   math_variant_presets[math_cramped_style_variant],      level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_overlay_accent_variant,  math_variant_presets[math_cramped_style_variant],      level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_numerator_variant,       math_variant_presets[math_numerator_style_variant],    level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_denominator_variant,     math_variant_presets[math_denominator_style_variant],  level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_superscript_variant,     math_variant_presets[math_superscript_style_variant],  level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_subscript_variant,       math_variant_presets[math_subscript_style_variant],    level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_prime_variant,           math_variant_presets[math_superscript_style_variant],  level_one, indirect_math_regular);
    tex_set_all_styles   (math_parameter_stack_variant,           math_variant_presets[math_numerator_style_variant],    level_one, indirect_math_regular);
}

/*tex

    This needs to be called just at the start of |mlist_to_hlist|, for backward compatibility with
    |\scriptspace|.

*/

void tex_finalize_math_parameters(void)
{
    int saved_trace = tracing_assigns_par;
    tracing_assigns_par = 0;
    if (tex_get_math_parameter(display_style,               math_parameter_space_after_script, NULL) == undefined_math_parameter) {
        tex_def_math_parameter(display_style,               math_parameter_space_after_script, script_space_par, level_one, indirect_math_regular, 1);
        tex_def_math_parameter(text_style,                  math_parameter_space_after_script, script_space_par, level_one, indirect_math_regular, 1);
        tex_def_math_parameter(script_style,                math_parameter_space_after_script, script_space_par, level_one, indirect_math_regular, 1);
        tex_def_math_parameter(script_script_style,         math_parameter_space_after_script, script_space_par, level_one, indirect_math_regular, 1);
        tex_def_math_parameter(cramped_display_style,       math_parameter_space_after_script, script_space_par, level_one, indirect_math_regular, 1);
        tex_def_math_parameter(cramped_text_style,          math_parameter_space_after_script, script_space_par, level_one, indirect_math_regular, 1);
        tex_def_math_parameter(cramped_script_style,        math_parameter_space_after_script, script_space_par, level_one, indirect_math_regular, 1);
        tex_def_math_parameter(cramped_script_script_style, math_parameter_space_after_script, script_space_par, level_one, indirect_math_regular, 1);
    }
    tracing_assigns_par = saved_trace;
}

static void tex_aux_math_parameter_error(int style, int param, const char *name)
{
    if (param >= 0) {
        tex_handle_error(
            normal_error_type,
            "Math error: parameter '%s' with id %i in style %i is not set",
            name, param, style,
            "Sorry, but I can't typeset math unless various parameters have been set. This is\n"
            "normally done by loading special math fonts into the math family slots. Your font\n"
            "set is lacking at least the parameter mentioned earlier."
        );
    } else {
        tex_formatted_error("math", "invalid parameter '%s' in style %i", name, style);
    }
    return;
}

/*tex
    For the moment this is experimental.
*/

static inline scaled tex_aux_max_scale(int style, int param)
{
    scaled scale = tex_get_math_parameter(style, param, NULL);
    if (scale > max_math_scaling_factor) {
        return max_math_scaling_factor;
    } else if (scale < 0) {
        return 0;
    } else {
        return scale;
    }
}

/*tex

    The non-static-ness of this function is for the benefit of |texmath.w|. Watch out, this one
    uses the style! The style and size numbers don't match because we have cramped styles.

*/

scaled tex_get_math_quad_style(int style)
{
    scaled scale = tex_aux_max_scale(style, math_parameter_x_scale);
    scaled value = tex_get_math_parameter(style, math_parameter_quad, NULL);
    if (value == undefined_math_parameter) {
        tex_aux_math_parameter_error(style, -1, "quad");
        return 0;
    } else {
        return scaledround(0.001 * value * scale);
    }
}

/*tex

    For this reason the next one is different because it is called with a size specifier instead
    of a style specifier.

*/

scaled tex_get_math_axis_size(int size)
{
    scaled value;
    switch (size) {
        case script_size       : size = script_style;        break;
        case script_script_size: size = script_script_style; break;
        default                : size = text_style;          break;
    }
    value = tex_get_math_parameter(size, math_parameter_axis, NULL);
    if (value == undefined_math_parameter) {
        tex_aux_math_parameter_error(size, -1, "axis");
        return 0;
    } else {
        return value;
    }
}

scaled tex_get_math_quad_size(int size)
{
    switch (size) {
        case script_size       : size = script_style;        break;
        case script_script_size: size = script_script_style; break;
        default                : size = text_style;          break;
    }
    return tex_get_math_parameter(size, math_parameter_quad, NULL);
}

scaled tex_get_math_exheight_size(int size)
{
    switch (size) {
        case script_size       : size = script_style;        break;
        case script_script_size: size = script_script_style; break;
        default                : size = text_style;          break;
    }
    return tex_get_math_parameter(size, math_parameter_exheight, NULL);
}

scaled tex_get_math_quad_size_scaled(int size) /* used in cur_mu */
{
    scaled value, scale;
    switch (size) {
        case script_size       : size = script_style;        break;
        case script_script_size: size = script_script_style; break;
        default                : size = text_style;          break;
    }
    value = tex_get_math_parameter(size, math_parameter_quad, NULL);
    scale = tex_aux_max_scale(size, math_parameter_x_scale);
 /* return tex_x_over_n(scaledround(0.001 * value * scale), 18); */
    return scaledround(0.001 * value * scale / 18.0); /* no glyphscale here .. kind of inconsistent */
}

scaled tex_get_math_quad_size_unscaled(int size) /* used in cur_mu */
{
    switch (size) {
        case script_size       : size = script_style;        break;
        case script_script_size: size = script_script_style; break;
        default                : size = text_style;          break;
    }
    return scaledround(tex_get_math_parameter(size, math_parameter_quad, NULL) / 18.0);
}

static int tex_aux_math_parameter_okay(int param)
{
    if (ignore_math_parameter(param) == 1) {
        if (tracing_math_par > 1) {
            tex_begin_diagnostic();
            tex_print_format("[math: parameter, name %s, ignored]", lmt_name_of_math_parameter(param));
            tex_end_diagnostic();
        }
        return 0;
    } else {
        return 1;
    }
}

scaled tex_get_math_parameter_checked(int style, int param)
{
    if (tex_aux_math_parameter_okay(param)) {
        scaled value = tex_get_math_parameter(style, param, NULL);
        if (value == undefined_math_parameter) {
            tex_aux_math_parameter_error(style, param, lmt_name_of_math_parameter(param));
            return 0;
        } else {
            return value;
        }
    } else {
        return 0;
    }
}

scaled tex_get_math_parameter_default(int style, int param, scaled dflt)
{
    if (tex_aux_math_parameter_okay(param)) {
        scaled value = tex_get_math_parameter(style, param, NULL);
        if (value == undefined_math_parameter) {
            return dflt;
        } else {
            return value;
        }
    } else {
        return dflt;
    }
}

void tex_run_math_italic_correction(void) {
    if (cur_chr == italic_correction_code) {
        tex_tail_append(tex_new_kern_node(0, explicit_kern_subtype)); /* maybe math_shape_kern */
    }
}

/* */

scaled tex_get_math_x_parameter(int style, int param)
{
    if (tex_aux_math_parameter_okay(param)) {
        scaled scale = tex_aux_max_scale(style, math_parameter_x_scale);
        scaled value = tex_get_math_parameter(style, param, NULL);
        if (value == undefined_math_parameter) {
            return value;  // ?? scaledround(value * scale * 0.001);
        } else {
            return value ? scaledround(0.000000001 * glyph_scale_par * glyph_x_scale_par * value * scale) : 0;
        }
    } else {
        return 0;
    }
}

scaled tex_get_math_x_parameter_checked(int style, int param)
{
    if (tex_aux_math_parameter_okay(param)) {
        scaled scale = tex_aux_max_scale(style, math_parameter_x_scale);
        scaled value = tex_get_math_parameter(style, param, NULL);
        if (value == undefined_math_parameter) {
            tex_aux_math_parameter_error(style, param, lmt_name_of_math_parameter(param));
            return 0;
        } else {
            return value ? scaledround(0.000000001 * glyph_scale_par * glyph_x_scale_par * value * scale) : 0;
        }
    } else {
        return 0;
    }
}

scaled tex_get_math_x_parameter_default(int style, int param, scaled dflt)
{
    if (tex_aux_math_parameter_okay(param)) {
        scaled scale = tex_aux_max_scale(style, math_parameter_x_scale);
        scaled value = tex_get_math_parameter(style, param, NULL);
        if (value == undefined_math_parameter) {
            return dflt;
        } else{
            return value ? scaledround(0.000000001 * glyph_scale_par * glyph_x_scale_par * value * scale) : 0;
        }
    } else {
        return dflt;
    }
}

scaled tex_get_math_y_parameter(int style, int param)
{
    if (tex_aux_math_parameter_okay(param)) {
        scaled scale = tex_aux_max_scale(style, math_parameter_y_scale);
        scaled value = tex_get_math_parameter(style, param, NULL);
        if (value == undefined_math_parameter) {
            return value;
        } else{
            return value ? scaledround(0.000000001 * glyph_scale_par * glyph_y_scale_par * value * scale) : 0;
        }
    } else {
        return 0;
    }
}

scaled tex_get_math_y_parameter_checked(int style, int param)
{
    if (tex_aux_math_parameter_okay(param)) {
        scaled scale = tex_aux_max_scale(style, math_parameter_y_scale);
        scaled value = tex_get_math_parameter(style, param, NULL);
        if (value == undefined_math_parameter) {
            tex_aux_math_parameter_error(style, param, lmt_name_of_math_parameter(param));
            return 0;
        } else {
            return value ? scaledround(0.000000001 * glyph_scale_par * glyph_y_scale_par * value * scale) : 0;
        }
    } else {
        return 0;
    }
}

scaled tex_get_math_y_parameter_default(int style, int param, scaled dflt)
{
    if (tex_aux_math_parameter_okay(param)) {
        scaled scale = tex_aux_max_scale(style, math_parameter_y_scale);
        scaled value = tex_get_math_parameter(style, param, NULL);
        if (value == undefined_math_parameter) {
            return dflt;
        } else {
            return value ? scaledround(0.000000001 * glyph_scale_par * glyph_y_scale_par * value * scale) : 0;
        }
    } else {
        return dflt;
    }
}

scaled tex_get_font_math_parameter(int font, int size, int param)
{
    scaled scale = tex_get_math_font_scale(font, size);
    scaled value = tex_aux_get_font_math_parameter(scale, font, param);
    if (value == undefined_math_parameter) {
        return undefined_math_parameter;
    } else {
        return value ? scaledround(0.001 * glyph_scale_par * value) : 0;
    }
}

/* maybe more precission, so multiply all and divide by 0.000000001 */

scaled tex_get_font_math_y_parameter(int font, int size, int param)
{
    scaled scale = tex_get_math_font_scale(font, size);
    scaled value = tex_aux_get_font_math_parameter(scale, font, param);
    if (value == undefined_math_parameter) {
        return undefined_math_parameter;
    } else {
        return value ? scaledround(0.000001 * glyph_scale_par * glyph_y_scale_par * value) : 0;
    }
}

scaled tex_get_font_math_x_parameter(int font, int size, int param)
{
    scaled scale = tex_get_math_font_scale(font, size);
    scaled value = tex_aux_get_font_math_parameter(scale, font, param);
    if (value == undefined_math_parameter) {
        return undefined_math_parameter;
    } else {
        return value ? scaledround(0.000001 * glyph_scale_par * glyph_x_scale_par * value) : 0;
    }
}

halfword tex_to_math_spacing_parameter(halfword left, halfword right)
{
    halfword param = math_parameter_spacing_pair(left,right);
    return (param >= math_parameter_atom_pairs_first && param <= math_parameter_atom_pairs_last) ? param : -1;
}

halfword tex_to_math_rules_parameter(halfword left, halfword right)
{
    halfword param = math_parameter_rules_pair(left,right);
    return (param >= math_parameter_atom_rules_first && param <= math_parameter_atom_rules_last) ? param : -1;
}

void tex_set_default_math_codes(void)
{
    mathcodeval mval = tex_no_math_code();
    /*tex This will remap old font families at runtime. */
    mval.class_value = math_use_current_family_code;
    /*tex Upright math digts come from family 0. */
    for (int d = '0'; d <= '9'; d++) {
        mval.character_value = d;
        tex_set_math_code(d, mval, level_one);
    }
    /* In traditional fonts math italic has family 1. */
    mval.family_value = 1;
    for (int u = 'A'; u <= 'Z'; u++) {
        mval.character_value = u;
        tex_set_math_code(u, mval, level_one);
    }
    for (int l = 'a'; l <= 'z'; l++) {
        mval.character_value = l;
        tex_set_math_code(l, mval, level_one);
    }
    /*tex This is kind of standard. */
    tex_set_del_code('.', (delcodeval) { { 0, 0, 0, }, { 0, 0, 0 } }, level_one);
}

int tex_in_main_math_style(halfword style)
{
    switch (style) {
        case display_style:
        case text_style:
            return 1;
        /*
         case cramped_display_style:
         case cramped_text_style:
            return 0; // could be parameter driven
        */
        default:
            return 0;
    }
}
