Section 1055: Building boxes and lists

The most important parts of main_control are concerned with ’s chief mission of box-making. We need to control the activities that put entries on vlists and hlists, as well as the activities that convert those lists into boxes. All of the necessary machinery has already been developed; it remains for us to “push the buttons” at the right times.

Section 1056

As an introduction to these routines, let’s consider one of the simplest cases: What happens when ‘\hrule’ occurs in vertical mode, or ‘\vrule’ in horizontal mode or math mode? The code in main_control is short, since the scan_rule_spec routine already does most of what is required; thus, there is no need for a special action procedure.

Note that baselineskip calculations are disabled after a rule in vertical mode, by setting prev_depth = IGNORE_DEPTH.

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

case VMODE + HRULE:
case HMODE + VRULE:
case MMODE + VRULE:
    tail_append(scan_rule_spec());
    if (abs(mode) == VMODE) {
        prev_depth = IGNORE_DEPTH;
    }
    else if (abs(mode) == HMODE) {
        space_factor = 1000;
    }
    break;

Section 1057

The processing of things like \hskip and \vskip is slightly more complicated. But the code in main_control is very short, since it simply calls on the action routine append_glue. Similarly, \kern activates append_kern.

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

case VMODE + VSKIP:
case HMODE + HSKIP:
case MMODE + HSKIP:
case MMODE + MSKIP:
    append_glue();
    break;

any_mode(KERN):
case MMODE + MKERN:
    append_kern();
    break;

Section 1058

The HSKIP and VSKIP command codes are used for control sequences like \hss and \vfil as well as for \hskip and \vskip. The difference is in the value of cur_chr.

constants.h
#define FIL_CODE     0 // identifies \hfil and \vfil
#define FILL_CODE    1 // identifies \hfill and \vfill
#define SS_CODE      2 // identifies \hss and \vss
#define FIL_NEG_CODE 3 // identifies \hfilneg and \vfilneg
#define SKIP_CODE    4 // identifies \hskip and \vskip
#define MSKIP_CODE   5 // identifies \mskip

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

primitive("hskip", HSKIP, SKIP_CODE);
primitive("hfil", HSKIP, FIL_CODE);
primitive("hfill", HSKIP, FILL_CODE);
primitive("hss", HSKIP, SS_CODE);
primitive("hfilneg", HSKIP, FIL_NEG_CODE);
primitive("vskip", VSKIP, SKIP_CODE);
primitive("vfil", VSKIP, FIL_CODE);
primitive("vfill", VSKIP, FILL_CODE);
primitive("vss", VSKIP, SS_CODE);
primitive("vfilneg", VSKIP, FIL_NEG_CODE);
primitive("mskip", MSKIP, MSKIP_CODE);
primitive("kern", KERN, EXPLICIT);
primitive("mkern", MKERN, MU_GLUE);

Section 1059

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

case HSKIP:
    switch (chr_code) {
    case SKIP_CODE:
        print_esc("hskip");
        break;
    
    case FIL_CODE:
        print_esc("hfil");
        break;
  
    case FILL_CODE:
        print_esc("hfill");
        break;

    case SS_CODE:
        print_esc("hss");
        break;

    default:
        print_esc("hfilneg");
    }
    break;

case VSKIP:
    switch (chr_code) {
    case SKIP_CODE:
        print_esc("vskip");
        break;
    
    case FIL_CODE:
        print_esc("vfil");
        break;
    
    case FILL_CODE:
        print_esc("vfill");
        break;
    
    case SS_CODE:
        print_esc("vss");
        break;
    
    default:
        print_esc("vfilneg");
    }
    break;

case MSKIP:
    print_esc("mskip");
    break;

case KERN:
    print_esc("kern");
    break;

case MKERN:
    print_esc("mkern");
    break;

Section 1060

All the work relating to glue creation has been relegated to the following subroutine. It does not call build_page, because it is used in at least one place where that would be a mistake.

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

void append_glue() {
    small_number s; // modifier of skip command
    s = cur_chr;
    switch (s) {
    case FIL_CODE:
        cur_val = FIL_GLUE;
        break;
    
    case FILL_CODE:
        cur_val = FILL_GLUE;
        break;
    
    case SS_CODE:
        cur_val = SS_GLUE;
        break;
    
    case FIL_NEG_CODE:
        cur_val = FIL_NEG_GLUE;
        break;
    
    case SKIP_CODE:
        scan_glue(GLUE_VAL);
        break;
    
    case MSKIP_CODE:
        scan_glue(MU_VAL);
    } // now |cur_val| points to the glue specification
    tail_append(new_glue(cur_val));
    if (s >= SKIP_CODE) {
        decr(glue_ref_count(cur_val));
        if (s > SKIP_CODE) {
            subtype(tail) = MU_GLUE;
        }
    }
}

Section 1061

boxes_and_lists.c
void append_kern() {
    quarterword s; // |subtype| of the kern node
    s = cur_chr;
    scan_dimen(s == MU_GLUE, false, false);
    tail_append(new_kern(cur_val));
    subtype(tail) = s;
}

Section 1062

Many of the actions related to box-making are triggered by the appearance of braces in the input. For example, when the user says ‘\hbox to 100pt{<hlist>}’ in vertical mode, the information about the box size (100pt, EXACTLY) is put onto save_stack with a level boundary word just above it, and cur_groupADJUSTED_HBOX_GROUP; enters restricted horizontal mode to process the hlist. The right brace eventually causes save_stack to be restored to its former state, at which time the information about the box size (100pt, EXACTLY) is available once again; a box is packaged and we leave restricted horizontal mode, appending the new box to the current list of the enclosing mode (in this case to the current list of vertical mode), followed by any vertical adjustments that were removed from the box by hpack.

The next few sections of the program are therefore concerned with the treatment of left and right curly braces.

Section 1063

If a left brace occurs in the middle of a page or paragraph, it simply introduces a new level of grouping, and the matching right brace will not have such a drastic effect. Such grouping affects neither the mode nor the current list.

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

non_math(LEFT_BRACE):
    new_save_level(SIMPLE_GROUP);
    break;

any_mode(BEGIN_GROUP):
    new_save_level(SEMI_SIMPLE_GROUP);
    break;

any_mode(END_GROUP):
    if (cur_group == SEMI_SIMPLE_GROUP) {
        unsave();
    }
    else {
        off_save();
    }
    break;

Section 1064

We have to deal with errors in which braces and such things are not properly nested. Sometimes the user makes an error of commission by inserting an extra symbol, but sometimes the user makes an error of omission. can’t always tell one from the other, so it makes a guess and tries to avoid getting into a loop.

The off_save routine is called when the current group code is wrong. It tries to insert something into the user’s input that will help clean off the top level.

boxes_and_lists.c
void off_save() {
    pointer p; // inserted token
    if (cur_group == BOTTOM_LEVEL) {
        // << Drop current token and complain that it was unmatched, 1066 >>
    }
    else {
        back_input();
        p = get_avail();
        link(TEMP_HEAD) = p;
        print_err("Missing ");
        // << Prepare to insert a token that matches |cur_group|, and print what it is, 1065 >>
        print(" inserted");
        ins_list(link(TEMP_HEAD));
        help5("I've inserted something that you may have forgotten.")
            ("(See the <inserted text> above.)")
            ("With luck, this will get me unwedged. But if you")
            ("really didn't forget anything, try typing `2' now; then")
            ("my insertion and my current dilemma will both disappear.");
        error();
    }
}

Section 1065

At this point, link(TEMP_HEAD) = p, a pointer to an empty one-word node.

⟨ Prepare to insert a token that matches cur_group, and print what it is 1065 ⟩≡

switch (cur_group) {
case SEMI_SIMPLE_GROUP:
    info(p) = CS_TOKEN_FLAG + FROZEN_END_GROUP;
    print_esc("endgroup"); 
    break;

case MATH_SHIFT_GROUP:
    info(p) = MATH_SHIFT_TOKEN + '$';
    print_char('$');
    break;

case MATH_LEFT_GROUP:
    info(p) = CS_TOKEN_FLAG + FROZEN_RIGHT;
    link(p) = get_avail();
    p = link(p);
    info(p) = OTHER_TOKEN + '.';
    print_esc("right.");
    break;

default:
    info(p) = RIGHT_BRACE_TOKEN + '}';
    print_char('}');
}

Section 1066

⟨ Drop current token and complain that it was unmatched 1066 ⟩≡

print_err("Extra ");
print_cmd_chr(cur_cmd, cur_chr);
help1("Things are pretty mixed up, but I think the worst is over.");
error();

Section 1067

The routine for a RIGHT_BRACE character branches into many subcases, since a variety of things may happen, depending on cur_group. Some types of groups are not supposed to be ended by a right brace; error messages are given in hopes of pinpointing the problem. Most branches of this routine will be filled in later, when we are ready to understand them; meanwhile, we must prepare ourselves to deal with such errors.

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

any_mode(RIGHT_BRACE):
    handle_right_brace();
    break;

Section 1068

boxes_and_lists.c
void handle_right_brace() {
    pointer p, q; // for short - term use
    scaled d;     // holds |split_max_depth| in |insert_group|
    int f;        // holds |floating_penalty| in |insert_group|
    switch (cur_group) {
    case SIMPLE_GROUP:
        unsave();
        break;
    
    case BOTTOM_LEVEL:
        print_err("Too many }'s");
        help2("You've closed more groups than you opened.")
            ("Such booboos are generally harmless, so keep going.");
        error();
        break;
    
    case SEMI_SIMPLE_GROUP:
    case MATH_SHIFT_GROUP:
    case MATH_LEFT_GROUP:
        extra_right_brace();
        break;
    
    // << Cases of |handle_right_brace| where a |RIGHT_BRACE| triggers a delayed action, 1085 >>

    default:
        confusion("rightbrace");
    }
}

Section 1069

⟨ Declare action procedures for use by main_control 1043 ⟩+≡

void extra_right_brace() {
    print_err("Extra }, or forgotten ");
    switch (cur_group) {
    case SEMI_SIMPLE_GROUP:
        print_esc("endgroup");
        break;
    
    case MATH_SHIFT_GROUP:
        print_char('$');
        break;

    case MATH_LEFT_GROUP:
        print_esc("right");
    }
    help5("I've deleted a group-closing symbol because it seems to be")
        ("spurious, as in `$x}$'. But perhaps the } is legitimate and")
        ("you forgot something else, as in `\\hbox{$x}'. In such cases")
        ("the way to recover is to insert both the forgotten and the")
        ("deleted material, e.g., by typing `I$}'.");
    error();
    incr(align_state);
}

Section 1070

Here is where we clear the parameters that are supposed to revert to their default values after every paragraph and when internal vertical mode is entered.

boxes_and_lists.c
void normal_paragraph() {
    if (looseness != 0) {
        eq_word_define(INT_BASE + LOOSENESS_CODE, 0);
    }
    if (hang_indent != 0) {
        eq_word_define(DIMEN_BASE + HANG_INDENT_CODE, 0);
    }
    if (hang_after != 1) {
        eq_word_define(INT_BASE + HANG_AFTER_CODE, 1);
    }
    if (par_shape_ptr != null) {
        eq_define(PAR_SHAPE_LOC, SHAPE_REF, null);
    }
}

Section 1071

Now let’s turn to the question of how \hbox is treated. We actually need to consider also a slightly larger context, since constructions like ‘\setbox3=\hbox...’ and ‘\leaders\hbox...’ and ‘\lower3.8pt\hbox...’ are supposed to invoke quite different actions after the box has been packaged. Conversely, constructions like ‘\setbox3=’ can be followed by a variety of different kinds of boxes, and we would like to encode such things in an efficient way.

In other words, there are two problems: to represent the context of a box, and to represent its type.

The first problem is solved by putting a “context code” on the save_stack, just below the two entries that give the dimensions produced by scan_spec. The context code is either a (signed) shift amount, or it is a large integer BOX_FLAG, where BOX_FLAG = . Codes BOX_FLAG through BOX_FLAG + 255 represent ‘\setbox0’ through ‘\setbox255’; codes BOX_FLAG + 256 through BOX_FLAG + 511 represent ‘\global\setbox0’ through ‘\global\setbox255’; code BOX_FLAG + 512 represents ‘\shipout’; and codes BOX_FLAG + 513 through BOX_FLAG + 515 represent ‘\leaders’, ‘\cleaders’, and ‘\xleaders’.

The second problem is solved by giving the command code MAKE_BOX to all control sequences that produce a box, and by using the following chr_code values to distinguish between them: BOX_CODE, COPY_CODE, LAST_BOX_CODE, VSPLIT_CODE, VTOP_CODE, VTOP_CODE + VMODE, and VTOP_CODE + HMODE, where the latter two are used to denote \vbox and \hbox, respectively.

constants.h
#define BOX_FLAG      0x40000000       // context code for '\setbox0'
#define SHIP_OUT_FLAG (BOX_FLAG + 512) // context code for '\shipout'
#define LEADER_FLAG   (BOX_FLAG + 513) // context code for '\leaders'
#define BOX_CODE      0                // |chr_code| for '\box'
#define COPY_CODE     1                // |chr_code| for '\copy'
#define LAST_BOX_CODE 2                // |chr_code| for '\lastbox'
#define VSPLIT_CODE   3                // |chr_code| for '\vsplit'
#define VTOP_CODE     4                // |chr_code| for '\vtop'

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

primitive("moveleft", HMOVE, 1);
primitive("moveright", HMOVE, 0);
primitive("raise", VMOVE, 1);
primitive("lower", VMOVE, 0);

primitive("box", MAKE_BOX, BOX_CODE);
primitive("copy", MAKE_BOX, COPY_CODE);
primitive("lastbox", MAKE_BOX, LAST_BOX_CODE);
primitive("vsplit", MAKE_BOX, VSPLIT_CODE);
primitive("vtop", MAKE_BOX, VTOP_CODE);
primitive("vbox", MAKE_BOX, VTOP_CODE + VMODE);
primitive("hbox", MAKE_BOX, VTOP_CODE + HMODE);
primitive("shipout", LEADER_SHIP, A_LEADERS - 1); // |SHIP_OUT_FLAG = LEADER_FLAG - 1|
primitive("leaders", LEADER_SHIP, A_LEADERS);
primitive("cleaders", LEADER_SHIP, C_LEADERS);
primitive("xleaders", LEADER_SHIP, X_LEADERS);

Section 1072

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

case HMOVE:
    if (chr_code == 1) {
        print_esc("moveleft");
    }
    else {
        print_esc("moveright");
    }
    break;

case VMOVE:
    if (chr_code == 1) {
        print_esc("raise");
    }
    else {
        print_esc("lower");
    }
    break;

case MAKE_BOX:
    switch (chr_code) {
    case BOX_CODE:
        print_esc("box");
        break;
  
    case COPY_CODE:
        print_esc("copy");
        break;
    
    case LAST_BOX_CODE:
        print_esc("lastbox");
        break;
  
    case VSPLIT_CODE:
        print_esc("vsplit");
        break;
  
    case VTOP_CODE:
        print_esc("vtop");
        break;
  
    case VTOP_CODE + VMODE:
        print_esc("vbox");
        break;
    
    default:
        print_esc("hbox");
    }
    break;

case LEADER_SHIP:
    if (chr_code == A_LEADERS) {
        print_esc("leaders");
    }
    else if (chr_code == C_LEADERS) {
        print_esc("cleaders");
    }
    else if (chr_code == X_LEADERS) {
        print_esc("xleaders");
    }
    else {
        print_esc("shipout");
    }
    break;

Section 1073

Constructions that require a box are started by calling scan_box with a specified context code. The scan_box routine verifies that a MAKE_BOX command comes next and then it calls begin_box.

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

case VMODE + HMOVE:
case HMODE + VMOVE:
case MMODE + VMOVE:
    t = cur_chr;
    scan_normal_dimen;
    if (t == 0) {
        scan_box(cur_val);
    }
    else {
        scan_box(-cur_val);
    }
    break;

any_mode(LEADER_SHIP):
    scan_box(LEADER_FLAG - A_LEADERS + cur_chr);
    break;

any_mode(MAKE_BOX):
    begin_box(0);
    break;

Section 1074

The global variable cur_box will point to a newly made box. If the box is void, we will have cur_box = null. Otherwise we will have type(cur_box) = HLIST_NODE or VLIST_NODE or RULE_NODE; the RULE_NODE case can occur only with leaders.

⟨ Global variables 13 ⟩+≡

pointer cur_box; // box to be placed into its context

Section 1075

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

boxes_and_lists.c
void box_end(int box_context) {
    pointer p; // |ORD_NOAD| for new box in math mode
    if (box_context < BOX_FLAG) {
        // << Append box |cur_box| to the current list, shifted by |box_context|, 1076 >>
    }
    else if (box_context < SHIP_OUT_FLAG) {
        // << Store |cur_box| in a box register, 1077 >>
    }
    else if (cur_box != null) {
        if (box_context > SHIP_OUT_FLAG) {
            // << Append a new leader node that uses |cur_box|, 1078 >>
        }
        else {
            ship_out(cur_box);
        }
    }
}

Section 1076

The global variable adjust_tail will be non-null if and only if the current box might include adjustments that should be appended to the current vertical list.

⟨ Append box cur_box to the current list, shifted by box_context 1076 ⟩≡

if (cur_box != null) {
    shift_amount(cur_box) = box_context;
    if (abs(mode) == VMODE) {
        append_to_vlist(cur_box);
        if (adjust_tail != null) {
            if (ADJUST_HEAD != adjust_tail) {
                link(tail) = link(ADJUST_HEAD);
                tail = adjust_tail;
            }
            adjust_tail = null;
        }
        if (mode > 0) {
            build_page();
        }
    }
    else {
        if (abs(mode) == HMODE) {
            space_factor = 1000;
        }
        else {
            p = new_noad();
            math_type(nucleus(p)) = SUB_BOX;
            info(nucleus(p)) = cur_box;
            cur_box = p;
        }
        link(tail) = cur_box;
        tail = cur_box;
    }
}

Section 1077

⟨ Store cur_box in a box register 1077 ⟩≡

if (box_context < BOX_FLAG + 256) {
    eq_define(BOX_BASE - BOX_FLAG + box_context, BOX_REF, cur_box);
}
else {
    geq_define(BOX_BASE - BOX_FLAG - 256 + box_context, BOX_REF, cur_box);
}

Section 1078

⟨ Append a new leader node that uses cur_box 1078 ⟩≡

// << Get the next non-blank non-relax non-call token, 404 >>
if ((cur_cmd == HSKIP && abs(mode) != VMODE)
    || (cur_cmd == VSKIP && abs(mode) == VMODE))
{
    append_glue();
    subtype(tail) = box_context - (LEADER_FLAG - A_LEADERS);
    leader_ptr(tail) = cur_box;
}
else {
    print_err("Leaders not followed by proper glue");
    help3("You should say `\\leaders <box or rule><hskip or vskip>'.")
        ("I found the <box or rule>, but there's no suitable")
        ("<hskip or vskip>, so I'm ignoring these leaders.");
    back_error();
    flush_node_list(cur_box);
}

Section 1079

Now that we can see what eventually happens to boxes, we can consider the first steps in their creation. The begin_box routine is called when box_context is a context specification, cur_chr specifies the type of box desired, and cur_cmd = MAKE_BOX.

boxes_and_lists.c
void begin_box(int box_context) {
    pointer p, q; // run through the current list
    int m;        // the length of a replacement list
    halfword k;   // 0 or |VMODE| or |HMODE|
    eight_bits n; // a box number
    
    switch (cur_chr) {
    case BOX_CODE:
        scan_eight_bit_int();
        cur_box = box(cur_val);
        box(cur_val) = null; // the box becomes void, at the same level
        break;
    
    case COPY_CODE:
        scan_eight_bit_int();
        cur_box = copy_node_list(box(cur_val));
        break;
    
    case LAST_BOX_CODE:
        // << If the current list ends with a box node, delete it from the list and make |cur_box| point to it; otherwise set |cur_box = null|, 1080 >>
        break;
    
    case VSPLIT_CODE:
        // << Split off part of a vertical box, make |cur_box| point to it, 1082 >>
        break;
    
    default:
        // << Initiate the construction of an hbox or vbox, then |return|, 1083 >>
    }
    box_end(box_context); // in simple cases, we use the box immediately
}

Section 1080

Note that the condition ¬is_char_node(tail) implies that head tail, since head is a one-word node.

⟨ If the current list ends with a box node, delete it from the list and make cur_box point to it; otherwise set cur_box = null 1080 ⟩≡

cur_box = null;
if (abs(mode) == MMODE) {
    you_cant();
    help1("Sorry; this \\lastbox will be void.");
    error();
}
else if (mode == VMODE && head == tail) {
    you_cant();
    help2("Sorry...I usually can't take things from the current page.")
        ("This \\lastbox will therefore be void.");
    error();
}
else if (!is_char_node(tail)
    && (type(tail) == HLIST_NODE || type(tail) == VLIST_NODE))
{
    // << Remove the last box, unless it's part of a discretionary, 1081 >>
}

Section 1081

⟨ Remove the last box, unless it’s part of a discretionary 1081 ⟩≡

q = head;
do {
    p = q;
    if (!is_char_node(q) && type(q) == DISC_NODE) {
        for(m = 1; m <= replace_count(q); m++) {
            p = link(p);
        }
        if (p == tail) {
            goto done;
        }
    }
    q = link(p);
} while (q != tail);
cur_box = tail;
shift_amount(cur_box) = 0;
tail = p;
link(p) = null;
done:

Section 1082

Here we deal with things like ‘\vsplit 13 to 100pt’.

⟨ Split off part of a vertical box, make cur_box point to it 1082 ⟩≡

scan_eight_bit_int();
n = cur_val;
if (!scan_keyword("to")) {
    print_err("Missing `to' inserted");
    help2("I'm working on `\\vsplit<box number> to <dimen>';")
        ("will look for the <dimen> next.");
    error();
}
scan_normal_dimen;
cur_box = vsplit(n, cur_val);

Section 1083

Here is where we enter restricted horizontal mode or internal vertical mode, in order to make a box.

⟨ Initiate the construction of an hbox or vbox, then return 1083 ⟩≡

k = cur_chr - VTOP_CODE;
saved(0) = box_context;
if (k == HMODE) {
    if (box_context < BOX_FLAG && abs(mode) == VMODE) {
        scan_spec(ADJUSTED_HBOX_GROUP, true);
    }
    else {
        scan_spec(HBOX_GROUP, true);
    }
}
else {
    if (k == VMODE) {
        scan_spec(VBOX_GROUP, true);
    }
    else {
        scan_spec(VTOP_GROUP, true);
        k = VMODE;
    }
    normal_paragraph();
}
push_nest();
mode = -k;
if (k == VMODE) {
    prev_depth = IGNORE_DEPTH;
    if (every_vbox != null) {
        begin_token_list(every_vbox, EVERY_VBOX_TEXT);
    }
}
else {
    space_factor = 1000;
    if (every_hbox != null) {
        begin_token_list(every_hbox, EVERY_HBOX_TEXT);
    }
}
return;

Section 1084

boxes_and_lists.c
// the next input should specify a box or perhaps a rule
void scan_box(int box_context) {
    // << Get the next non-blank non-relax non-call token, 404 >>
    if (cur_cmd == MAKE_BOX) {
        begin_box(box_context);
    }
    else if (box_context >= LEADER_FLAG
        && (cur_cmd == HRULE || cur_cmd == VRULE))
    {
        cur_box = scan_rule_spec();
        box_end(box_context);
    }
    else {
        print_err("A <box> was supposed to be here");
        help3("I was expecting to see \\hbox or \\vbox or \\copy or \\box or")
            ("something like that. So you might find something missing in")
            ("your output. But keep trying; you can fix this later.");
        back_error();
    }
}

Section 1085

When the right brace occurs at the end of an \hbox or \vbox or \vtop construction, the package routine comes into action. We might also have to finish a paragraph that hasn’t ended.

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

case HBOX_GROUP:
    package(0);
    break;

case ADJUSTED_HBOX_GROUP:
    adjust_tail = ADJUST_HEAD;
    package(0);
    break;

case VBOX_GROUP:
    end_graf();
    package(0);
    break;

case VTOP_GROUP:
    end_graf();
    package(VTOP_CODE);
    break;

Section 1086

boxes_and_lists.c
void package(small_number c) {
    scaled h;  // height of box
    pointer p; // first node in a box
    scaled d;  // max depth
    d = box_max_depth;
    unsave();
    save_ptr -= 3;
    if (mode == -HMODE) {
        cur_box = hpack(link(head), saved(2), saved(1));
    }
    else {
        cur_box = vpackage(link(head), saved(2), saved(1), d);
        if (c == VTOP_CODE) {
            // << Readjust the height and depth of |cur_box|, for \vtop, 1087 >>
        }
    }
    pop_nest();
    box_end(saved(0));
}

Section 1087

The height of a ‘\vtop’ box is inherited from the first item on its list, if that item is an HLIST_NODE, VLIST_NODE, or RULE_NODE; otherwise the \vtop height is zero.

⟨ Readjust the height and depth of cur_box, for \vtop 1087 ⟩≡

h = 0;
p = list_ptr(cur_box);
if (p != null && type(p) <= RULE_NODE) {
    h = height(p);
}
depth(cur_box) += (-h + height(cur_box));
height(cur_box) = h;

Section 1088

A paragraph begins when horizontal-mode material occurs in vertical mode, or when the paragraph is explicitly started by ‘\indent’ or ‘\noindent’.

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

primitive("indent", START_PAR, 1);
primitive("noindent", START_PAR, 0);

Section 1089

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

case START_PAR:
    if (chr_code == 0) {
        print_esc("noindent");
    }
    else {
        print_esc("indent");
    }
    break;

Section 1090

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

case VMODE + START_PAR:
    new_graf(cur_chr > 0);
    break;

case VMODE + LETTER:
case VMODE + OTHER_CHAR:
case VMODE + CHAR_NUM:
case VMODE + CHAR_GIVEN:
case VMODE + MATH_SHIFT:
case VMODE + UN_HBOX:
case VMODE + VRULE:
case VMODE + ACCENT:
case VMODE + DISCRETIONARY:
case VMODE + HSKIP:
case VMODE + VALIGN:
case VMODE + EX_SPACE:
case VMODE + NO_BOUNDARY:
    back_input();
    new_graf(true);
    break;

Section 1091

boxes_and_lists.c
small_number norm_min(int h) {
    if (h <= 0) {
        return 1;
    }
    if (h >= 63) {
        return 63;
    }
    return h;
}

void new_graf(bool indented) {
    prev_graf = 0;
    if (mode == VMODE || head != tail) {
        tail_append(new_param_glue(PAR_SKIP_CODE));
    }
    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;
    if (indented) {
        tail = new_null_box();
        link(head) = tail;
        width(tail) = par_indent;    
    }
    if (every_par != null) {
        begin_token_list(every_par, EVERY_PAR_TEXT);
    }
    if (nest_ptr == 1) {
        build_page(); // put |par_skip| glue on current page
    }
}

Section 1092

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

case HMODE + START_PAR:
case MMODE + START_PAR:
    indent_in_hmode();
    break;

Section 1093

boxes_and_lists.c
void indent_in_hmode() {
    pointer p, q;
    if (cur_chr > 0) {
        // \indent
        p = new_null_box();
        width(p) = par_indent;
        if (abs(mode) == HMODE) {
            space_factor = 1000;
        }
        else {
            q = new_noad();
            math_type(nucleus(q)) = SUB_BOX;
            info(nucleus(q)) = p;
            p = q;
        }
        tail_append(p);
    }
}

Section 1094

A paragraph ends when a PAR_END command is sensed, or when we are in horizontal mode when reaching the right brace of vertical-mode routines like \vbox, \insert, or \output.

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

case VMODE + PAR_END:
    normal_paragraph();
    if (mode > 0) {
        build_page();
    }
    break;

case HMODE + PAR_END:
    if (align_state < 0) {
        off_save(); // this tries to recover from an alignment that didn't end properly
    }
    end_graf(); // this takes us to the enclosing mode, if |mode > 0|
    if (mode == VMODE) {
        build_page();
    }
    break;

case HMODE + STOP:
case HMODE + VSKIP:
case HMODE + HRULE:
case HMODE + UN_VBOX:
case HMODE + HALIGN:
    head_for_vmode();
    break;

Section 1095

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

Section 1096

boxes_and_lists.c
void end_graf() {
    if (mode == HMODE) {
        if (head == tail) {
            pop_nest(); // null paragraphs are ignored
        }
        else {
            line_break(widow_penalty);
        }
        normal_paragraph();
        error_count = 0;
    }
}

Section 1097

Insertion and adjustment and mark nodes are constructed by the following pieces of the program.

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

any_mode(INSERT):
case HMODE + VADJUST:
case MMODE + VADJUST:
    begin_insert_or_adjust();
    break;

any_mode(MARK):
    make_mark();
    break;

Section 1098

⟨ Forbidden cases detected in main_control 1048 ⟩+≡

case VMODE + VADJUST:

Section 1099

boxes_and_lists.c
void begin_insert_or_adjust() {
    if (cur_cmd == VADJUST) {
        cur_val = 255;
    }
    else {
        scan_eight_bit_int();
        if (cur_val == 255) {
            print_err("You can't ");
            print_esc("insert");
            print_int(255);
            help1("I'm changing to \\insert0; box 255 is special.");
            error();
            cur_val = 0;
        }
    }
    saved(0) = cur_val;
    incr(save_ptr);
    new_save_level(INSERT_GROUP);
    scan_left_brace();
    normal_paragraph();
    push_nest();
    mode = -VMODE;
    prev_depth = IGNORE_DEPTH;
}

Section 1100

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

case INSERT_GROUP:
    end_graf();
    q = split_top_skip;
    add_glue_ref(q);
    d = split_max_depth;
    f = floating_penalty;
    unsave();
    decr(save_ptr);
    // now |saved(0)| is the insertion number, or 255 for |vadjust|
    p = vpack(link(head), NATURAL);
    pop_nest();
    if (saved(0) < 255) {
        tail_append(get_node(INS_NODE_SIZE));
        type(tail) = INS_NODE;
        subtype(tail) = saved(0);
        height(tail) = height(p) + depth(p);
        ins_ptr(tail) = list_ptr(p);
        split_top_ptr(tail) = q;
        depth(tail) = d;
        float_cost(tail) = f;
    }
    else {
        tail_append(get_node(SMALL_NODE_SIZE));
        type(tail) = ADJUST_NODE;
        subtype(tail) = 0; // the |subtype| is not used
        adjust_ptr(tail) = list_ptr(p);
        delete_glue_ref(q);
    }
    free_node(p, BOX_NODE_SIZE);
    if (nest_ptr == 0) {
        build_page();
    }
    break;

case OUTPUT_GROUP:
    // << Resume the page builder after an output routine has come to an end, 1026 >>
    break;

Section 1101

boxes_and_lists.c
void make_mark() {
    pointer p; // new node
    p = scan_toks(false, true);
    p = get_node(SMALL_NODE_SIZE);
    type(p) = MARK_NODE;
    subtype(p) = 0; // the |subtype| is not used
    mark_ptr(p) = def_ref;
    link(tail) = p;
    tail = p;
}

Section 1102

Penalty nodes get into a list via the BREAK_PENALTY command.

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

any_mode(BREAK_PENALTY):
    append_penalty();
    break;

Section 1103

boxes_and_lists.c
void append_penalty() {
    scan_int();
    tail_append(new_penalty(cur_val));
    if (mode == VMODE) {
        build_page();
    }
}

Section 1104

The REMOVE_ITEM command removes a penalty, kern, or glue node if it appears at the tail of the current list, using a brute-force linear scan. Like \lastbox, this command is not allowed in vertical mode (except internal vertical mode), since the current list in vertical mode is sent to the page builder. But if we happen to be able to implement it in vertical mode, we do.

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

any_mode(REMOVE_ITEM):
    delete_last();
    break;

Section 1105

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

boxes_and_lists.c
void delete_last() {
    pointer p, q; // run through the current list
    int m;        // the length of a replacement list
    if (mode == VMODE && tail == head) {
        // << Apologize for inability to do the operation now, unless \unskip follows non-glue, 1106 >>
    }
    else if (!is_char_node(tail) && type(tail) == cur_chr) {
        q = head;
        do {
            p = q;
            if (!is_char_node(q) && type(q) == DISC_NODE) {
                for(m = 1; m <= replace_count(q); m++) {
                    p = link(p);
                }
                if (p == tail) {
                    return;
                }
            }
            q = link(p);
        } while (q != tail);
        link(p) = null;
        flush_node_list(tail);
        tail = p;
    }
}

Section 1106

⟨ Apologize for inability to do the operation now, unless \unskip follows non-glue 1106 ⟩≡

if (cur_chr != GLUE_NODE || last_glue != MAX_HALFWORD) {
    you_cant();
    help2("Sorry...I usually can't take things from the current page.")
        ("Try `I\\vskip-\\lastskip' instead.");
    if (cur_chr == KERN_NODE) {
        help_line[0] = "Try `I\\kern-\\lastkern' instead.";
    }
    else if (cur_chr != GLUE_NODE) {
        help_line[0] = "Perhaps you can make the output routine do it.";
    }
    error();
}

Section 1107

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

primitive("unpenalty", REMOVE_ITEM, PENALTY_NODE);
primitive("unkern", REMOVE_ITEM, KERN_NODE);
primitive("unskip", REMOVE_ITEM, GLUE_NODE);
primitive("unhbox", UN_HBOX, BOX_CODE);
primitive("unhcopy", UN_HBOX, COPY_CODE);
primitive("unvbox", UN_VBOX, BOX_CODE);
primitive("unvcopy", UN_VBOX, COPY_CODE);

Section 1108

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

case REMOVE_ITEM:
    if (chr_code == GLUE_NODE) {
        print_esc("unskip");
    }
    else if (chr_code == KERN_NODE) {
        print_esc("unkern");
    }
    else {
        print_esc("unpenalty");
    }
    break;

case UN_HBOX:
    if (chr_code == COPY_CODE) {
        print_esc("unhcopy");
    }
    else {
        print_esc("unhbox");
    }
    break;

case UN_VBOX:
    if (chr_code == COPY_CODE) {
        print_esc("unvcopy");
    }
    else {
        print_esc("unvbox");
    }
    break;

Section 1109

The UN_HBOX and UN_VBOX commands unwrap one of the 256 current boxes.

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

case VMODE + UN_VBOX:
case HMODE + UN_HBOX:
case MMODE + UN_HBOX:
    unpackage();
    break;

Section 1110

boxes_and_lists.c
void unpackage() {
    pointer p; // the box
    int c;     // should we copy?
    c = cur_chr;
    scan_eight_bit_int();
    p = box(cur_val);
    if (p == null) {
        return;
    }
    if (abs(mode) == MMODE
        || (abs(mode) == VMODE && type(p) != VLIST_NODE)
        || (abs(mode) == HMODE && type(p) != HLIST_NODE))
    {
        print_err("Incompatible list can't be unboxed");
        help3("Sorry, Pandora. (You sneaky devil.)")
            ("I refuse to unbox an \\hbox in vertical mode or vice versa.")
            ("And I can't open any boxes in math mode.");
        error();
        return;
    }
    if (c == COPY_CODE) {
        link(tail) = copy_node_list(list_ptr(p));
    }
    else {
        link(tail) = list_ptr(p);
        box(cur_val) = null;
        free_node(p, BOX_NODE_SIZE);
    }
    while (link(tail) != null) {
        tail = link(tail);
    }
}

Section 1111

⟨ Forbidden cases detected in main_control 1048 ⟩+≡

case VMODE + ITAL_CORR:

Section 1112

Italic corrections are converted to kern nodes when the ITAL_CORR command follows a character. In math mode the same effect is achieved by appending a kern of zero here, since italic corrections are supplied later.

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

case HMODE + ITAL_CORR:
    append_italic_correction();
    break;

case MMODE + ITAL_CORR:
    tail_append(new_kern(0));
    break;

Section 1113

boxes_and_lists.c
void append_italic_correction() {
    pointer p;              // |CHAR_NODE| at the tail of the current list
    internal_font_number f; // the font in the |CHAR_NODE|
    if (tail != head) {
        if (is_char_node(tail)) {
            p = tail;
        }
        else if (type(tail) == LIGATURE_NODE) {
            p = lig_char(tail);
        }
        else {
            return;
        }
        f = font(p);
        tail_append(new_kern(char_italic(f, char_info(f, character(p)))));
        subtype(tail) = EXPLICIT;
    }
}

Section 1114

Discretionary nodes are easy in the common case ‘\-’, but in the general case we must process three braces full of items.

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

primitive("-", DISCRETIONARY, 1);
primitive("discretionary", DISCRETIONARY, 0);

Section 1115

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

case DISCRETIONARY:
    if (chr_code == 1) {
        print_esc("-");
    }
    else {
        print_esc("discretionary");
    }
    break;

Section 1116

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

case HMODE + DISCRETIONARY:
case MMODE + DISCRETIONARY:
    append_discretionary();
    break;

Section 1117

The space factor does not change when we append a discretionary node, but it starts out as 1000 in the subsidiary lists.

boxes_and_lists.c
void append_discretionary() {
    int c; // hyphen character
    tail_append(new_disc());
    if (cur_chr == 1) {
        c = hyphen_char[cur_font];
        if (c >= 0 && c < 256) {
            pre_break(tail) = new_character(cur_font, c);
        }
    }
    else {
        incr(save_ptr);
        saved(-1) = 0;
        new_save_level(DISC_GROUP);
        scan_left_brace();
        push_nest();
        mode = -HMODE;
        space_factor = 1000;
    }
}

Section 1118

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

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

case DISC_GROUP:
    build_discretionary();
    break;

Section 1119

boxes_and_lists.c
void build_discretionary() {
    pointer p, q; // for link manipulation
    int n;        // length of discretionary list
    unsave();
    // << Prune the current list, if necessary, until it contains only |CHAR_NODE|, |KERN_NODE|, |HLIST_NODE|, |VLIST_NODE|, |RULE_NODE|, and |LIGATURE_NODE| items; set |n| to the length of the list, and set |q| to the list's tail, 1121 >>
    p = link(head);
    pop_nest();
    switch (saved(-1)) {
    case 0:
        pre_break(tail) = p;
        break;
    
    case 1:
        post_break(tail) = p;
        break;
    
    case 2:
        // << Attach list |p| to the current list, and record its length; then finish up and |return|, 1120 >>
    } // there are no other cases
    incr(saved(-1));
    new_save_level(DISC_GROUP);
    scan_left_brace();
    push_nest();
    mode = -HMODE;
    space_factor = 1000;
}

Section 1120

⟨ Attach list p to the current list, and record its length; then finish up and return 1120 ⟩≡

if (n > 0 && abs(mode) == MMODE) {
    print_err("Illegal math ");
    print_esc("discretionary");
    help2("Sorry: The third part of a discretionary break must be")
        ("empty, in math formulas. I had to delete your third part.");
    flush_node_list(p);
    n = 0;
    error();
}
else {
    link(tail) = p;
}
if (n <= MAX_QUARTERWORD) {
    replace_count(tail) = n;
}
else {
    print_err("Discretionary list is too long");
    help2("Wow---I never thought anybody would tweak me here.")
        ("You can't seriously need such a huge discretionary list?");
    error();
}
if (n > 0) {
    tail = q;
}
decr(save_ptr);
return;

Section 1121

During this loop, p = link(q) and there are n items preceding p.

⟨ Prune the current list, if necessary, until it contains only CHAR_NODE, KERN_NODE, HLIST_NODE, VLIST_NODE, RULE_NODE, and LIGATURE_NODE items; set n to the length of the list, and set q to the list’s tail 1121 ⟩≡

q = head;
p = link(q);
n = 0;
while (p != null) {
    if (!is_char_node(p)
        && type(p) > RULE_NODE
        && type(p) != KERN_NODE
        && type(p) != LIGATURE_NODE)
    {
        print_err("Improper discretionary list");
        help1("Discretionary lists must contain only boxes and kerns.");
        error();
        begin_diagnostic();
        print_nl("The following discretionary sublist has been deleted:");
        show_box(p);
        end_diagnostic(true);
        flush_node_list(p);
        link(q) = null;
        break; // goto done
    }
    q = p;
    p = link(q);
    incr(n);
}
// done:

Section 1122

We need only one more thing to complete the horizontal mode routines, namely the \accent primitive.

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

case HMODE + ACCENT:
    make_accent();
    break;

Section 1123

The positioning of accents is straightforward but tedious. Given an accent of width a, designed for characters of height x and slant s; and given a character of width w, height h, and slant t: We will shift the accent down by x − h, and we will insert kern nodes that have the effect of centering the accent over the character and shifting the accent to the right by = (wa) + h tx s. If either character is absent from the font, we will simply use the other, without shifting.

boxes_and_lists.c
void make_accent() {
    double s, t;              // amount of slant
    pointer p, q, r;          // character, box, and kern nodes
    internal_font_number f;   // relevant font
    scaled a, h, x, w, delta; // heights and widths, as explained above
    memory_word i;            // character information
    
    scan_char_num();
    f = cur_font;
    p = new_character(f, cur_val);
    if (p != null) {
        x = x_height(f);
        s = slant(f) / 65536.0;
        a = char_width(f, char_info(f, character(p)));
        do_assignments();
        // << Create a character node |q| for the next character, but set |q = null| if problems arise, 1124 >>
        if (q != null) {
            // << Append the accent with appropriate kerns, then set |p = q|, 1125 >>
        }
        link(tail) = p;
        tail = p;
        space_factor = 1000;
    }
}

Section 1124

⟨ Create a character node q for the next character, but set q = null if problems arise 1124 ⟩≡

q = null;
f = cur_font;
if (cur_cmd == LETTER
    || cur_cmd == OTHER_CHAR
    || cur_cmd == CHAR_GIVEN)
{
    q = new_character(f, cur_chr);
}
else if (cur_cmd == CHAR_NUM) {
    scan_char_num();
    q = new_character(f, cur_val);
}
else {
    back_input();
}

Section 1125

The kern nodes appended here must be distinguished from other kerns, lest they be wiped away by the hyphenation algorithm or by a previous line break.

The two kerns are computed with (machine-dependent) real arithmetic, but their sum is machine-independent; the net effect is machine-independent, because the user cannot remove these nodes nor access them via \lastkern.

⟨ Append the accent with appropriate kerns, then set p = q 1125 ⟩≡

t = slant(f) / 65536.0;
i = char_info(f, character(q));
w = char_width(f, i);
h = char_height(f, height_depth(i));
if (h != x) {
    // the accent must be shifted up or down
    p = hpack(p, NATURAL);
    shift_amount(p) = x - h;
}
delta = (scaled)round((w - a)/2.0 + h*t - x*s);
r = new_kern(delta);
subtype(r) = ACC_KERN;
link(tail) = r;
link(r) = p;
tail = new_kern(-a - delta);
subtype(tail) = ACC_KERN;
link(p) = tail;
p = q;

Section 1126

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

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

any_mode(CAR_RET):
any_mode(TAB_MARK):
    align_error();
    break;

any_mode(NO_ALIGN):
    no_align_error();
    break;

any_mode(OMIT):
    omit_error();
    break;

Section 1127

error.c
void align_error() {
    if (abs(align_state) > 2) {
        // << Express consternation over the fact that no alignment is in progress, 1128 >>
    }
    else {
        back_input();
        if (align_state < 0) {
            print_err("Missing { inserted");
            incr(align_state);
            cur_tok = LEFT_BRACE_TOKEN + '{';
        }
        else {
            print_err("Missing } inserted");
            decr(align_state);
            cur_tok = RIGHT_BRACE_TOKEN + '}';
        }
        help3("I've put in what seems to be necessary to fix")
            ("the current column of the current alignment.")
            ("Try to go on, since this might almost work.");
        ins_error();
    }
}

Section 1128

⟨ Express consternation over the fact that no alignment is in progress 1128 ⟩≡

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

Section 1129

The help messages here contain a little white lie, since \noalign and \omit are allowed also after ‘\noalign{...}’.

⟨ Declare action procedures for use by main_control 1043 ⟩+≡

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

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

Section 1130

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

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

case VMODE + HALIGN:
case HMODE + VALIGN:
    init_align();
    break;

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

case VMODE + ENDV:
case HMODE + ENDV:
    do_endv();
    break;

Section 1131

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

A devious user might force an ENDV command to occur just about anywhere; we must defeat such hacks.

boxes_and_lists.c
void do_endv() {
    base_ptr = input_ptr;
    input_stack[base_ptr] = cur_input;
    while (input_stack[base_ptr].index_field != V_TEMPLATE
        && input_stack[base_ptr].loc_field == null
        && input_stack[base_ptr].state_field == TOKEN_LIST)
    {
        decr(base_ptr);
    }
    if (input_stack[base_ptr].index_field != V_TEMPLATE
        || input_stack[base_ptr].loc_field != null
        || input_stack[base_ptr].state_field != TOKEN_LIST)
    {
        fatal_error("(interwoven alignment preambles are not allowed)");
    }
    if (cur_group == ALIGN_GROUP) {
        end_graf();
        if (fin_col()) {
            fin_row();
        }
    }
    else {
        off_save();
    }
}

Section 1132

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

case ALIGN_GROUP:
    back_input();
    cur_tok = CS_TOKEN_FLAG + FROZEN_CR;
    print_err("Missing ");
    print_esc("cr");
    print(" inserted");
    help1("I'm guessing that you meant to end an alignment here.");
    ins_error();
    break;

Section 1133

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

case NO_ALIGN_GROUP:
    end_graf();
    unsave();
    align_peek();
    break;

Section 1134

Finally, \endcsname is not supposed to get through to main_control.

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

any_mode(END_CS_NAME):
    cs_error();
    break;

Section 1135

⟨ Declare action procedures for use by main_control 1043 ⟩+≡

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