Section 1136: Building math lists

The routines that 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 “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.

math_lists.c
// << Start file |math_lists.c|, 1382 >>

void push_math(int c) {
    push_nest();
    mode = -MMODE;
    incompleat_noad = null;
    new_save_level(c);
}

Section 1137

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.

⟨ Cases of main_control that build boxes and lists 1056 ⟩+≡

case HMODE + MATH_SHIFT:
    init_math();
    break;

Section 1138

math_lists.c
void init_math() {
    scaled w;               // new or partial |pre_display_size|
    scaled l = 0;           // new |display_width|
    scaled s = 0;           // new |display_indent|
    pointer p;              // current node when calculating |pre_display_size|
    pointer q;              // glue specification when calculating |pre_display_size|
    internal_font_number f; // font in current |CHAR_NODE|
    int n;                  // scope of paragraph shape specification
    scaled v;               // |w| plus possible glue amount
    scaled d;               // increment to |v|
    
    get_token(); // |get_x_token| would fail on \ifmmode!
    if (cur_cmd == MATH_SHIFT && mode > 0) {
        // << Go into display math mode, 1145 >>
    }
    else {
        back_input();
        // << Go into ordinary math mode, 1139 >>
    }
}

Section 1139

⟨ Go into ordinary math mode 1139 ⟩≡

push_math(MATH_SHIFT_GROUP);
eq_word_define(INT_BASE + CUR_FAM_CODE, -1);
if (every_math != null) {
    begin_token_list(every_math, EVERY_MATH_TEXT);
}

Section 1140

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.

⟨ Cases of main_control that build boxes and lists 1056 ⟩+≡

case MMODE + EQ_NO:
    if (privileged()) {
        if (cur_group == MATH_SHIFT_GROUP) {
            start_eq_no();
        }
        else {
            off_save();
        }
    }
    break;

Section 1141

⟨ Put each of TeX’s primitives into the hash table 226 ⟩+≡

primitive("eqno", EQ_NO, 0);
primitive("leqno", EQ_NO, 1);

Section 1142

When 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.

math_lists.c
void start_eq_no() {
    saved(0) = cur_chr;
    incr(save_ptr);
    // << Go into ordinary math mode, 1139 >>
}

Section 1143

⟨ Cases of print_cmd_chr for symbolic printing of primitives 227 ⟩+≡

case EQ_NO:
    if (chr_code == 1) {
        print_esc("leqno");
    }
    else {
        print_esc("eqno");
    }
    break;

Section 1144

⟨ Forbidden cases detected in main_control 1048 ⟩+≡

non_math(EQ_NO):

Section 1145

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.

⟨ Go into display math mode 1145 ⟩≡

if (head == tail) {
    // '\noindent$$' or '$$ $$'
    pop_nest();
    w = -MAX_DIMEN;
}
else {
    line_break(display_widow_penalty);
    // << Calculate the natural width, |w|, by which the characters of the final line extend to the right of the reference point, plus two ems; or set |w = MAX_DIMEN| if the non-blank information on that line is affected by stretching or shrinking, 1146 >>
}
// now we are in vertical mode, working on the list that will contain the display

// << Calculate the length, |l|, and the shift amount, |s|, of the display lines, 1149 >>
push_math(MATH_SHIFT_GROUP);
mode = MMODE;
eq_word_define(INT_BASE + CUR_FAM_CODE, -1);
eq_word_define(DIMEN_BASE + PRE_DISPLAY_SIZE_CODE, w);
eq_word_define(DIMEN_BASE + DISPLAY_WIDTH_CODE, l);
eq_word_define(DIMEN_BASE + DISPLAY_INDENT_CODE, s);
if (every_display != null) {
    begin_token_list(every_display, EVERY_DISPLAY_TEXT);
}
if (nest_ptr == 1) {
    build_page();
}

Section 1146

⟨ Calculate the natural width, w, by which the characters of the final line extend to the right of the reference point, plus two ems; or set w = MAX_DIMEN if the non-blank information on that line is affected by stretching or shrinking 1146 ⟩≡

v = shift_amount(just_box) + 2 * quad(cur_font);
w = -MAX_DIMEN;
p = list_ptr(just_box);
while (p != null) {
    // << Let |d| be the natural width of node |p|; if the node is "visible," |goto found|; if the node is glue that stretches or shrinks, set |v = MAX_DIMEN|, 1147 >>

    if (v < MAX_DIMEN) {
        v += d;
    }
    goto not_found;
found:
    if (v < MAX_DIMEN) {
        v += d;
        w = v;
    }
    else {
        w = MAX_DIMEN;
        break; // goto done
    }
not_found:
    p = link(p);
}
// done:

Section 1147

⟨ Let d be the natural width of node p; if the node is “visible,” goto found; if the node is glue that stretches or shrinks, set v = MAX_DIMEN 1147 ⟩≡

reswitch:
if (is_char_node(p)) {
    f = font(p);
    d = char_width(f, char_info(f, character(p)));
    goto found;
}

switch (type(p)) {
case HLIST_NODE:
case VLIST_NODE:
case RULE_NODE:
    d = width(p);
    goto found;

case LIGATURE_NODE:
    // << Make node |p| look like a |CHAR_NODE| and |goto reswitch|, 652 >>

case KERN_NODE:
case MATH_NODE:
    d = width(p);
    break;

case GLUE_NODE:
    // << Let |d| be the natural width of this glue; if stretching or shrinking, set |v = MAX_DIMEN|; |goto found| in the case of leaders, 1148 >>
    break;

case WHATSIT_NODE:
    // << Let |d| be the width of the whatsit |p|, 1361 >>
    break;

default:
    d = 0;
}

Section 1148

We need to be careful that w, v, and d do not depend on any glue_set values, since such values are subject to system-dependent rounding. System-dependent numbers are not allowed to infiltrate parameters like pre_display_size, since is supposed to make the same decisions on all machines.

⟨ Let d be the natural width of this glue; if stretching or shrinking, set v = MAX_DIMEN; goto found in the case of leaders 1148 ⟩≡

q = glue_ptr(p);
d = width(q);
if (glue_sign(just_box) == STRETCHING) {
    if (glue_order(just_box) == stretch_order(q) && stretch(q) != 0) {
        v = MAX_DIMEN;
    }
}
else if (glue_sign(just_box) == SHRINKING) {
    if (glue_order(just_box) == shrink_order(q) && shrink(q) != 0) {
        v = MAX_DIMEN;
    }
}
if (subtype(p) >= A_LEADERS) {
    goto found;
}

Section 1149

A displayed equation is considered to be three lines long, so we calculate the length and offset of line number prev_graf + 2.

⟨ Calculate the length, l, and the shift amount, s, of the display lines 1149 ⟩≡

if (par_shape_ptr == null) {
    if (hang_indent != 0) {
        if ((hang_after >= 0 && prev_graf + 2 > hang_after)
            || prev_graf + 1 < -hang_after)
        {
            l = hsize - abs(hang_indent);
            if (hang_indent > 0) {
                s = hang_indent;
            }
            else {
                s = 0;
            }
        }
    }
    else {
        l = hsize;
        s = 0;
    }
}
else {
    n = info(par_shape_ptr);
    if (prev_graf + 2 >= n) {
        p = par_shape_ptr + 2 * n;
    }
    else {
        p = par_shape_ptr + 2 * (prev_graf + 2);
    }
    s = mem[p - 1].sc;
    l = mem[p].sc;
}

Section 1150

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.

⟨ Cases of main_control that build boxes and lists 1056 ⟩+≡

case MMODE + LEFT_BRACE:
    tail_append(new_noad());
    back_input();
    scan_math(nucleus(tail));
    break;

Section 1151

Recall that the nucleus, subscr, and supscr fields in a noad are broken down into subfields called math_type and either info or (fam, 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.

texmath.h
#define fam_in_range (cur_fam >= 0 && cur_fam < 16)
math_lists.c
void scan_math(pointer p) {
    int c; // math character code

restart:
    // << Get the next non-blank non-relax non-call token, 404 >>

reswitch:
    switch (cur_cmd) {
    case LETTER:
    case OTHER_CHAR:
    case CHAR_GIVEN:
        c = ho(math_code(cur_chr));
        if (c == 0x8000) {
            // << Treat |cur_chr| as an active character, 1152 >>
            goto restart;
        }
        break;
    
    case CHAR_NUM:
        scan_char_num();
        cur_chr = cur_val;
        cur_cmd = CHAR_GIVEN;
        goto reswitch;
    
    case MATH_CHAR_NUM:
        scan_fifteen_bit_int();
        c = cur_val;
        break;
    
    case MATH_GIVEN:
        c = cur_chr;
        break;
    
    case DELIM_NUM:
        scan_twenty_seven_bit_int();
        c = cur_val / 0x1000;
        break;
    
    default:
        // << Scan a subformula enclosed in braces and |return|, 1153 >>
    }
    math_type(p) = MATH_CHAR;
    character(p) = c % 256;
    if (c >= VAR_CODE && fam_in_range) {
        fam(p) = cur_fam;
    }
    else {
        fam(p) = (c / 256) % 16;
    }
}

Section 1152

An active character that is an OUTER_CALL is allowed here.

⟨ Treat cur_chr as an active character 1152 ⟩≡

cur_cs = cur_chr + ACTIVE_BASE;
cur_cmd = eq_type(cur_cs);
cur_chr = equiv(cur_cs);
x_token();
back_input();

Section 1153

The pointer p is placed on save_stack while a complex subformula is being scanned.

⟨ Scan a subformula enclosed in braces and return 1153 ⟩≡

back_input();
scan_left_brace();
saved(0) = p;
incr(save_ptr);
push_math(MATH_GROUP);
return;

Section 1154

The simplest math formula is, of course, ‘$ $’, when no noads are generated. The next simplest cases involve a single character, e.g., ‘$x$’. Even though such cases may not seem to be very interesting, the reader can perhaps understand how happy the author was when ‘$x$’ was first properly typeset by . The code in this section was used.

⟨ Cases of main_control that build boxes and lists 1056 ⟩+≡

case MMODE + LETTER:
case MMODE + OTHER_CHAR:
case MMODE + CHAR_GIVEN:
    set_math_char(ho(math_code(cur_chr)));
    break;

case MMODE + CHAR_NUM:
    scan_char_num();
    cur_chr = cur_val;
    set_math_char(ho(math_code(cur_chr)));
    break;

case MMODE + MATH_CHAR_NUM:
    scan_fifteen_bit_int();
    set_math_char(cur_val);
    break;

case MMODE + MATH_GIVEN:
    set_math_char(cur_chr);
    break;

case MMODE + DELIM_NUM:
    scan_twenty_seven_bit_int();
    set_math_char(cur_val / 0x1000);
    break;

Section 1155

The set_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.

math_lists.c
void set_math_char(int c) {
    pointer p; // the new noad
    if (c >= 0x8000) {
        // << Treat |cur_chr| as an active character, 1152 >>
    }
    else {
        p = new_noad();
        math_type(nucleus(p)) = MATH_CHAR;
        character(nucleus(p)) = c % 256;
        fam(nucleus(p)) = (c / 256) % 16;
        if (c >= VAR_CODE) {
            if (fam_in_range) {
                fam(nucleus(p)) = cur_fam;
            }
            type(p) = ORD_NOAD;
        }
        else {
            type(p) = ORD_NOAD + (c / 0x1000);
        }
        link(tail) = p;
        tail = p;
    }
}

Section 1156

Primitive math operators like \mathop and \underline are given the command code MATH_COMP, supplemented by the noad type that they generate.

⟨ Put each of TeX’s primitives into the hash table 226 ⟩+≡

primitive("mathord", MATH_COMP, ORD_NOAD);
primitive("mathop", MATH_COMP, OP_NOAD);
primitive("mathbin", MATH_COMP, BIN_NOAD);
primitive("mathrel", MATH_COMP, REL_NOAD);
primitive("mathopen", MATH_COMP, OPEN_NOAD);
primitive("mathclose", MATH_COMP, CLOSE_NOAD);
primitive("mathpunct", MATH_COMP, PUNCT_NOAD);
primitive("mathinner", MATH_COMP, INNER_NOAD);
primitive("underline", MATH_COMP, UNDER_NOAD);
primitive("overline", MATH_COMP, OVER_NOAD);
primitive("displaylimits", LIMIT_SWITCH, NORMAL);
primitive("limits", LIMIT_SWITCH, LIMITS);
primitive("nolimits", LIMIT_SWITCH, NO_LIMITS);

Section 1157

⟨ Cases of print_cmd_chr for symbolic printing of primitives 227 ⟩+≡

case MATH_COMP:
    switch (chr_code) {
    case ORD_NOAD:
        print_esc("mathord");
        break;
    
    case OP_NOAD:
        print_esc("mathop");
        break;
    
    case BIN_NOAD:
        print_esc("mathbin");
        break;

    case REL_NOAD:
        print_esc("mathrel");
        break;
    
    case OPEN_NOAD:
        print_esc("mathopen");
        break;
    
    case CLOSE_NOAD:
        print_esc("mathclose");
        break;
    
    case PUNCT_NOAD:
        print_esc("mathpunct");
        break;
    
    case INNER_NOAD:
        print_esc("mathinner");
        break;
    
    case UNDER_NOAD:
        print_esc("underline");
        break;
    
    default:
        print_esc("overline");
    }
    break;

case LIMIT_SWITCH:
    if (chr_code == LIMITS) {
        print_esc("limits");
    }
    else if (chr_code == NO_LIMITS) {
        print_esc("nolimits");
    }
    else {
        print_esc("displaylimits");
    }
    break;

Section 1158

⟨ Cases of main_control that build boxes and lists 1056 ⟩+≡

case MMODE + MATH_COMP:
    tail_append(new_noad());
    type(tail) = cur_chr;
    scan_math(nucleus(tail));
    break;

case MMODE + LIMIT_SWITCH:
    math_limit_switch();
    break;

Section 1159

math_lists.c
void math_limit_switch() {
    if (head != tail && type(tail) == OP_NOAD) {
        subtype(tail) = cur_chr;
        return;
    }
    print_err("Limit controls must follow a math operator");
    help1("I'm ignoring this misplaced \\limits or \\nolimits command.");
    error();
}

Section 1160

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.

math_lists.c
void scan_delimiter(pointer p, bool r) {
    if (r) {
        scan_twenty_seven_bit_int();
    }
    else {
        // << Get the next non-blank non-relax non-call token, 404 >>
        switch (cur_cmd) {
        case LETTER:
        case OTHER_CHAR:
            cur_val = del_code(cur_chr);
            break;
        
        case DELIM_NUM:
            scan_twenty_seven_bit_int();
            break;
        
        default:
            cur_val = -1;
        }
    }
    if (cur_val < 0) {
        // << Report that an invalid delimiter code is being changed to null; set |cur_val = 0|, 1161 >>
    }
    small_fam(p) = (cur_val / 0x100000) % 16;
    small_char(p) = (cur_val / 0x1000) % 256;
    large_fam(p) = (cur_val / 256) % 16;
    large_char(p) = cur_val % 256;
}

Section 1161

⟨ Report that an invalid delimiter code is being changed to null; set cur_val = 0 1161 ⟩≡

print_err("Missing delimiter (. inserted)");
help6("I was expecting to see something like `(' or `\\{' or")
    ("`\\}' here. If you typed, e.g., `{' instead of `\\{', you")
    ("should probably delete the `{' by typing `1' now, so that")
    ("braces don't get unbalanced. Otherwise just proceed.")
    ("Acceptable delimiters are characters whose \\delcode is")
    ("nonnegative, or you can use `\\delimiter <delimiter code>'.");
back_error();
cur_val = 0;

Section 1162

⟨ Cases of main_control that build boxes and lists 1056 ⟩+≡

case MMODE + RADICAL:
    math_radical();
    break;

Section 1163

math_lists.c
void math_radical() {
    tail_append(get_node(RADICAL_NOAD_SIZE));
    type(tail) = RADICAL_NOAD;
    subtype(tail) = NORMAL;
    mem[nucleus(tail)] = empty_field;
    mem[subscr(tail)] = empty_field;
    mem[supscr(tail)] = empty_field;
    scan_delimiter(left_delimiter(tail), true);
    scan_math(nucleus(tail));
}

Section 1164

⟨ Cases of main_control that build boxes and lists 1056 ⟩+≡

case MMODE + ACCENT:
case MMODE + MATH_ACCENT:
    math_ac();
    break;

Section 1165

math_lists.c
void math_ac() {
    if (cur_cmd == ACCENT) {
        // << Complain that the user should have said \mathaccent, 1166 >>
    }
    tail_append(get_node(ACCENT_NOAD_SIZE));
    type(tail) = ACCENT_NOAD;
    subtype(tail) = NORMAL;
    mem[nucleus(tail)] = empty_field;
    mem[subscr(tail)] = empty_field;
    mem[supscr(tail)] = empty_field;
    math_type(accent_chr(tail)) = MATH_CHAR;
    scan_fifteen_bit_int();
    character(accent_chr(tail)) = cur_val % 256;
    if (cur_val >= VAR_CODE && fam_in_range) {
        fam(accent_chr(tail)) = cur_fam;
    }
    else {
        fam(accent_chr(tail)) = (cur_val / 256) % 16;
    }
    scan_math(nucleus(tail));
}

Section 1166

⟨ Complain that the user should have said \mathaccent 1166 ⟩≡

print_err("Please use ");
print_esc("mathaccent");
print(" for accents in math mode");
help2("I'm changing \\accent to \\mathaccent here; wish me luck.")
    ("(Accents are not the same in formulas as they are in text.)");
error();

Section 1167

⟨ Cases of main_control that build boxes and lists 1056 ⟩+≡

case MMODE + VCENTER:
    scan_spec(VCENTER_GROUP, false);
    normal_paragraph();
    push_nest();
    mode = -VMODE;
    prev_depth = IGNORE_DEPTH;
    if (every_vbox != null) {
        begin_token_list(every_vbox, EVERY_VBOX_TEXT);
    }
    break;

Section 1168

⟨ Cases of handle_right_brace where a RIGHT_BRACE triggers a delayed action 1085 ⟩+≡

case VCENTER_GROUP:
    end_graf();
    unsave();
    save_ptr -= 2;
    p = vpack(link(head), saved(1), saved(0));
    pop_nest();
    tail_append(new_noad());
    type(tail) = VCENTER_NOAD;
    math_type(nucleus(tail)) = SUB_BOX;
    info(nucleus(tail)) = p;
    break;

Section 1169

The routine that inserts a STYLE_NODE holds no surprises.

⟨ Put each of TeX’s primitives into the hash table 226 ⟩+≡

primitive("displaystyle", MATH_STYLE, DISPLAY_STYLE);
primitive("textstyle", MATH_STYLE, TEXT_STYLE);
primitive("scriptstyle", MATH_STYLE, SCRIPT_STYLE);
primitive("scriptscriptstyle", MATH_STYLE, SCRIPT_SCRIPT_STYLE);

Section 1170

⟨ Cases of print_cmd_chr for symbolic printing of primitives 227 ⟩+≡

case MATH_STYLE:
    print_style(chr_code);
    break;

Section 1171

⟨ Cases of main_control that build boxes and lists 1056 ⟩+≡

case MMODE + MATH_STYLE:
    tail_append(new_style(cur_chr));
    break;

case MMODE + NON_SCRIPT:
    tail_append(new_glue(ZERO_GLUE));
    subtype(tail) = COND_MATH_GLUE;
    break;

case MMODE + MATH_CHOICE:
    append_choices();
    break;

Section 1172

The routine that scans the four mlists of a \mathchoice is very much like the routine that builds discretionary nodes.

math_lists.c
void append_choices() {
    tail_append(new_choice());
    incr(save_ptr);
    saved(-1) = 0;
    push_math(MATH_CHOICE_GROUP);
    scan_left_brace();
}

Section 1173

⟨ Cases of handle_right_brace where a RIGHT_BRACE triggers a delayed action 1085 ⟩+≡

case MATH_CHOICE_GROUP:
    build_choices();
    break;

Section 1174

math_lists.c
// << Declare the function called |fin_mlist|, 1184 >>

void build_choices() {
    pointer p; // the current mlist
    unsave();
    p = fin_mlist(null);
    switch (saved(-1)) {
    case 0:
        display_mlist(tail) = p;
        break;
    
    case 1:
        text_mlist(tail) = p;
        break;
    
    case 2:
        script_mlist(tail) = p;
        break;
    
    case 3:
        script_script_mlist(tail) = p;
        decr(save_ptr);
        return;
    } // there are no other cases
    incr(saved(-1));
    push_math(MATH_CHOICE_GROUP);
    scan_left_brace();
}

Section 1175

Subscripts and superscripts are attached to the previous nucleus by the action procedure called sub_sup. We use the facts that SUB_MARK = SUP_MARK + 1 and subscr(p) = supscr(p) + 1.

⟨ Cases of main_control that build boxes and lists 1056 ⟩+≡

case MMODE + SUB_MARK:
case MMODE + SUP_MARK:
    sub_sup();
    break;

Section 1176

math_lists.c
void sub_sup() {
    small_number t; // type of previous sub/superscript
    pointer p;      // field to be filled by |scan_math|
    t = EMPTY;
    p = null;
    if (tail != head && scripts_allowed(tail)) {
        p = supscr(tail) + cur_cmd - SUP_MARK; // |supscr| or |subscr|
        t = math_type(p);
    }
    if (p == null || t != EMPTY) {
        // << Insert a dummy noad to be sub/superscripted, 1177 >>
    }
    scan_math(p);
}

Section 1177

⟨ Insert a dummy noad to be sub/superscripted 1177 ⟩≡

tail_append(new_noad());
p = supscr(tail) + cur_cmd - SUP_MARK; // |supscr| or |subscr|
if (t != EMPTY) {
    if (cur_cmd == SUP_MARK) {
        print_err("Double superscript");
        help1("I treat `x^1^2' essentially like `x^1{}^2'.");
    }
    else {
        print_err("Double subscript");
        help1("I treat `x_1_2' essentially like `x_1{}_2'.");
    }
    error();
}

Section 1178

An operation like ‘\over’ causes the current mlist to go into a state of suspended animation: incompleat_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 incompleat fraction noad, and that noad will become the whole formula, unless it is surrounded by ‘\left’ and ‘\right’ delimiters.

constants.h
#define ABOVE_CODE     0 // '\above' 
#define OVER_CODE      1 // '\over' 
#define ATOP_CODE      2 // '\atop' 
#define DELIMITED_CODE 3 // '\abovewithdelims', etc.

⟨ Put each of TeX’s primitives into the hash table 226 ⟩+≡

primitive("above", ABOVE, ABOVE_CODE);
primitive("over", ABOVE, OVER_CODE);
primitive("atop", ABOVE, ATOP_CODE);
primitive("abovewithdelims", ABOVE, DELIMITED_CODE + ABOVE_CODE);
primitive("overwithdelims", ABOVE, DELIMITED_CODE + OVER_CODE);
primitive("atopwithdelims", ABOVE, DELIMITED_CODE + ATOP_CODE);

Section 1179

⟨ Cases of print_cmd_chr for symbolic printing of primitives 227 ⟩+≡

case ABOVE:
    switch (chr_code) {
    case OVER_CODE:
        print_esc("over");
        break;
    
    case ATOP_CODE:
        print_esc("atop");
        break;
    
    case DELIMITED_CODE + ABOVE_CODE:
        print_esc("abovewithdelims");
        break;
    
    case DELIMITED_CODE + OVER_CODE:
        print_esc("overwithdelims");
        break;
    
    case DELIMITED_CODE + ATOP_CODE:
        print_esc("atopwithdelims");
        break;
    
    default:
        print_esc("above");
    }
    break;

Section 1180

⟨ Cases of main_control that build boxes and lists 1056 ⟩+≡

case MMODE + ABOVE:
    math_fraction();
    break;

Section 1181

math_lists.c
void math_fraction() {
    small_number c; // the type of generalized fraction we are scanning
    c = cur_chr;
    if (incompleat_noad != null) {
        // << Ignore the fraction operation and complain about this ambiguous case, 1183 >>
    }
    else {
        incompleat_noad = get_node(FRACTION_NOAD_SIZE);
        type(incompleat_noad) = FRACTION_NOAD;
        subtype(incompleat_noad) = NORMAL;
        math_type(numerator(incompleat_noad)) = SUB_MLIST;
        info(numerator(incompleat_noad)) = link(head);
        mem[denominator(incompleat_noad)] = empty_field;
        mem[left_delimiter(incompleat_noad)] = null_delimiter;
        mem[right_delimiter(incompleat_noad)] = null_delimiter;
        link(head) = null;
        tail = head;
        // << Use code |c| to distinguish between generalized fractions, 1182 >>
    }
}

Section 1182

⟨ Use code c to distinguish between generalized fractions 1182 ⟩≡

if (c >= DELIMITED_CODE) {
    scan_delimiter(left_delimiter(incompleat_noad), false);
    scan_delimiter(right_delimiter(incompleat_noad), false);
}
switch (c % DELIMITED_CODE) {
case ABOVE_CODE:
    scan_normal_dimen;
    thickness(incompleat_noad) = cur_val;
    break;

case OVER_CODE:
    thickness(incompleat_noad) = DEFAULT_CODE;
    break;

case ATOP_CODE:
    thickness(incompleat_noad) = 0;
} // there are no other cases

Section 1183

⟨ Ignore the fraction operation and complain about this ambiguous case 1183 ⟩≡

if (c >= DELIMITED_CODE) {
    scan_delimiter(GARBAGE, false);
    scan_delimiter(GARBAGE, false);
}
if (c % DELIMITED_CODE == ABOVE_CODE) {
    scan_normal_dimen;
}
print_err("Ambiguous; you need another { and }");
help3("I'm ignoring this fraction specification, since I don't")
    ("know whether a construction like `x \\over y \\over z'")
    ("means `{x \\over y} \\over z' or `x \\over {y \\over z}'.");
error();

Section 1184

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

⟨ Declare the function called fin_mlist 1184 ⟩≡

pointer fin_mlist(pointer p) {
    pointer q; // the mlist to return
    if (incompleat_noad != null) {
        // << Compleat the incompleat noad, 1185 >>
    }
    else {
        link(tail) = p;
        q = link(head);
    }
    pop_nest();
    return q;
}

Section 1185

⟨ Compleat the incompleat noad 1185 ⟩≡

math_type(denominator(incompleat_noad)) = SUB_MLIST;
info(denominator(incompleat_noad)) = link(head);
if (p == null) {
    q = incompleat_noad;
}
else {
    q = info(numerator(incompleat_noad));
    if (type(q) != LEFT_NOAD) {
        confusion("right");
    }
    info(numerator(incompleat_noad)) = link(q);
    link(q) = incompleat_noad;
    link(incompleat_noad) = p;
}

Section 1186

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/superscripts, or when they surround an accent that is the nucleus of an Ord atom.

⟨ Cases of handle_right_brace where a RIGHT_BRACE triggers a delayed action 1085 ⟩+≡

case MATH_GROUP:
    unsave();
    decr(save_ptr);
    math_type(saved(0)) = SUB_MLIST;
    p = fin_mlist(null);
    info(saved(0)) = p;
    if (p != null && link(p) == null) {
        if (type(p) == ORD_NOAD) {
            if (math_type(subscr(p)) == EMPTY && math_type(supscr(p)) == EMPTY) {
                mem[saved(0)] = mem[nucleus(p)];
                free_node(p, NOAD_SIZE);
            }
        }
        else if (type(p) == ACCENT_NOAD
            && saved(0) == nucleus(tail)
            && type(tail) == ORD_NOAD)
        {
            // << Replace the tail of the list by |p|, 1187 >>
        }
    }
    break;

Section 1187

⟨ Replace the tail of the list by p 1187 ⟩≡

q = head;
while (link(q) != tail) {
    q = link(q);
}
link(q) = p;
free_node(tail, NOAD_SIZE);
tail = p;

Section 1188

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.

⟨ Put each of TeX’s primitives into the hash table 226 ⟩+≡

primitive("left", LEFT_RIGHT, LEFT_NOAD);
primitive("right", LEFT_RIGHT, RIGHT_NOAD);
text(FROZEN_RIGHT) = str_ptr - 1; // "right"
eqtb[FROZEN_RIGHT] = eqtb[cur_val];

Section 1189

⟨ Cases of print_cmd_chr for symbolic printing of primitives 227 ⟩+≡

case LEFT_RIGHT:
    if (chr_code == LEFT_NOAD) {
        print_esc("left");
    }
    else {
        print_esc("right");
    }
    break;

Section 1190

⟨ Cases of main_control that build boxes and lists 1056 ⟩+≡

case MMODE + LEFT_RIGHT:
    math_left_right();
    break;

Section 1191

math_lists.c
void math_left_right() {
    small_number t; // |LEFT_NOAD| or |RIGHT_NOAD|
    pointer p;      // new noad
    t = cur_chr;
    if (t == RIGHT_NOAD && cur_group != MATH_LEFT_GROUP) {
        // << Try to recover from mismatched \right, 1192 >>
    }
    else {
        p = new_noad();
        type(p) = t;
        scan_delimiter(delimiter(p), false);
        if (t == LEFT_NOAD) {
            push_math(MATH_LEFT_GROUP);
            link(head) = p;
            tail = p;
        }
        else {
            p = fin_mlist(p);
            unsave(); // end of |MATH_LEFT_GROUP|
            tail_append(new_noad());
            type(tail) = INNER_NOAD;
            math_type(nucleus(tail)) = SUB_MLIST;
            info(nucleus(tail)) = p;
        }
    }
}

Section 1192

⟨ Try to recover from mismatched \right 1192 ⟩≡

if (cur_group == MATH_SHIFT_GROUP) {
    scan_delimiter(GARBAGE, false);
    print_err("Extra ");
    print_esc("right");
    help1("I'm ignoring a \\right that had no matching \\left.");
    error();
}
else {
    off_save();
}

Section 1193

Here is the only way out of math mode.

⟨ Cases of main_control that build boxes and lists 1056 ⟩+≡

case MMODE + MATH_SHIFT:
    if (cur_group == MATH_SHIFT_GROUP) {
        after_math();
    }
    else {
        off_save();
    }
    break;

Section 1194

math_lists.c
void after_math() {
    bool l;      // '\leqno' instead of '\eqno'
    bool danger; // not enough symbol fonts are present
    int m;       // |MMODE| or |-MMODE|
    pointer p;   // the formula
    pointer a;   // box containing equation number
    // << Local variables for finishing a displayed formula, 1198 >>

    danger = false;
    // << Check that the necessary fonts for math symbols are present; if not, flush the current math lists and set |danger = true|, 1195 >>
    m = mode;
    l = false;
    p = fin_mlist(null); // this pops the nest
    if (mode == -m) {
        // end of equation number
        // << Check that another $ follows, 1197 >>
        cur_mlist = p;
        cur_style = TEXT_STYLE;
        mlist_penalties = false;
        mlist_to_hlist();
        a = hpack(link(TEMP_HEAD), NATURAL);
        unsave();
        decr(save_ptr); // now |cur_group = MATH_SHIFT_GROUP|
        if (saved(0) == 1) {
            l = true;
        }
        danger = false;
        // << Check that the necessary fonts for math symbols are present; if not, flush the current math lists and set |danger = true|, 1195 >>
        m = mode;
        p = fin_mlist(null);
    }
    else {
        a = null;
    }
    if (m < 0) {
        // << Finish math in text, 1196 >>
    }
    else {
        if (a == null) {
            // << Check that another $ follows, 1197 >>
        }
        // << Finish displayed math, 1199 >>
    }
}

Section 1195

⟨ Check that the necessary fonts for math symbols are present; if not, flush the current math lists and set danger = true 1195 ⟩≡

if (font_params[fam_fnt(2 + TEXT_SIZE)] < TOTAL_MATHSY_PARAMS
    || font_params[fam_fnt(2 + SCRIPT_SIZE)] < TOTAL_MATHSY_PARAMS
    || font_params[fam_fnt(2 + SCRIPT_SCRIPT_SIZE)] < TOTAL_MATHSY_PARAMS)
{
    print_err("Math formula deleted: Insufficient symbol fonts");
    help3("Sorry, but I can't typeset math unless \\textfont 2")
        ("and \\scriptfont 2 and \\scriptscriptfont 2 have all")
        ("the \\fontdimen values needed in math symbol fonts.");
    error();
    flush_math();
    danger = true;
}
else if (font_params[fam_fnt(3 + TEXT_SIZE)] < TOTAL_MATHEX_PARAMS
    || font_params[fam_fnt(3 + SCRIPT_SIZE)] < TOTAL_MATHEX_PARAMS
    || font_params[fam_fnt(3 + SCRIPT_SCRIPT_SIZE)] < TOTAL_MATHEX_PARAMS)
{
    print_err("Math formula deleted: Insufficient extension fonts");
    help3("Sorry, but I can't typeset math unless \\textfont 3")
        ("and \\scriptfont 3 and \\scriptscriptfont 3 have all")
        ("the \\fontdimen values needed in math extension fonts.");
    error();
    flush_math();
    danger = true;
}

Section 1196

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.

⟨ Finish math in text 1196 ⟩≡

tail_append(new_math(math_surround, BEFORE));
cur_mlist = p;
cur_style = TEXT_STYLE;
mlist_penalties = (mode > 0);
mlist_to_hlist();
link(tail) = link(TEMP_HEAD);
while (link(tail) != null) {
    tail = link(tail);
}
tail_append(new_math(math_surround, AFTER));
space_factor = 1000;
unsave();

Section 1197

gets to the following part of the program when the first ‘$’ ending a display has been scanned.

⟨ Check that another follows 1197 ⟩≡

get_x_token();
if (cur_cmd != MATH_SHIFT) {
    print_err("Display math should end with <span class="katex"><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">&quot;</span><span class="mclose">)</span><span class="mpunct">;</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">h</span><span class="mord mathnormal">e</span><span class="mord mathnormal">lp</span><span class="mord">2</span><span class="mopen">(</span><span class="mord">&quot;</span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mord mathnormal">h</span><span class="mord mathnormal">e</span><span class="mord">‘</span></span></span></span>' that I just saw supposedly matches a previous `$$'.")
        ("So I shall assume that you typed `' both times.");
    back_error();
}

Section 1198

We have saved the worst for last: The fussiest part of math mode processing occurs when a displayed formula is being centered and placed with an optional equation number.

⟨ Local variables for finishing a displayed formula 1198 ⟩≡

pointer b;           // box containing the equation
scaled w;            // width of the equation
scaled z;            // width of the line
scaled e;            // width of equation number
scaled q;            // width of equation number plus space to separate from equation
scaled d;            // displacement of equation in the line
scaled s;            // move the line right this much
small_number g1, g2; // glue parameter codes for before and after
pointer r;           // kern node used to position the display
pointer t;           // tail of adjustment list

Section 1199

At this time p points to the mlist for the formula; a is either null or it points to a box containing the equation number; and we are in vertical mode (or internal vertical mode).

⟨ Finish displayed math 1199 ⟩≡

cur_mlist = p;
cur_style = DISPLAY_STYLE;
mlist_penalties = false;
mlist_to_hlist();
p = link(TEMP_HEAD);
adjust_tail = ADJUST_HEAD;
b = hpack(p, NATURAL);
p = list_ptr(b);
t = adjust_tail;
adjust_tail = null;
w = width(b);
z = display_width;
s = display_indent;
if (a == null || danger) {
    e = 0;
    q = 0;
}
else {
    e = width(a);
    q = e + math_quad(TEXT_SIZE);
}
if (w + q > z) {
    // << Squeeze the equation as much as possible; if there is an equation number that should go on a separate line by itself, set |e = 0|, 1201 >>
}

// << Determine the displacement, |d|, of the left edge of the equation, with respect to the line size |z|, assuming that |l = false|, 1202 >>

// << Append the glue or equation number preceding the display, 1203 >>
// << Append the display and perhaps also the equation number, 1204 >>
// << Append the glue or equation number following the display, 1205 >>
resume_after_display();

Section 1200

math_lists.c
void resume_after_display() {
    if (cur_group != MATH_SHIFT_GROUP) {
        confusion("display");
    }
    unsave();
    prev_graf += 3;
    push_nest();
    mode = HMODE;
    space_factor = 1000;
    set_cur_lang;
    clang = cur_lang;
    prev_graf = (norm_min(left_hyphen_min)*64 + norm_min(right_hyphen_min))*0x10000 + cur_lang;
    // << Scan an optional space, 443 >>
    if (nest_ptr == 1) {
        build_page();
    }
}

Section 1201

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

⟨ Squeeze the equation as much as possible; if there is an equation number that should go on a separate line by itself, set e = 0 1201 ⟩≡

if (e != 0 && (w - total_shrink[NORMAL] + q <= z
    || total_shrink[FIL] != 0
    || total_shrink[FILL] != 0
    || total_shrink[FILLL] != 0))
{
    free_node(b, BOX_NODE_SIZE);
    b = hpack(p, z - q, EXACTLY);
}
else {
    e = 0;
    if (w > z) {
        free_node(b, BOX_NODE_SIZE);
        b = hpack(p, z, EXACTLY);
    }
}
w = width(b);

Section 1202

We try first to center the display without regard to the existence of the equation number. If that would make it too close (where “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.

⟨ Determine the displacement, d, of the left edge of the equation, with respect to the line size z, assuming that l = false 1202 ⟩≡

d = half(z - w);
if (e > 0 && d < 2*e) {
    // too close
    d = half(z - w - e);
    if (p != null
        && !is_char_node(p)
        && type(p) == GLUE_NODE)
    {
        d = 0;
    }
}

Section 1203

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.

⟨ Append the glue or equation number preceding the display 1203 ⟩≡

tail_append(new_penalty(pre_display_penalty));
if (d + s <= pre_display_size || l) {
     // not enough clearance
    g1 = ABOVE_DISPLAY_SKIP_CODE;
    g2 = BELOW_DISPLAY_SKIP_CODE;
}
else {
    g1 = ABOVE_DISPLAY_SHORT_SKIP_CODE;
    g2 = BELOW_DISPLAY_SHORT_SKIP_CODE;
}
if (l && e == 0) {
    // it follows that |type(a) = HLIST_NODE|
    shift_amount(a) = s;
    append_to_vlist(a);
    tail_append(new_penalty(INF_PENALTY));
}
else {
    tail_append(new_param_glue(g1));
}

Section 1204

⟨ Append the display and perhaps also the equation number 1204 ⟩≡

if (e != 0) {
    r = new_kern(z - w - e - d);
    if (l) {
        link(a) = r;
        link(r) = b;
        b = a;
        d = 0;
    }
    else {
        link(b) = r;
        link(r) = a;
    }
    b = hpack(b, NATURAL);
}
shift_amount(b) = s + d;
append_to_vlist(b);

Section 1205

⟨ Append the glue or equation number following the display 1205 ⟩≡

if (a != null
    && e == 0
    && !l)
{
    tail_append(new_penalty(INF_PENALTY));
    shift_amount(a) = s + z - width(a);
    append_to_vlist(a);
    g2 = 0;
}
if (t != ADJUST_HEAD) {
    // migrating material comes after equation number
    link(tail) = link(ADJUST_HEAD);
    tail = t;
}
tail_append(new_penalty(post_display_penalty));
if (g2 > 0) {
    tail_append(new_param_glue(g2));
}

Section 1206

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.

⟨ Finish an alignment in a display 1206 ⟩≡

do_assignments();
if (cur_cmd != MATH_SHIFT) {
    // << Pontificate about improper alignment in display, 1207 >>
}
else {
    // << Check that another $ follows, 1197 >>
}
pop_nest();
tail_append(new_penalty(pre_display_penalty));
tail_append(new_param_glue(ABOVE_DISPLAY_SKIP_CODE));
link(tail) = p;
if (p != null) {
    tail = q;
}
tail_append(new_penalty(post_display_penalty));
tail_append(new_param_glue(BELOW_DISPLAY_SKIP_CODE));
prev_depth = aux_save.sc;
resume_after_display();

Section 1207

⟨ Pontificate about improper alignment in display 1207 ⟩≡

print_err("Missing  inserted");
help2("Displays can use special alignments (like \\eqalignno)")
    ("only if nothing but the alignment itself is between $$'s.");
back_error();