texalign.c /size: 104 Kb    last modification: 2025-02-21 11:03
1/*
2    See license.txt in the root of this project.
3*/
4
5# include "luametatex.h"
6
7/*tex
8
9    It's sort of a miracle whenever |halign and |valign| work, because they cut across so many of
10    the control structures of \TEX. Therefore the present page is probably not the best place for
11    a beginner to start reading this program; it is better to master everything else first.
12
13    Let us focus our thoughts on an example of what the input might be, in order to get some idea
14    about how the alignment miracle happens. The example doesn't do anything useful, but it is
15    sufficiently general to indicate all of the special cases that must be dealt with; please do
16    not be disturbed by its apparent complexity and meaninglessness.
17
18    \starttyping
19    \tabskip 2pt plus 3pt
20    \halign to 300pt{u1#v1&
21    \hskip 50pt \tabskip 1pt plus 1fil u2#v2&
22    \hskip 50pt u3#v3\cr
23    \hskip 25pt a1&\omit a2&\vrule\cr
24    \hskip 25pt \noalign\{\vskip 3pt}
25    \hskip 25pt b1\span b2\cr
26    \hskip 25pt \omit&c2\span\omit\cr}
27    \stoptyping
28
29    Here's what happens:
30
31    \startitemize
32
33        \startitem
34            When |\halign to 300pt {}| is scanned, the |scan_align_spec| routine places the 300pt
35            dimension onto the |save_stack|, and an |align_group| code is placed above it. This
36            will make it possible to complete the alignment when the matching right brace is found.
37        \stopitem
38
39        \startitem
40            The preamble is scanned next. Macros in the preamble are not expanded, except as part
41            of a tabskip specification. For example, if |u2| had been a macro in the preamble above,
42            it would have been expanded, since \TEX\ must look for |minus ...| as part of the
43            tabskip glue. A preamble list is constructed based on the user's preamble; in our case
44            it contains the following seven items:
45
46            \starttabulate
47            \NC \type{\glue 2pt plus 3pt}              \NC the tabskip preceding column 1      \NC \NR
48            \NC \type{\alignrecord} of width $-\infty$ \NC preamble info for column 1          \NC \NR
49            \NC \type{\glue 2pt plus 3pt}              \NC the tabskip between columns 1 and 2 \NC \NR
50            \NC \type{\alignrecord} of width $-\infty$ \NC preamble info for column 2          \NC \NR
51            \NC \type{\glue 1pt plus 1fil}             \NC the tabskip between columns 2 and 3 \NC \NR
52            \NC \type{\alignrecord} of width $-\infty$ \NC preamble info for column 3          \NC \NR
53            \NC \type{\glue 1pt plus 1fil}             \NC the tabskip following column 3      \NC \NR
54            \stoptabulate
55
56            These \quote {alignrecord} entries have the same size as an |unset_node|, since they
57            will later be converted into such nodes. These alignrecord nodes have no |depth| field;
58            this is split into |u_part| and |v_part|, and they point to token lists for the
59            templates of the alignment. For example, the |u_part| field in the first alignrecord
60            points to the token list |u1|, i.e., the template preceding the \type {#} for column~1.
61            Furthermore, They have a |span_ptr| instead of a |node_attr| field, and these |span_ptr|
62            fields are initially set to the value |end_span|, for reasons explained below.
63        \stopitem
64
65        \startitem
66            \TEX\ now looks at what follows the |\cr| that ended the preamble. It is not |\noalign|
67            or |\omit|, so this input is put back to be read again, and the template |u1| is fed to
68            the scanner. Just before reading |u1|, \TeX\ goes into restricted horizontal mode. Just
69            after reading |u1|, \TEX\ will see |a1|, and then (when the |&| is sensed) \TEX\ will
70            see |v1|. Then \TEX\ scans an |end_template| token, indicating the end of a column. At
71            this point an |unset_node| is created, containing the contents of the current hlist
72            (i.e., |u1a1v1|). The natural width of this unset node replaces the |width| field of
73            the alignrecord for column~1; in general, the alignrecords will record the maximum
74            natural width that has occurred so far in a given column.
75        \stopitem
76
77        \startitem
78            Since |\omit| follows the |&|, the templates for column~2 are now bypassed. Again \TEX\
79            goes into restricted horizontal mode and makes an |unset_node| from the resulting hlist;
80            but this time the hlist contains simply |a2|. The natural width of the new unset box is
81            remembered in the |width| field of the alignrecord for column~2.
82        \stopitem
83
84        \startitem
85            A third |unset_node| is created for column 3, using essentially the mechanism that
86            worked for column~1; this unset box contains |u3\vrule v3|. The vertical rule in this
87            case has running dimensions that will later extend to the height and depth of the whole
88            first row, since each |unset_node| in a row will eventually inherit the height and depth
89            of its enclosing box.
90        \stopitem
91
92        \startitem
93            The first row has now ended; it is made into a single unset box comprising the following
94            seven items:
95
96            \starttyping
97            \glue 2pt plus 3pt
98            \unsetbox for 1 column: u1a1v1
99            \glue 2pt plus 3pt
100            \unsetbox for 1 column: a2
101            \glue 1pt plus 1fil
102            \unsetbox for 1 column: u3\vrule v3
103            \glue 1pt plus 1fil
104            \stoptyping
105
106            The width of this unset row is unimportant, but it has the correct height and depth, so
107            the correct baselineskip glue will be computed as the row is inserted into a vertical
108            list.
109        \stopitem
110
111        \startitem
112            Since |\noalign| follows the current |\cr|, \TEX\ appends additional material (in this
113            case |\vskip 3pt|) to the vertical list. While processing this material, \TeX\ will be
114            in internal vertical mode, and |no_align_group| will be on |save_stack|.
115        \stopitem
116
117        \startitem
118            The next row produces an unset box that looks like this:
119
120            \starttyping
121            \glue 2pt plus 3pt
122            \unsetbox for 2 columns: u1b1v1u2b2v2
123            \glue 1pt plus 1fil
124            \unsetbox for 1 column: {(empty)}
125            \glue 1pt plus 1fil
126            \stoptyping
127
128            The natural width of the unset box that spans columns 1~and~2 is stored in a \quote
129            {span node}, which we will explain later; the |span_ptr| field of the alignrecord for
130            column~1 now points to the new span node, and the |span_ptr| of the span node points to
131            |end_span|.
132        \stopitem
133
134        \startitem
135
136            The final row produces the unset box
137
138            \starttyping
139            \glue 2pt plus 3pt
140            \unsetbox for 1 column: (empty)
141            \glue 2pt plus 3pt
142            \unsetbox for 2 columns: u2c2v2
143            \glue 1pt plus 1fil
144            \stoptyping
145
146            A new span node is attached to the align record for column 2.
147        \stopitem
148
149        \startitem
150            The last step is to compute the true column widths and to change all the unset boxes to
151            hboxes, appending the whole works to the vertical list that encloses the |\halign|. The
152            rules for deciding on the final widths of each unset column box will be explained below.
153        \stopitem
154
155    \stopitemize
156
157    Note that as |\halign| is being processed, we fearlessly give up control to the rest of \TEX. At
158    critical junctures, an alignment routine is called upon to step in and do some little action, but
159    most of the time these routines just lurk in the background. It's something like post-hypnotic
160    suggestion.
161
162    We have mentioned that alignrecords contain no |height| or |depth| fields. Their |glue_sign| and
163    |glue_order| are pre-empted as well, since it is necessary to store information about what to do
164    when a template ends. This information is called the |extra_info| field.
165
166    Alignments can occur within alignments, so a small stack is used to access the alignrecord
167    information. At each level we have a |preamble| pointer, indicating the beginning of the
168    preamble list; a |cur_align| pointer, indicating the current position in the preamble list; a
169    |cur_span| pointer, indicating the value of |cur_align| at the beginning of a sequence of
170    spanned columns; a |cur_loop| pointer, indicating the tabskip glue before an alignrecord that
171    should be copied next if the current list is extended; and the |align_state| variable, which
172    indicates the nesting of braces so that |\cr| and |\span| and tab marks are properly
173    intercepted. There also are pointers |cur_head| and |cur_tail| to the head and tail of a list
174    of adjustments being moved out from horizontal mode to vertical~mode, and alike |cur_pre_head|
175    and |cur_pre_tail| for pre-adjust lists.
176
177    The current values of these nine quantities appear in global variables; when they have to be
178    pushed down, they are stored in 6-word nodes, and |align_ptr| points to the topmost such node.
179
180*/
181
182/*tex
183
184    So far, hardly anything has been added to the alignment code so the above, original \TEX\
185    the program documentation still applies. Of course we have callbacks. Attributes are a bit
186    complicating here. I experimented with some row and cell specific ones but grouping will always
187    make it messy. One never knows what a preamble injects. So leaving it as-is is better than a
188    subtoptimal solution with side effects. To mention one aspect: we have unset nodes that use the
189    attribute fields for other purposes and get adapted later on anyway. I'll look into it again
190    at some point.
191
192    Contrary to other mechanisms, there are not that many extensions. One is that we can nest
193    |\noalign| (so we don't need kludges at the macro level). The look ahead trickery has not been
194    changed but we might get some variants (we have protected macros so it's not as sensitive as
195    it was in the past.
196
197    The |\tabsize| feature is experimental and possibly a prelude to more. I played with that
198    when a test file (korean font table) was allocating so many nodes that I wondered if we could
199    limit that (and redundant boxes and glue are the only things we can do here). It actually
200    also saves a bit of runtime. This feature has not been tested yet with |\span| and |\omit|.
201
202    The |\noalign| command accepts a couple of keywords that specify options to be applied to the 
203    next row. These options are similar to the ones for boxes. 
204
205    Maybe: lefttabskip righttabskip middletabskip
206
207*/
208
209typedef enum align_options {
210    align_option_exactly  = 0x01, 
211    align_option_reverse  = 0x10, 
212    align_option_discard  = 0x20, 
213    align_option_noskips  = 0x40, 
214    align_option_callback = 0x80, 
215} align_options;
216
217/* Do we still need these .. we have a dedicated stack. */
218
219typedef enum saved_align_entries {
220    saved_align_mode_entry     = 0,
221    saved_align_amount_entry   = 0,
222    saved_align_callback_entry = 0,
223    saved_align_n_of_records   = 1,
224} saved_align_entries;
225
226# define saved_align_mode     saved_value_1(saved_align_mode_entry)
227# define saved_align_amount   saved_value_2(saved_align_amount_entry)
228# define saved_align_callback saved_value_3(saved_align_amount_entry)
229
230static inline void saved_alignment_initialize(void)
231{
232    saved_type(0) = saved_record_0;
233    saved_record(0) = alignment_save_type;
234}
235
236int tex_show_alignment_record(void)
237{
238    tex_print_str("alignment ");
239    switch (saved_type(0)) { 
240       case saved_record_0:
241            tex_print_format("mode %i, amount %p", saved_value_1(0), saved_value_2(0));
242            break;
243        default: 
244            return 0;
245    }
246    return 1;
247}
248
249typedef struct alignment_row_state { 
250    halfword orientation;
251    scaled   xoffset;
252    scaled   yoffset;
253    scaled   xmove;
254    scaled   ymove;
255    halfword shift;
256    halfword source;
257    halfword target;
258    halfword anchor;
259    halfword attrlist; 
260} alignment_row_state;
261
262typedef struct alignment_state_info {
263    halfword cur_align;             /*tex The current position in the preamble list. */
264    halfword cur_span;              /*tex The start of the currently spanned columns in the preamble list. */
265    halfword cur_loop;              /*tex A place to copy when extending a periodic preamble. */
266    halfword align_ptr;             /*tex The most recently pushed-down alignment stack node. */
267    halfword cur_post_adjust_head;  /*tex Adjustment list head pointer. */
268    halfword cur_post_adjust_tail;  /*tex Adjustment list tail pointer. */
269    halfword cur_pre_adjust_head;   /*tex Pre-adjustment list head pointer. */
270    halfword cur_pre_adjust_tail;   /*tex Pre-adjustment list tail pointer. */
271    halfword cur_post_migrate_head;
272    halfword cur_post_migrate_tail;
273    halfword cur_pre_migrate_head;
274    halfword cur_pre_migrate_tail;
275    halfword hold_token_head;       /*tex head of a temporary list of another kind */
276    halfword omit_template;         /*tex a constant token list */
277    halfword no_align_level;
278    halfword attr_list;
279    halfword cell_source;           
280    halfword wrap_source;           /*tex There's also a field in the row_state. */
281    halfword row_state_set;
282    halfword options;
283    halfword callback;
284    alignment_row_state row_state; 
285} alignment_state_info ;
286
287static alignment_state_info lmt_alignment_state = {
288    .cur_align             = null,
289    .cur_span              = null,
290    .cur_loop              = null,
291    .align_ptr             = null,
292    .cur_post_adjust_head  = null,
293    .cur_post_adjust_tail  = null,
294    .cur_pre_adjust_head   = null,
295    .cur_pre_adjust_tail   = null,
296    .cur_post_migrate_head = null,
297    .cur_post_migrate_tail = null,
298    .cur_pre_migrate_head  = null,
299    .cur_pre_migrate_tail  = null,
300    .hold_token_head       = null,  /*tex head of a temporary list of another kind */
301    .omit_template         = null,  /*tex a constant token list */
302    .no_align_level        = 0,
303    .attr_list             = null,
304    .cell_source           = 0,
305    .wrap_source           = 0,
306    .row_state_set         = 0, 
307    .options               = 0,
308    .callback              = 0,
309    .row_state = { 
310        .attrlist    = null,
311        .orientation = 0,
312        .xoffset     = 0,
313        .yoffset     = 0,
314        .xmove       = 0,
315        .ymove       = 0,
316        .shift       = 0,
317        .source      = 0,
318        .target      = 0,
319        .anchor      = 0,
320    }
321};
322
323int tex_in_alignment(void)
324{
325 // return lmt_alignment_state.cur_loop ? 1 : 0;
326    return lmt_alignment_state.align_ptr ? 1 : 0;
327}
328
329static void tex_aux_wipe_row_state(void)
330{
331    lmt_alignment_state.row_state.attrlist = null;
332    lmt_alignment_state.row_state.orientation = 0;
333    lmt_alignment_state.row_state.xoffset = 0;
334    lmt_alignment_state.row_state.yoffset = 0;
335    lmt_alignment_state.row_state.xmove = 0;
336    lmt_alignment_state.row_state.ymove = 0;
337    lmt_alignment_state.row_state.shift = 0;
338    lmt_alignment_state.row_state.source = 0;
339    lmt_alignment_state.row_state.target = 0;
340    lmt_alignment_state.row_state.anchor = 0;
341    lmt_alignment_state.row_state_set = 0;
342}
343
344/*tex The current preamble list: */
345
346# define preamble node_next(align_head)
347
348/*tex We use them before we define them: */
349
350static void tex_aux_initialize_row    (void);
351static void tex_aux_initialize_column (void);
352static void tex_aux_finish_row        (void);
353static int  tex_aux_finish_column     (void);
354static void tex_aux_finish_align      (void);
355
356/*tex
357    We get |alignment_record| into |unset_node| and |unset_node| into |[hv]list_node|. And because
358    we can access the fields later on w emake sure that we wipe them. The box orientation field kind
359    of protects reading them but still it's nicer this way. In general in \LUATEX\ and \LUAMETATEX\
360    we need to be more careful because we expose fields.
361*/
362
363static inline void tex_aux_change_list_type(halfword n, quarterword type)
364{
365    node_type(n) = type;
366    box_w_offset(n) = 0;    /* box_glue_stretch    align_record_span_ptr   */
367    box_h_offset(n) = 0;    /* box_glue_shrink     align_record_extra_info */
368    box_d_offset(n) = 0;    /* box_span_count                              */
369    box_x_offset(n) = 0;    /*                     align_record_u_part     */
370    box_y_offset(n) = 0;    /*                     align_record_v_part     */
371 /* box_geometry(n) = 0; */ /* box_size                                    */
372    box_orientation(n) = 0; /* box_size                                    */
373}
374
375/*tex
376
377    The |align_state| and |preamble| variables are initialized elsewhere. Alignment stack
378    maintenance is handled by a pair of trivial routines called |push_alignment| and |pop_alignment|.
379
380    It makes not much sense to add support for an |attr| keyword to |\halign| and |\valign| because
381    then we need to decide if we tag rows or cells or both or come up with |cellattr| and |rowattr|
382    and such. But then it even makes sense to have explicit commands (in addition to the seperator)
383    to tags individual cells. It's too much hassle for now and the advantages are not that large.
384
385    This code has a history so changing it now is tricky. For instance we could the top of the align 
386    stack instead of the copied values. On the other hand, working with copies makes that we can 
387    mess with these. And the gain would be little anywya, if at all. 
388
389*/
390
391static void tex_aux_push_alignment(void)
392{
393    /*tex The new alignment stack node: */
394    halfword p = tex_new_node(align_stack_node, 0);
395    /* todo: just a memory copy */
396    align_stack_align_ptr(p) = lmt_alignment_state.align_ptr;
397    align_stack_cur_align(p) = lmt_alignment_state.cur_align;
398    align_stack_preamble(p) = preamble;
399    align_stack_cur_span(p) = lmt_alignment_state.cur_span;
400    align_stack_cur_loop(p) = lmt_alignment_state.cur_loop;
401    align_stack_align_state(p) = lmt_input_state.align_state;
402    align_stack_wrap_source(p) = lmt_alignment_state.wrap_source;
403    align_stack_no_align_level(p) = lmt_alignment_state.no_align_level;
404    align_stack_cur_post_adjust_head(p) = lmt_alignment_state.cur_post_adjust_head;
405    align_stack_cur_post_adjust_tail(p) = lmt_alignment_state.cur_post_adjust_tail;
406    align_stack_cur_pre_adjust_head(p) = lmt_alignment_state.cur_pre_adjust_head;
407    align_stack_cur_pre_adjust_tail(p) = lmt_alignment_state.cur_pre_adjust_tail;
408    align_stack_cur_post_migrate_head(p) = lmt_alignment_state.cur_post_migrate_head;
409    align_stack_cur_post_migrate_tail(p) = lmt_alignment_state.cur_post_migrate_tail;
410    align_stack_cur_pre_migrate_head(p) = lmt_alignment_state.cur_pre_migrate_head;
411    align_stack_cur_pre_migrate_tail(p) = lmt_alignment_state.cur_pre_migrate_tail;
412    align_stack_options(p) = lmt_alignment_state.options;
413    align_stack_attr_list(p) = lmt_alignment_state.attr_list;
414    align_stack_callback(p) = lmt_alignment_state.callback;
415    /* */
416    align_stack_row_attrlist(p) = lmt_alignment_state.row_state.attrlist;
417    align_stack_row_orientation(p) = lmt_alignment_state.row_state.orientation;
418    align_stack_row_yoffset(p) = lmt_alignment_state.row_state.xoffset;
419    align_stack_row_xoffset(p) = lmt_alignment_state.row_state.yoffset;
420    align_stack_row_ymove(p) = lmt_alignment_state.row_state.xmove;
421    align_stack_row_xmove(p) = lmt_alignment_state.row_state.ymove;
422    align_stack_row_shift(p) = lmt_alignment_state.row_state.shift;
423    align_stack_row_source(p) = lmt_alignment_state.row_state.source;
424    align_stack_row_target(p) = lmt_alignment_state.row_state.target;
425    align_stack_row_anchor(p) = lmt_alignment_state.row_state.anchor;
426    /* */
427    lmt_alignment_state.align_ptr = p;
428    lmt_alignment_state.cur_post_adjust_head = tex_new_temp_node();
429    lmt_alignment_state.cur_pre_adjust_head = tex_new_temp_node();
430    lmt_alignment_state.cur_post_migrate_head = tex_new_temp_node();
431    lmt_alignment_state.cur_pre_migrate_head = tex_new_temp_node();
432    /* */
433    lmt_alignment_state.cell_source = 0;
434    lmt_alignment_state.wrap_source = 0;
435 // lmt_alignment_state.options = 0;
436    /* todo: put in align_stack, also wipe attr if needed */
437    tex_aux_wipe_row_state();
438}
439
440static void tex_aux_pop_alignment(void)
441{
442    /*tex The top alignment stack node: */
443    halfword p = lmt_alignment_state.align_ptr;
444    tex_flush_node(lmt_alignment_state.cur_post_adjust_head);
445    tex_flush_node(lmt_alignment_state.cur_pre_adjust_head);
446    tex_flush_node(lmt_alignment_state.cur_post_migrate_head);
447    tex_flush_node(lmt_alignment_state.cur_pre_migrate_head);
448    lmt_alignment_state.align_ptr = align_stack_align_ptr(p);
449    lmt_alignment_state.cur_align = align_stack_cur_align(p);
450    preamble = align_stack_preamble(p);
451    lmt_alignment_state.cur_span = align_stack_cur_span(p);
452    lmt_alignment_state.cur_loop = align_stack_cur_loop(p);
453    lmt_input_state.align_state = align_stack_align_state(p);
454    lmt_alignment_state.wrap_source = align_stack_wrap_source(p);
455    lmt_alignment_state.no_align_level  = align_stack_no_align_level(p);
456    lmt_alignment_state.cur_post_adjust_head = align_stack_cur_post_adjust_head(p);
457    lmt_alignment_state.cur_post_adjust_tail = align_stack_cur_post_adjust_tail(p);
458    lmt_alignment_state.cur_pre_adjust_head = align_stack_cur_pre_adjust_head(p);
459    lmt_alignment_state.cur_pre_adjust_tail = align_stack_cur_pre_adjust_tail(p);
460    lmt_alignment_state.cur_post_migrate_head = align_stack_cur_post_migrate_head(p);
461    lmt_alignment_state.cur_post_migrate_tail = align_stack_cur_post_migrate_tail(p);
462    lmt_alignment_state.cur_pre_migrate_head = align_stack_cur_pre_migrate_head(p);
463    lmt_alignment_state.cur_pre_migrate_tail = align_stack_cur_pre_migrate_tail(p);
464    lmt_alignment_state.options = align_stack_options(p);
465    lmt_alignment_state.attr_list = align_stack_attr_list(p);
466    lmt_alignment_state.callback = align_stack_callback(p);
467    /* */
468    lmt_alignment_state.row_state.attrlist = align_stack_row_attrlist(p);    
469    lmt_alignment_state.row_state.orientation = align_stack_row_orientation(p);
470    lmt_alignment_state.row_state.xoffset = align_stack_row_yoffset(p);  
471    lmt_alignment_state.row_state.yoffset = align_stack_row_xoffset(p);     
472    lmt_alignment_state.row_state.xmove = align_stack_row_ymove(p);  
473    lmt_alignment_state.row_state.ymove = align_stack_row_xmove(p);     
474    lmt_alignment_state.row_state.shift = align_stack_row_shift(p);       
475    lmt_alignment_state.row_state.source = align_stack_row_source(p);      
476    lmt_alignment_state.row_state.target = align_stack_row_target(p);      
477    lmt_alignment_state.row_state.anchor = align_stack_row_anchor(p);      
478    /* */
479    tex_flush_node(p);
480}
481
482/*tex
483
484    \TEX\ has eight procedures that govern alignments: |initialize_align| and |finish_align| are
485    used at the very beginning and the very end; |initialize_row| and |finish_row| are used at
486    the beginning and end of individual rows; |initialize_span| is used at the beginning of a
487    sequence of spanned columns (possibly involving only one column); |initialize_column| and
488    |finish_column| are used at the beginning and end of individual columns; and |align_peek| is
489    used after |\cr| to see whether the next item is |\noalign|.
490
491    We shall consider these routines in the order they are first used during the course of a
492    complete |\halign|, namely |initialize_align|, |align_peek|, |initialize_row|,
493    |initialize_span|, |initialize_column|, |finish_column|, |finish_row|, |finish_align|.
494
495    The preamble is copied directly, except that |\tabskip| causes a change to the tabskip glue,
496    thereby possibly expanding macros that immediately follow it. An appearance of |\span| also
497    causes such an expansion.
498
499    Note that if the preamble contains |\global\tabskip|, the |\global| token survives in the
500    preamble and the |\tabskip| defines new tabskip glue (locally).
501
502    We enter |\span| into |eqtb| with |tab_mark| as its command code, and with |span_code| as the
503    command modifier. This makes \TEX\ interpret it essentially the same as an alignment delimiter
504    like |&|, yet it is recognizably different when we need to distinguish it from a normal
505    delimiter. It also turns out to be useful to give a special |cr_code| to |\cr|, and an even
506    larger |cr_cr_code| to |\crcr|.
507
508    The end of a template is represented by two frozen control sequences called |\endtemplate|. The
509    first has the command code |end_template|, which is |> outer_call|, so it will not easily
510    disappear in the presence of errors. The |get_x_token| routine converts the first into the
511    second, which has |endv| as its command code.
512
513    The |cr_code| is distinct from |span_code| and from any character and |\crcr| differs from
514    |\cr|.
515*/
516
517/*
518    In \LUAMETATEX\ the code has been adapted a bit. Because we have some access to alignment
519    related properties (commands, lists, etc.) The command codes have been reshuffled and
520    combined. Instead of dedicated cmd codes, we have a shared cmd with subtypes. The logic
521    hasn't changed, just the triggering of actions. In theory there can be a performance penalty
522    (due to extra checking) but in practice that will not be noticed becasue this seldom happens.
523    The advange is that we have a uniform token interface. It also makes it possible to extend
524    the code.
525
526*/
527
528static void tex_aux_get_preamble_token(void)
529{
530  RESTART:
531    tex_get_token();
532    while (cur_cmd == alignment_cmd && cur_chr == span_code) {
533        /*tex This token will be expanded once. */
534        tex_get_token();
535        if (cur_cmd > max_command_cmd) {
536            tex_expand_current_token();
537            tex_get_token();
538        }
539    }
540    switch (cur_cmd) {
541        case end_template_cmd:
542            tex_alignment_interwoven_error(5);
543            break;
544        case internal_dimension_cmd:
545            if (cur_chr == internal_dimension_location(tab_size_code)) {
546                scaled v = tex_scan_dimension(0, 0, 0, 1, NULL, NULL);
547                tex_word_define(global_defs_par > 0 ? global_flag_bit : 0, internal_dimension_location(tab_size_code), v);
548                goto RESTART;
549            } else {
550                break;
551            }
552        case internal_glue_cmd:
553            if (cur_chr == internal_glue_location(tab_skip_code)) {
554                halfword v = tex_scan_glue(glue_val_level, 1, 0);
555                if (global_defs_par > 0) {
556                    update_tex_tab_skip_global(v);
557                } else {
558                    update_tex_tab_skip_local(v);
559                }
560                goto RESTART;
561            } else {
562                break;
563            }
564        case call_cmd:
565        case protected_call_cmd:
566        case semi_protected_call_cmd:
567        case constant_call_cmd:
568        case tolerant_call_cmd:
569        case tolerant_protected_call_cmd:
570        case tolerant_semi_protected_call_cmd:
571            if (has_eq_flag_bits(cur_cs, noaligned_flag_bit)) {
572                tex_expand_current_token();
573                goto RESTART;
574            } else {
575                break;
576            }
577    }
578}
579
580/*tex
581
582    When |\halign| or |\valign| has been scanned in an appropriate mode, \TEX\ calls
583    |initialize_align|, whose task is to get everything off to a good start. This mostly involves
584    scanning the preamble and putting its information into the preamble list.
585
586*/
587
588static void tex_aux_scan_align_spec(quarterword c)
589{
590    quarterword mode = packing_additional;
591    quarterword options = 0;
592    scaled amount = 0;
593    halfword callback = 0;
594    halfword attrlist = null;
595    bool brace = false;
596    while (1) {
597        cur_val = 0; /* why */
598        switch (tex_scan_character("acdnrtsACDNRTS", 1, 1, 1)) {
599            case 0:
600                goto DONE;
601            case 'a': case 'A':
602                if (tex_scan_mandate_keyword("attr", 1)) {
603                    attrlist = tex_scan_attribute(attrlist);
604                }
605                break;
606            case 'c': case 'C':
607                /* We permits multiple callbacks so we |or| them. */
608                if (tex_scan_mandate_keyword("callback", 1)) {
609                    options |= align_option_callback;
610                    if (tex_scan_character("sS", 0, 0, 0)) {
611                        callback |= tex_scan_integer(0, NULL, NULL);
612                    } else { 
613                        callback = tex_scan_integer(0, NULL, NULL);
614                    }
615                }
616                break;
617            case 'd': case 'D':
618                if (tex_scan_mandate_keyword("discard", 1)) {
619                    options |= align_option_discard;
620                }
621                break;
622            case 'n': case 'N':
623                if (tex_scan_mandate_keyword("noskips", 1)) {
624                    options |= align_option_noskips;
625                }
626                break;
627            case 'r': case 'R':
628                if (tex_scan_mandate_keyword("reverse", 1)) {
629                    options |= align_option_reverse;
630                }
631                break;
632            case 't': case 'T':
633                if (tex_scan_mandate_keyword("to", 1)) {
634                    mode = packing_exactly;
635                    options |= align_option_exactly;
636                    amount = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
637                }
638                break;
639            case 's': case 'S':
640                if (tex_scan_mandate_keyword("spread", 1)) {
641                    mode = packing_additional;
642                    options &= (~ align_option_exactly);
643                    amount = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
644                }
645                break;
646            case '{':
647                brace = true;
648                goto DONE;
649            default:
650                goto DONE;
651        }
652    }
653  DONE:
654    if (! attrlist) {
655        attrlist = tex_current_attribute_list();
656    } 
657    if (options & align_option_noskips) {
658        options &= (~ align_option_discard);
659    }
660    /*tex Now we're referenced. We need to preserve this over the group. */
661    add_attribute_reference(attrlist);
662    saved_alignment_initialize();
663    saved_align_mode = mode;
664    saved_align_amount = amount;
665    saved_align_callback = callback;
666    lmt_save_state.save_stack_data.ptr += saved_align_n_of_records;
667    tex_new_save_level(c);
668    if (! brace) {
669        tex_scan_left_brace();
670    }
671    lmt_alignment_state.attr_list = attrlist;
672    lmt_alignment_state.options = options;
673    lmt_alignment_state.callback = callback;
674}
675
676/*tex
677
678    The tricky part about alignments is getting the templates into the scanner at the right time,
679    and recovering control when a row or column is finished.
680
681    We usually begin a row after each |\cr| has been sensed, unless that |\cr| is followed by
682    |\noalign| or by the right brace that terminates the alignment. The |align_peek| routine is
683    used to look ahead and do the right thing; it either gets a new row started, or gets a
684    |\noalign} started, or finishes off the alignment.
685
686*/
687
688static void tex_aux_align_peek(void);
689
690static void tex_aux_trace_no_align(const char *s)
691{
692    if (tracing_alignments_par > 0) {
693        tex_begin_diagnostic();
694        tex_print_format("[alignment: %s noalign, level %i]", s, lmt_alignment_state.no_align_level);
695        tex_end_diagnostic();
696    }
697}
698
699static void tex_aux_run_no_align(void)
700{
701    /* */
702    int brace = 0;
703    int done = lmt_alignment_state.row_state_set;
704    while (1) {
705        int add = 0;
706      AGAIN:
707        switch (tex_scan_character("atrsoxyATRSOXY", 1, 1, 1)) {
708            case 0:
709                goto DONE;
710            case 't': case 'T':
711                if (tex_scan_mandate_keyword("target", 1)) {
712                    lmt_alignment_state.row_state.target = tex_scan_integer(1, NULL, NULL);
713                    done = 1;
714                }
715                break;
716            case 'a': case 'A':
717                switch (tex_scan_character("ntdNTD", 0, 0, 0)) {
718                    case 'd': case 'D':
719                        if (tex_scan_mandate_keyword("add", 2)) {
720                            add = 1;
721                            goto AGAIN;
722                        }
723                        break;
724                    case 't': case 'T':
725                        if (tex_scan_mandate_keyword("attr", 2)) {
726                            halfword i = tex_scan_attribute_register_number();
727                            halfword v = tex_scan_integer(1, NULL, NULL);
728                            if (eq_value(register_attribute_location(i)) != v) {
729                                if (lmt_alignment_state.row_state.attrlist) {
730                                    lmt_alignment_state.row_state.attrlist = tex_patch_attribute_list(lmt_alignment_state.row_state.attrlist, i, v);
731                                } else if (lmt_alignment_state.attr_list) {
732                                    lmt_alignment_state.row_state.attrlist = tex_copy_attribute_list_set(lmt_alignment_state.attr_list, i, v);
733                                } else {
734                                    lmt_alignment_state.row_state.attrlist = tex_copy_attribute_list_set(tex_current_attribute_list(), i, v);
735                                }
736                                done = 1;
737                            }
738                        }
739                        break;
740                    case 'n': case 'N':
741                        if (tex_scan_mandate_keyword("anchor", 2)) {
742                            switch (tex_scan_character("sS", 0, 0, 0)) {
743                                case 's': case 'S':
744                                    lmt_alignment_state.row_state.anchor = tex_scan_anchors(0);
745                                    break;
746                                default:
747                                    lmt_alignment_state.row_state.anchor = tex_scan_anchor(0);
748                                    break;
749                            }
750                            done = 1;
751                        }
752                        break;
753                    default:
754                        tex_aux_show_keyword_error("attr|anchor|add");
755                        goto DONE;
756                }
757                break;
758            case 'r': case 'R':
759                if (tex_scan_mandate_keyword("reset", 1)) {
760                    tex_aux_wipe_row_state();
761                    done = 0;
762                }
763                break;
764            case 's': case 'S':
765                switch (tex_scan_character("hoHO", 0, 0, 0)) {
766                    case 'h': case 'H':
767                        if (tex_scan_mandate_keyword("shift", 2)) {
768                            lmt_alignment_state.row_state.shift = (add ? lmt_alignment_state.row_state.shift : 0) 
769                                + tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
770                            done = 1;
771                        }
772                        break;
773                    case 'o': case 'O':
774                        if (tex_scan_mandate_keyword("source", 2)) {
775                            lmt_alignment_state.row_state.source = tex_scan_integer(1, NULL, NULL);
776                            done = 1;
777                        }
778                        break;
779                    default:
780                        tex_aux_show_keyword_error("shift|source");
781                        goto DONE;
782                }
783                break;
784            case 'o': case 'O':
785                if (tex_scan_mandate_keyword("orientation", 1)) {
786                    lmt_alignment_state.row_state.orientation = tex_scan_orientation(0);
787                    done = 1;
788                }
789                break;
790            case 'x': case 'X':
791                switch (tex_scan_character("omOM", 0, 0, 0)) {
792                    case 'o': case 'O' :
793                        if (tex_scan_mandate_keyword("xoffset", 2)) {
794                            lmt_alignment_state.row_state.xoffset = (add ? lmt_alignment_state.row_state.xoffset : 0) + tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
795                            done = 1;
796                        }
797                        break;
798                    case 'm': case 'M' :
799                        if (tex_scan_mandate_keyword("xmove", 2)) {
800                            lmt_alignment_state.row_state.xmove = (add ? lmt_alignment_state.row_state.xmove : 0) + tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
801                            done = 1;
802                        }
803                        break;
804                    default:
805                        tex_aux_show_keyword_error("xoffset|xmove");
806                        goto DONE;
807                }
808                break;
809            case 'y': case 'Y':
810                switch (tex_scan_character("omOM", 0, 0, 0)) {
811                    case 'o': case 'O' :
812                        if (tex_scan_mandate_keyword("yoffset", 2)) {
813                            lmt_alignment_state.row_state.yoffset = (add ? lmt_alignment_state.row_state.yoffset : 0) + tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
814                            done = 1;
815                        }
816                        break;
817                    case 'm': case 'M' :
818                        if (tex_scan_mandate_keyword("ymove", 2)) {
819                            lmt_alignment_state.row_state.ymove = (add ? lmt_alignment_state.row_state.ymove : 0) + tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
820                            done = 1;
821                        }
822                        break;
823                    default:
824                        tex_aux_show_keyword_error("yoffset|ymove");
825                        goto DONE;
826                }
827                break;
828            case '{':
829                brace = 1;
830                goto DONE;
831            default:
832                goto DONE;
833        }
834        add = 0;
835    }
836  DONE:
837    lmt_alignment_state.row_state_set = done;
838    /* */
839    if (! brace) {
840        tex_scan_left_brace();
841    }
842    tex_new_save_level(no_align_group);
843    ++lmt_alignment_state.no_align_level;
844    tex_aux_trace_no_align("entering");
845    if (cur_list.mode == internal_vmode) {
846        tex_normal_paragraph(no_align_par_context);
847    }
848}
849
850static int tex_aux_nested_no_align(void)
851{
852    int state = lmt_alignment_state.no_align_level > 0;
853    if (state) {
854//        tex_scan_left_brace();
855//        tex_new_save_level(no_align_group);
856//        ++lmt_alignment_state.no_align_level;
857//        tex_aux_trace_no_align("entering");
858//        if (cur_list.mode == internal_vmode) {
859//            tex_normal_paragraph(no_align_par_context);
860//        }
861        tex_aux_run_no_align();
862    }
863    return state;
864}
865
866void tex_finish_no_alignment_group(void)
867{
868    if (! tex_wrapped_up_paragraph(no_align_par_context, 0)) { /* needs testing */
869        tex_end_paragraph(no_align_group, no_align_par_context);
870        tex_aux_trace_no_align("leaving");
871        --lmt_alignment_state.no_align_level;
872        tex_unsave();
873        if (lmt_alignment_state.no_align_level == 0) {
874            tex_aux_align_peek();
875        }
876    }
877}
878
879static void tex_aux_align_peek(void)
880{
881  RESTART:
882    lmt_input_state.align_state = busy_alignment_state;
883  AGAIN:
884    tex_get_x_or_protected();
885    switch (cur_cmd) {
886        case spacer_cmd:
887            goto AGAIN;
888        case right_brace_cmd:
889            tex_aux_finish_align();
890            break;
891        case call_cmd:
892        case protected_call_cmd:
893        case semi_protected_call_cmd:
894        case constant_call_cmd:
895        case tolerant_call_cmd:
896        case tolerant_protected_call_cmd:
897        case tolerant_semi_protected_call_cmd:
898            if (has_eq_flag_bits(cur_cs, noaligned_flag_bit)) {
899                tex_expand_current_token();
900                goto RESTART;
901            } else {
902                goto NEXTROW;
903            }
904        case alignment_cmd:
905            switch (cur_chr) {
906                case cr_cr_code:
907                    /*tex Ignore |\crcr|. */
908                    goto RESTART;
909                case no_align_code:
910                    tex_aux_run_no_align();
911                    return;
912            }
913            // fall through
914        default:
915          NEXTROW:
916            /*tex Start a new row. */
917            tex_aux_initialize_row();
918            /*tex Start a new column and replace what we peeked at. */
919            tex_aux_initialize_column();
920            break;
921    }
922}
923
924/*tex
925*
926    Magick numbers are used to indicate the level of alignment. However, keep in  mind that in
927    \LUANETATEX\ the fundamental parts of the rendering are separated. Contrary to traditional
928    \TEX\ we don't have the interwoven hyphenation, ligature building, kerning, etc.\ code.
929
930    In the end we have a list starting and ending with tabskips and align records seperated by
931    such skips.
932
933*/
934
935void tex_run_alignment_initialize(void)
936{
937    halfword saved_cs = cur_cs;
938    tex_aux_push_alignment();
939    lmt_input_state.align_state = initial_alignment_state;
940    /*tex
941        When |\halign| is used as a displayed formula, there should be no other pieces of mlists
942        present.
943    */
944    if (cur_list.mode == mmode && ((cur_list.tail != cur_list.head) || cur_list.incomplete_noad)) {
945        tex_handle_error(
946            normal_error_type,
947            "Improper \\halign inside math mode",
948            "Displays can use special alignments (like \\eqalignno) only if nothing but the\n"
949            "alignment itself is in math mode. So I've deleted the formulas that preceded this\n"
950            "alignment."
951        );
952        tex_flush_math();
953    }
954    /*tex We enter a new semantic level. */
955    tex_push_nest();
956    /*tex
957        In vertical modes, |prev_depth| already has the correct value. But if we are in |mmode|
958        (displayed formula mode), we reach out to the enclosing vertical mode for the |prev_depth|
959        value that produces the correct baseline calculations.
960    */
961    if (cur_list.mode == mmode) {
962        cur_list.mode = internal_vmode;
963        cur_list.prev_depth = lmt_nest_state.nest[lmt_nest_state.nest_data.ptr - 2].prev_depth;
964    } else if (cur_list.mode > 0) {
965        cur_list.mode = -cur_list.mode;
966    }
967    /*tex This one also saves some in the state. */
968    tex_aux_scan_align_spec(align_group);
969    /*tex
970        Scan the preamble. Even when we ignore zero tabskips, we do store them in the list because
971        the machinery later on steps over them and checking for present glue makes the code
972        horrible. The overhead is small because it's only the preamble where we waste glues then.
973    */
974    preamble = null;
975    lmt_alignment_state.cur_align = align_head;
976    lmt_alignment_state.cur_loop = null;
977    lmt_input_state.scanner_status = scanner_is_aligning;
978    lmt_input_state.warning_index = saved_cs;
979    lmt_input_state.align_state = initial_alignment_state;
980    /*tex At this point, |cur_cmd = left_brace|. */
981    while (1) {
982        /*tex Append the current tabskip glue to the preamble list. */
983        halfword glue = tex_new_param_glue_node(tab_skip_code, tab_skip_glue);
984        if ((lmt_alignment_state.options & align_option_noskips) && tex_glue_is_zero(glue)) {
985            node_subtype(glue) = ignored_glue;
986        }
987        tex_couple_nodes(lmt_alignment_state.cur_align, glue);
988        lmt_alignment_state.cur_align = glue;
989        if (cur_cmd == alignment_cmd && (cur_chr == cr_code || cur_chr == cr_cr_code)) { /* Also cr_cr here? */
990            /*tex A |\cr| ends the preamble. */
991            break;
992        } else {
993            /*tex
994                Scan preamble text until |cur_cmd| is |tab_mark| or |car_ret| and then scan the
995                template |u_j|, putting the resulting token list in |hold_token_head|. Spaces are
996                eliminated from the beginning of a template.
997            */
998            halfword record = null;
999            halfword current = lmt_alignment_state.hold_token_head;
1000            token_link(current) = null;
1001            while (1) {
1002                tex_aux_get_preamble_token();
1003                if ((cur_cmd == alignment_cmd && cur_chr == align_content_code) || cur_cmd == parameter_cmd) {
1004                    break;
1005                } else if ((cur_cmd == alignment_cmd || cur_cmd == alignment_tab_cmd) && (lmt_input_state.align_state == initial_alignment_state)) {
1006                    if ((current == lmt_alignment_state.hold_token_head) && (! lmt_alignment_state.cur_loop) && (cur_cmd == alignment_tab_cmd)) {
1007                        lmt_alignment_state.cur_loop = lmt_alignment_state.cur_align;
1008                    } else {
1009                        tex_back_input(cur_tok);
1010                        tex_handle_error(
1011                            normal_error_type,
1012                            "Missing # inserted in alignment preamble",
1013                            "There should be exactly one # between &'s, when an \\halign or \\valign is being\n"
1014                            "set up. In this case you had none, so I've put one in; maybe that will work."
1015                        );
1016                        break;
1017                    }
1018                } else if (cur_cmd != spacer_cmd || current != lmt_alignment_state.hold_token_head) {
1019                    current = tex_store_new_token(current, cur_tok);
1020                }
1021            }
1022            /*tex A new align record: */
1023            record = tex_new_node(align_record_node, 0);
1024            tex_couple_nodes(lmt_alignment_state.cur_align, record);
1025            lmt_alignment_state.cur_align = record;
1026            align_record_span_ptr(record) = end_span;
1027            box_width(record) = null_flag;
1028            align_record_pre_part(record) = token_link(lmt_alignment_state.hold_token_head);
1029            /*tex Scan the template |v_j|, putting the resulting token list in |hold_token_head|. */
1030            current = lmt_alignment_state.hold_token_head;
1031            token_link(current) = null;
1032            while (1) {
1033                tex_aux_get_preamble_token();
1034                if ((cur_cmd == alignment_cmd || cur_cmd == alignment_tab_cmd) && (lmt_input_state.align_state == initial_alignment_state)) {
1035                    break;
1036                } else if ((cur_cmd == alignment_cmd && cur_chr == align_content_code) || cur_cmd == parameter_cmd) {
1037                    tex_handle_error(
1038                        normal_error_type,
1039                        "Only one # is allowed per tab",
1040                        "There should be exactly one # between &'s, when an \\halign or \\valign is being\n"
1041                        "set up. In this case you had more than one, so I'm ignoring all but the first."
1042                    );
1043                } else {
1044                    current = tex_store_new_token(current, cur_tok);
1045                }
1046            }
1047            if (tab_size_par > 0) {
1048                box_size(record) = tab_size_par;
1049                set_box_package_state(record, package_dimension_size_set);
1050            } else {
1051                box_width(record) = null_flag;
1052            }
1053            /*tex Put |\endtemplate| at the end: */
1054            current = tex_store_new_token(current, deep_frozen_end_template_token);
1055            align_record_post_part(lmt_alignment_state.cur_align) = token_link(lmt_alignment_state.hold_token_head);
1056        }
1057    }
1058    if (tracing_alignments_par > 1) {
1059        tex_begin_diagnostic();
1060        tex_print_str("<alignment preamble>");
1061        tex_show_node_list(preamble, max_integer, max_integer);
1062        tex_end_diagnostic();
1063    }
1064    if (lmt_alignment_state.options & align_option_callback) {
1065        lmt_alignment_callback(cur_list.head, preamble_pass_alignment_context, lmt_alignment_state.callback, lmt_alignment_state.attr_list, preamble);
1066    }
1067    lmt_input_state.scanner_status = scanner_is_normal;
1068    tex_new_save_level(align_group);
1069    if (every_cr_par) {
1070        tex_begin_token_list(every_cr_par, every_cr_text);
1071    }
1072    /*tex Look for |\noalign| or |\omit|. */
1073    tex_aux_align_peek();
1074}
1075
1076void tex_finish_alignment_group(void)
1077{
1078    tex_back_input(cur_tok);
1079    cur_tok = deep_frozen_cr_token;
1080    tex_handle_error(
1081        insert_error_type,
1082        "Missing \\cr inserted",
1083        "I'm guessing that you meant to end an alignment here."
1084    );
1085}
1086
1087/*tex
1088
1089    The parameter to |initialize_span| is a pointer to the alignrecord where the next column or group
1090    of columns will begin. A new semantic level is entered, so that the columns will generate a list
1091    for subsequent packaging.
1092
1093*/
1094
1095static void tex_aux_initialize_span(halfword p)
1096{
1097    tex_push_nest();
1098    if (cur_list.mode == restricted_hmode) {
1099        cur_list.space_factor = default_space_factor;
1100    } else {
1101        cur_list.prev_depth = ignore_depth_criterion_par;
1102        tex_normal_paragraph(span_par_context);
1103    }
1104    lmt_alignment_state.cur_span = p;
1105}
1106
1107/*tex
1108
1109    To start a row (i.e., a \quote {row} that rhymes with \quote {dough} but not with \quote
1110    {bough}), we enter a new semantic level, copy the first tabskip glue, and change from internal
1111    vertical mode to restricted horizontal mode or vice versa. The |space_factor| and |prev_depth|
1112    are not used on this semantic level, but we clear them to zero just to be tidy.
1113
1114*/
1115
1116static void tex_aux_initialize_row(void)
1117{
1118    tex_push_nest();
1119    cur_list.mode = (- hmode - vmode) - cur_list.mode; /* weird code : - 3 - cur_list.mode : so a buogus line */
1120    if (cur_list.mode == restricted_hmode) {                     
1121        cur_list.space_factor = 0;
1122    } else {
1123        cur_list.prev_depth = 0;
1124    }
1125    lmt_alignment_state.cur_align = preamble;
1126    if (node_subtype(preamble) != ignored_glue) {
1127        halfword glue = tex_new_glue_node(preamble, tab_skip_glue);
1128        tex_tail_append(glue);
1129        tex_attach_attribute_list_attribute(glue, lmt_alignment_state.attr_list);
1130    }
1131    lmt_alignment_state.cur_align = node_next(preamble);
1132    lmt_alignment_state.cur_post_adjust_tail = lmt_alignment_state.cur_post_adjust_head;
1133    lmt_alignment_state.cur_pre_adjust_tail = lmt_alignment_state.cur_pre_adjust_head;
1134    lmt_alignment_state.cur_post_migrate_tail = lmt_alignment_state.cur_post_migrate_head;
1135    lmt_alignment_state.cur_pre_migrate_tail = lmt_alignment_state.cur_pre_migrate_head;
1136    tex_aux_initialize_span(lmt_alignment_state.cur_align);
1137    /* todo: wipe attr */
1138}
1139
1140/*tex
1141
1142    When a column begins, we assume that |cur_cmd| is either |omit| or else the current token should
1143    be put back into the input until the \<u_j> template has been scanned. Note that |cur_cmd| might
1144    be |tab_mark| or |car_ret|. We also assume that |align_state| is approximately 1000000 at this
1145    time. We remain in the same mode, and start the template if it is called for.
1146
1147*/
1148
1149static inline void tex_alignment_aux_flush_local_template(halfword record)
1150{
1151    if (record) { 
1152        if (align_record_pre_local(record)) { 
1153            tex_flush_token_list(align_record_pre_local(record));
1154            align_record_pre_local(record) = null;
1155        }
1156        if (align_record_post_local(record)) { 
1157            tex_flush_token_list(align_record_post_local(record));
1158            align_record_post_local(record) = null;
1159        }
1160    }
1161}
1162
1163static inline void tex_alignment_aux_scan_local_template(halfword record)
1164{
1165    halfword prehead = null;
1166    halfword posthead = null;
1167    halfword pretail = null;
1168    halfword posttail = null;
1169    do {
1170        tex_get_x_token();
1171    } while (cur_cmd == spacer_cmd);
1172    if (cur_cmd == left_brace_cmd) {
1173        prehead = tex_scan_toks_normal(1, &pretail);
1174        // tex_store_new_token(pretail, deep_frozen_end_template_token);
1175    } else { 
1176        tex_handle_error(
1177            insert_error_type,
1178            "Missing pre cell token list"
1179        );
1180    }
1181    do {
1182        tex_get_x_token();
1183    } while (cur_cmd == spacer_cmd);
1184    if (cur_cmd == left_brace_cmd) {
1185        posthead = tex_scan_toks_normal(1, &posttail);
1186        tex_store_new_token(posttail, deep_frozen_end_template_token);
1187    } else { 
1188        tex_handle_error(
1189            insert_error_type,
1190            "Missing post cell token list"
1191        );
1192    }
1193    if (prehead) {
1194        align_record_pre_local(record) = token_link(prehead);
1195        token_link(prehead) = null;
1196        tex_put_available_token(prehead);
1197    }
1198    if (posthead) {
1199        align_record_post_local(record) = token_link(posthead);
1200        token_link(posthead) = null;
1201        tex_put_available_token(posthead);
1202    } 
1203}
1204
1205static void tex_aux_initialize_column(void)
1206{
1207    halfword cmd = cur_cmd;
1208    halfword chr = cur_chr;
1209    align_record_cmd(lmt_alignment_state.cur_align) = cmd;
1210    align_record_chr(lmt_alignment_state.cur_align) = chr;
1211    if (cmd == alignment_cmd) {
1212        switch (chr) { 
1213            case re_align_code:
1214                {
1215                    tex_alignment_aux_scan_local_template(lmt_alignment_state.cur_align);
1216                    cur_cmd = cmd;
1217                    cur_chr = chr;
1218                    lmt_input_state.align_state = busy_alignment_state;
1219                    goto OKAY;
1220                }
1221            case omit_code: 
1222                {
1223                    lmt_input_state.align_state = 0;
1224                    return; 
1225                }
1226        }
1227    }
1228    /*tex Now |align_state = 1000000|, one of these magic numbers. */
1229    tex_back_input(cur_tok);
1230  OKAY:
1231    if (every_tab_par) {
1232        tex_begin_token_list(every_tab_par, every_tab_text);
1233    }
1234    tex_begin_token_list(align_record_pre_local(lmt_alignment_state.cur_align) ? align_record_pre_local(lmt_alignment_state.cur_align) : align_record_pre_part(lmt_alignment_state.cur_align), template_pre_text);
1235}
1236
1237/*tex
1238
1239    The scanner sets |align_state| to zero when the |u_j| template ends. When a subsequent |\cr|
1240    or |\span| or tab mark occurs with |align_state=0|, the scanner activates the following code,
1241    which fires up the |v_j| template. We need to remember the |cur_chr|, which is either
1242    |cr_cr_code|, |cr_code|, |span_code|, or a character code, depending on how the column text has
1243    ended.
1244
1245    This part of the program had better not be activated when the preamble to another alignment is
1246    being scanned, or when no alignment preamble is active.
1247
1248*/
1249
1250void tex_insert_alignment_template(void)
1251{
1252    if (lmt_input_state.scanner_status == scanner_is_aligning || ! lmt_alignment_state.cur_align) {
1253        tex_alignment_interwoven_error(6);
1254    } else {
1255        /*tex in case of an |\omit| the gets discarded and is nowhere else referenced. */
1256        halfword cmd = align_record_cmd(lmt_alignment_state.cur_align);
1257        halfword chr = align_record_chr(lmt_alignment_state.cur_align);
1258        halfword tok = (cmd == alignment_cmd && chr == omit_code) ? lmt_alignment_state.omit_template : align_record_post_local(lmt_alignment_state.cur_align) ? align_record_post_local(lmt_alignment_state.cur_align) : align_record_post_part(lmt_alignment_state.cur_align);
1259        align_record_cmd(lmt_alignment_state.cur_align) = cur_cmd;
1260        align_record_chr(lmt_alignment_state.cur_align) = cur_chr;
1261        tex_begin_token_list(tok, template_post_text);
1262        lmt_input_state.align_state = busy_alignment_state;
1263        lmt_alignment_state.cell_source = alignment_cell_source_par;
1264        if (alignment_wrap_source_par) {
1265            lmt_alignment_state.wrap_source = alignment_wrap_source_par;
1266        }
1267    }
1268}
1269
1270/*tex Determine the stretch or shrink order */
1271
1272static inline singleword tex_aux_determine_order(scaled *total)
1273{
1274    if      (total[filll_glue_order]) return filll_glue_order;
1275    else if (total[fill_glue_order])  return fill_glue_order;
1276    else if (total[fil_glue_order])   return fil_glue_order;
1277    else if (total[fi_glue_order])    return fi_glue_order;
1278    else                              return normal_glue_order;
1279}
1280
1281/*tex
1282
1283    A span node is a 3-word record containing |width|, |span_span|, and |span_ptr| fields. The
1284    |span_span| field indicates the number of spanned columns; the |span_ptr| field points to a
1285    span node for the same starting column, having a greater extent of spanning, or to |end_span|,
1286    which has the largest possible |span_span| field; the |width| field holds the largest natural
1287    width corresponding to a particular set of spanned columns.
1288
1289    A list of the maximum widths so far, for spanned columns starting at a given column, begins
1290    with the |span_ptr| field of the alignrecord for that column. The code has to make sure that
1291    there is room for |span_ptr| in both the align record and the span nodes, which is why
1292    |span_ptr| replaces |node_attr|.
1293
1294*/
1295
1296static halfword tex_aux_new_span_node(halfword n, int s, scaled w)
1297{
1298    halfword p = tex_new_node(span_node, 0);
1299    span_ptr(p) = n; /*tex This one overlaps with |alignment_record_ptr|. */
1300    span_span(p) = s;
1301    span_width(p) = w;
1302    return p;
1303}
1304
1305/*tex
1306
1307    When the |end_template| command at the end of a |v_j| template comes through the scanner,
1308    things really start to happen; and it is the |finialize_column| routine that makes them happen.
1309    This routine returns |true| if a row as well as a column has been finished.
1310
1311*/
1312
1313void tex_alignment_interwoven_error(int n)
1314{
1315    tex_formatted_error("alignment", "interwoven preambles are not allowed, case %d", n);
1316}
1317
1318halfword tex_alignment_hold_token_head(void)
1319{
1320    return lmt_alignment_state.hold_token_head;
1321}
1322
1323static int tex_aux_finish_column(void)
1324{
1325    tex_alignment_aux_flush_local_template(lmt_alignment_state.cur_align);
1326    if (! lmt_alignment_state.cur_align) {
1327        tex_confusion("end template, case 1");
1328    } else {
1329        halfword q = node_next(lmt_alignment_state.cur_align);
1330        if (! q) {
1331            tex_confusion("end template, case 2");
1332        } else if (lmt_input_state.align_state < interwoven_alignment_threshold) {
1333            tex_alignment_interwoven_error(1);
1334        } else {
1335            /*tex A few state variables. */
1336            halfword cmd = align_record_cmd(lmt_alignment_state.cur_align);
1337            halfword chr = align_record_chr(lmt_alignment_state.cur_align);
1338            /*tex
1339                We check the alignrecord after the current one. If the preamble list has been
1340                traversed, check that the row has ended.
1341            */
1342            halfword record = node_next(q);
1343            if (alignment_wrap_source_par) {
1344                lmt_alignment_state.wrap_source = alignment_wrap_source_par;
1345            }
1346            if (! record && ! ((cmd == alignment_cmd) && (chr == cr_code || chr == cr_cr_code))) {
1347                if (lmt_alignment_state.cur_loop) {
1348                    /*tex Lengthen the preamble periodically. A new align record: */
1349                    record = tex_new_node(align_record_node, 0);
1350                    tex_couple_nodes(q, record);
1351                    align_record_span_ptr(record) = end_span;
1352                    box_width(record) = null_flag;
1353                    lmt_alignment_state.cur_loop = node_next(lmt_alignment_state.cur_loop);
1354                    /*tex Copy the templates from node |cur_loop| into node |p|. */
1355                    {
1356                        halfword q = lmt_alignment_state.hold_token_head;
1357                        halfword r = align_record_pre_part(lmt_alignment_state.cur_loop);
1358                        while (r) {
1359                            q = tex_store_new_token(q, token_info(r));
1360                            r = token_link(r);
1361                        }
1362                        token_link(q) = null;
1363                        align_record_pre_part(record) = token_link(lmt_alignment_state.hold_token_head);
1364                    }
1365                    {
1366                        halfword q = lmt_alignment_state.hold_token_head;
1367                        halfword r = align_record_post_part(lmt_alignment_state.cur_loop);
1368                        while (r) {
1369                            q = tex_store_new_token(q, token_info(r));
1370                            r = token_link(r);
1371                        }
1372                        token_link(q) = null;
1373                        align_record_post_part(record) = token_link(lmt_alignment_state.hold_token_head);
1374                    }
1375                    lmt_alignment_state.cur_loop = node_next(lmt_alignment_state.cur_loop);
1376                    {
1377                        halfword glue = tex_new_glue_node(lmt_alignment_state.cur_loop, tab_skip_glue);
1378                        if ((lmt_alignment_state.options & align_option_noskips) && tex_glue_is_zero(glue)) {
1379                            node_subtype(glue) = ignored_glue;
1380                        }
1381                        tex_couple_nodes(record, glue);
1382                    }
1383                } else {
1384                    chr = cr_code;
1385                    align_record_chr(lmt_alignment_state.cur_align) = chr;
1386                    tex_handle_error(
1387                        normal_error_type,
1388                        "Extra alignment tab has been changed to \\cr",
1389                        "You have given more \\span or & marks than there were in the preamble to the\n"
1390                        "\\halign or \\valign now in progress. So I'll assume that you meant to type \\cr\n"
1391                        "instead."
1392                    );
1393                }
1394            }
1395            if (! (cmd == alignment_cmd && chr == span_code)) {
1396                /*tex a new unset box */
1397                halfword cell = null;
1398                /*tex natural width */
1399                scaled width = 0;
1400                scaled size = 0;
1401                int state = 0;
1402                int packing = packing_additional;
1403                /*tex The span counter. */
1404                halfword spans = 0;
1405                tex_unsave();
1406                tex_new_save_level(align_group);
1407                /*tex Package an unset box for the current column and record its width. */
1408                state = has_box_package_state(lmt_alignment_state.cur_align, package_dimension_size_set);
1409                if (state) {
1410                    size = box_size(lmt_alignment_state.cur_align);
1411                    packing = packing_exactly;
1412                }
1413                if (cur_list.mode == restricted_hmode) {
1414                    lmt_packaging_state.post_adjust_tail = lmt_alignment_state.cur_post_adjust_tail;
1415                    lmt_packaging_state.pre_adjust_tail = lmt_alignment_state.cur_pre_adjust_tail;
1416                    lmt_packaging_state.post_migrate_tail = lmt_alignment_state.cur_post_migrate_tail;
1417                    lmt_packaging_state.pre_migrate_tail = lmt_alignment_state.cur_pre_migrate_tail;
1418                    cell = tex_filtered_hpack(cur_list.head, cur_list.tail, size, packing, align_set_group, direction_unknown, 0, null, 0, 0);
1419                    width = box_width(cell);
1420                    lmt_alignment_state.cur_post_adjust_tail = lmt_packaging_state.post_adjust_tail;
1421                    lmt_alignment_state.cur_pre_adjust_tail = lmt_packaging_state.pre_adjust_tail;
1422                    lmt_alignment_state.cur_post_migrate_tail = lmt_packaging_state.post_migrate_tail;
1423                    lmt_alignment_state.cur_pre_migrate_tail = lmt_packaging_state.pre_migrate_tail;
1424                    lmt_packaging_state.post_adjust_tail = null;
1425                    lmt_packaging_state.pre_adjust_tail = null;
1426                    lmt_packaging_state.post_migrate_tail = null;
1427                    lmt_packaging_state.pre_migrate_tail = null;
1428                    lmt_packaging_state.except = 0;
1429                } else {
1430                    cell = tex_filtered_vpack(node_next(cur_list.head), size, packing, 0, align_set_group, direction_unknown, 0, null, 0, 0, NULL);
1431                    width = box_height(cell);
1432                }
1433                if (lmt_alignment_state.cell_source) {
1434                    box_source_anchor(cell) = lmt_alignment_state.cell_source;
1435                    tex_set_box_geometry(cell, anchor_geometry);
1436                }
1437                tex_attach_attribute_list_attribute(cell, lmt_alignment_state.attr_list);
1438                if (lmt_alignment_state.cur_span != lmt_alignment_state.cur_align) {
1439                    /*tex Update width entry for spanned columns. */
1440                    halfword ptr = lmt_alignment_state.cur_span;
1441                    do {
1442                        ++spans;
1443                        ptr = node_next(node_next(ptr));
1444                    } while (ptr != lmt_alignment_state.cur_align);
1445                    if (spans > max_quarterword) {
1446                        /*tex This can happen, but won't. */
1447                        tex_confusion("too many spans");
1448                    }
1449                    ptr = lmt_alignment_state.cur_span;
1450                    while (span_span(align_record_span_ptr(ptr)) < spans) {
1451                        ptr = align_record_span_ptr(ptr);
1452                    }
1453                    if (span_span(align_record_span_ptr(ptr)) > spans) {
1454                        halfword span = tex_aux_new_span_node(align_record_span_ptr(ptr), spans, width);
1455                        align_record_span_ptr(ptr) = span;
1456                    } else if (span_width(align_record_span_ptr(ptr)) < width) {
1457                        span_width(align_record_span_ptr(ptr)) = width;
1458                    }
1459                } else if (width > box_width(lmt_alignment_state.cur_align)) {
1460                    box_width(lmt_alignment_state.cur_align) = width;
1461                }
1462                tex_aux_change_list_type(cell, unset_node);
1463                box_span_count(cell) = spans;
1464                if (! state) {
1465                    singleword order = tex_aux_determine_order(lmt_packaging_state.total_stretch);
1466                    box_glue_order(cell) = order;
1467                    box_glue_stretch(cell) = lmt_packaging_state.total_stretch[order];
1468                    order = tex_aux_determine_order(lmt_packaging_state.total_shrink);
1469                    box_glue_sign(cell) = order; /* hm, sign */
1470                    box_glue_shrink(cell) = lmt_packaging_state.total_shrink[order];
1471                }
1472                tex_pop_nest();
1473                tex_tail_append(cell);
1474                /*tex Copy the tabskip glue between columns. */
1475                if (node_subtype(node_next(lmt_alignment_state.cur_align)) != ignored_glue) {
1476                    halfword glue = tex_new_glue_node(node_next(lmt_alignment_state.cur_align), tab_skip_glue);
1477                    tex_attach_attribute_list_attribute(cell, lmt_alignment_state.attr_list);
1478                    tex_tail_append(glue);
1479                }
1480                if (cmd == alignment_cmd && (chr == cr_code || chr == cr_cr_code)) {
1481                    return 1;
1482                } else {
1483                    tex_aux_initialize_span(record);
1484                }
1485            }
1486            lmt_input_state.align_state = busy_alignment_state;
1487            do {
1488                tex_get_x_or_protected();
1489            } while (cur_cmd == spacer_cmd);
1490            lmt_alignment_state.cur_align = record;
1491            tex_aux_initialize_column();
1492        }
1493    }
1494    return 0;
1495}
1496
1497/*tex
1498
1499    At the end of a row, we append an unset box to the current vlist (for |\halign|) or the current
1500    hlist (for |\valign|). This unset box contains the unset boxes for the columns, separated by
1501    the tabskip glue. Everything will be set later.
1502
1503*/
1504
1505static void tex_aux_finish_row(void)
1506{
1507    halfword row;
1508    if (cur_list.mode == restricted_hmode) {
1509        row = tex_filtered_hpack(cur_list.head, cur_list.tail, 0, packing_additional, finish_row_group, direction_unknown, 0, null, 0, 0);
1510        tex_pop_nest();
1511        if (lmt_alignment_state.cur_pre_adjust_head != lmt_alignment_state.cur_pre_adjust_tail) {
1512            tex_inject_adjust_list(lmt_alignment_state.cur_pre_adjust_head, 0, null, NULL);
1513        }
1514        if (lmt_alignment_state.cur_pre_migrate_head != lmt_alignment_state.cur_pre_migrate_tail) {
1515            tex_append_list(lmt_alignment_state.cur_pre_migrate_head, lmt_alignment_state.cur_pre_migrate_tail);
1516        }
1517        tex_append_to_vlist(row, lua_key_index(alignment), NULL);
1518        if (lmt_alignment_state.cur_post_migrate_head != lmt_alignment_state.cur_post_migrate_tail) {
1519            tex_append_list(lmt_alignment_state.cur_post_migrate_head, lmt_alignment_state.cur_post_migrate_tail);
1520        }
1521        if (lmt_alignment_state.cur_post_adjust_head != lmt_alignment_state.cur_post_adjust_tail) {
1522            tex_inject_adjust_list(lmt_alignment_state.cur_post_adjust_head, 0, null, NULL);
1523        }
1524    } else {
1525        row = tex_filtered_vpack(node_next(cur_list.head), 0, packing_additional, max_depth_par, finish_row_group, direction_unknown, 0, null, 0, 0, NULL);
1526        tex_pop_nest();
1527        tex_tail_append(row);
1528        cur_list.space_factor = default_space_factor;
1529    }
1530    /*tex 
1531        Currently this one can be overloaded by the one set on the row via the noalign trickery
1532        which is probably okay. 
1533    */
1534    if (lmt_alignment_state.wrap_source) {
1535        box_source_anchor(row) = lmt_alignment_state.wrap_source;
1536        box_geometry(row) |= anchor_geometry;
1537    }
1538    /* 
1539        This also wipes (list) fields that we might set below, like |xoffset| that is used for 
1540        specific alignments purposes. 
1541    */
1542    tex_aux_change_list_type(row, unset_node);
1543    /* */
1544    tex_attach_attribute_list_attribute(row, lmt_alignment_state.row_state.attrlist ? 
1545        lmt_alignment_state.row_state.attrlist : lmt_alignment_state.attr_list);
1546    /*tex 
1547        The next blob of code duplicates some of packaging code but because we fetch from different
1548        fields we cannot share. Maybe, when I add this kind of features to other mechanisms (how 
1549        about cells!) then the next code will become some helper. 
1550    */
1551    if (lmt_alignment_state.row_state_set) { 
1552        halfword orientation = lmt_alignment_state.row_state.orientation;
1553        halfword anchor = lmt_alignment_state.row_state.anchor;
1554        scaled shift = lmt_alignment_state.row_state.shift;
1555        halfword source = lmt_alignment_state.row_state.source;
1556        halfword target = lmt_alignment_state.row_state.target;
1557        scaled xoffset = lmt_alignment_state.row_state.xoffset;
1558        scaled yoffset = lmt_alignment_state.row_state.yoffset;
1559        scaled xmove = lmt_alignment_state.row_state.xmove;
1560        scaled ymove = lmt_alignment_state.row_state.ymove;
1561        singleword geometry = box_geometry(row);
1562        /* */
1563        if (xoffset || yoffset || xmove || ymove) {
1564            geometry |= offset_geometry;
1565        }
1566        if (orientation) {
1567            geometry |= orientation_geometry;
1568        }
1569        /* */
1570        if (tex_has_geometry(geometry, offset_geometry) || tex_has_geometry(geometry, orientation_geometry)) {
1571            scaled wd = box_width(row);
1572            scaled ht = box_height(row);
1573            scaled dp = box_depth(row);
1574            if (xmove) {
1575                xoffset = tex_aux_checked_dimension1(xoffset + xmove);
1576                wd = tex_aux_checked_dimension2(wd + xmove);
1577                set_box_package_state(row, package_dimension_size_set); /* safeguard */
1578            }
1579            if (ymove) {
1580                yoffset = tex_aux_checked_dimension1(yoffset + ymove);
1581                ht = tex_aux_checked_dimension2(ht + ymove);
1582                dp = tex_aux_checked_dimension2(dp - ymove);
1583            }
1584            box_w_offset(row) = wd;
1585            box_h_offset(row) = ht;
1586            box_d_offset(row) = dp;
1587            switch (orientationonly(orientation)) {
1588                case 0 : /*   0 */
1589                    break;
1590                case 2 : /* 180 */
1591                    box_height(row) = dp;
1592                    box_depth(row) = ht;
1593                    geometry |= orientation_geometry;
1594                    break;
1595                case 1 : /*  90 */
1596                case 3 : /* 270 */
1597                    box_width(row) = ht + dp;
1598                    box_height(row) = wd;
1599                    box_depth(row) = 0;
1600                    geometry |= orientation_geometry;
1601                    break;
1602                case 4 : /*   0 */
1603                    box_height(row) = ht + dp;
1604                    box_depth(row) = 0;
1605                    geometry |= orientation_geometry;
1606                    break;
1607                case 5 : /* 180 */
1608                    box_height(row) = 0;
1609                    box_depth(row) = ht + dp;
1610                    geometry |= orientation_geometry;
1611                    break;
1612                default :
1613                    break;
1614            }
1615            if (xoffset || yoffset) {
1616                box_x_offset(row) = xoffset;
1617                box_y_offset(row) = yoffset;
1618                geometry |= offset_geometry;
1619            }
1620        }
1621        if (shift) { 
1622            box_shift_amount(row) = shift;
1623        }
1624        if (source || target) {
1625            box_source_anchor(row) = source;
1626            box_target_anchor(row) = target;
1627            geometry |= anchor_geometry;
1628        }
1629        box_anchor(row) = anchor;
1630        box_orientation(row) = orientation;
1631        box_geometry(row) = (singleword) geometry;
1632    }
1633    /*tex
1634        We no longer need the row state so best wipe it now! Then we're ready for the next row. 
1635    */
1636    tex_aux_wipe_row_state();
1637    /* */
1638    if (every_cr_par) {
1639        tex_begin_token_list(every_cr_par, every_cr_text);
1640    }
1641    tex_aux_align_peek();
1642    /*tex Note that |glue_shrink(p) = 0| since |glue_shrink == shift_amount|. */
1643}
1644
1645/*tex
1646
1647    Finally, we will reach the end of the alignment, and we can breathe a sigh of relief that
1648    memory hasn't overflowed. All the unset boxes will now be set so that the columns line up,
1649    taking due account of spanned columns.
1650
1651    Normalizing by stripping zero tabskips makes the lists a little smaller which then is easier
1652    on later processing. But is is an option. We could actually not inject zero skips at all but
1653    then the code starts deviating too much. In some cases it can save a lot of zero glue nodes
1654    but we allocate them initially anyway. We don't save runtime here. (Some day I'll play a bit
1655    more with this and then probably also implement some pending extensions.)
1656
1657*/
1658
1659static void tex_aux_strip_zero_tab_skips(halfword q)
1660{
1661    halfword h = box_list(q);
1662    halfword t = h;
1663    while (t) {
1664        halfword n = node_next(t);
1665        if (node_type(t) == glue_node && node_subtype(t) == tab_skip_glue && tex_glue_is_zero(t)) {
1666            tex_try_couple_nodes(node_prev(t),n);
1667            if (t == h) {
1668                /*tex We only come here once. */
1669                h = n;
1670                box_list(q) = h;
1671            }
1672            tex_flush_node(t);
1673        }
1674        t = n;
1675    }
1676}
1677
1678/*tex 
1679    We currently have a mix of states but maybe some day we will exposer the save stack and then it
1680    is handy to have the state values there. So for now I keep this (as reminder). 
1681*/
1682
1683static void tex_aux_finish_align(void)
1684{
1685    /*tex a shared register for the list operations (others are localized) */
1686    halfword preroll;
1687    /*tex shift offset for unset boxes */
1688    scaled offset = 0;
1689    /*tex something new */
1690    halfword reverse = lmt_alignment_state.options & align_option_reverse;
1691    halfword callback = lmt_alignment_state.options & align_option_callback;
1692    halfword discard = normalize_line_mode_option(discard_zero_tab_skips_mode) || (lmt_alignment_state.options & align_option_discard);
1693    halfword amount = 0;
1694    halfword mode = 0;
1695    /*tex The |align_group| was for individual entries: */
1696    if (cur_group != align_group) {
1697        tex_confusion("align, case 1");
1698    }
1699    tex_unsave();
1700    /*tex The |align_group| was for the whole alignment: */
1701    if (cur_group != align_group) {
1702        tex_confusion("align, case 2");
1703    }
1704    tex_unsave();
1705    if (lmt_nest_state.nest[lmt_nest_state.nest_data.ptr - 1].mode == mmode) {
1706        offset = display_indent_par;
1707    }
1708    lmt_save_state.save_stack_data.ptr -= saved_align_n_of_records;
1709    lmt_packaging_state.pack_begin_line = -cur_list.mode_line;
1710    /*tex
1711        All content is available now so this is a perfect spot for some processing. However, we
1712        cannot mess with the unset boxes (as these can have special properties). The main reason
1713        for some postprocessing can be to align (vertically) at a specific location in a cell
1714        but then we also need to process twice (and adapt the width in the preamble record).
1715
1716        We flush the tokenlists so that in principle we can access the align record nodes as normal
1717        lists.
1718    */
1719    amount = saved_align_amount;
1720    mode = saved_align_mode;
1721 /* callback = saved_align_callback; */ /* also in state record */
1722    {
1723        halfword q = node_next(preamble);
1724        do {
1725            tex_flush_token_list(align_record_pre_part(q));
1726            tex_flush_token_list(align_record_post_part(q));
1727            align_record_pre_part(q) = null;
1728            align_record_post_part(q) = null;
1729            q = node_next(node_next(q));
1730        } while (q);
1731    }
1732    if (callback) {
1733        lmt_alignment_callback(cur_list.head, preroll_pass_alignment_context, lmt_alignment_state.callback, lmt_alignment_state.attr_list, preamble);
1734    }
1735    /*tex
1736
1737        Go through the preamble list, determining the column widths and changing the alignrecords
1738        to dummy unset boxes.
1739
1740        It's time now to dismantle the preamble list and to compute the column widths. Let $w_{ij}$
1741        be the maximum of the natural widths of all entries that span columns $i$ through $j$,
1742        inclusive. The alignrecord for column~$i$ contains  $w_{ii}$ in its |width| field, and there
1743        is also a linked list of the nonzero $w_{ij}$ for increasing $j$, accessible via the |info|
1744        field; these span nodes contain the value $j-i+|min_quarterword|$ in their |link| fields.
1745        The values of $w_{ii}$ were initialized to |null_flag|, which we regard as $-\infty$.
1746
1747        The final column widths are defined by the formula $$ w_j = \max_{1\L i\L j} \biggl( w_{ij}
1748        - \sum_{i\L k < j}(t_k + w_k) \biggr), $$ where $t_k$ is the natural width of the tabskip
1749        glue between columns $k$ and~$k + 1$. However, if $w_{ij} = -\infty$ for all $i$ in the
1750        range $1 <= i <= j$ (i.e., if every entry that involved column~$j$ also involved column~$j
1751        + 1$), we let $w_j = 0$, and we zero out the tabskip glue after column~$j$.
1752
1753        \TEX\ computes these values by using the following scheme: First $w_1 = w_{11}$. Then
1754        replace $w_{2j}$ by $\max(w_{2j}, w_{1j} - t_1 - w_1)$, for all $j > 1$. Then $w_2 =
1755        w_{22}$. Then replace $w_{3j}$ by $\max(w_{3j}, w_{2j} - t_2 - w_2)$ for all $j > 2$; and
1756        so on. If any $w_j$ turns out to be $-\infty$, its value is changed to zero and so is the
1757        next tabskip.
1758
1759    */
1760    {
1761        halfword q = node_next(preamble);
1762        do {
1763            /* So |q| and |p| point to alignment nodes that become unset ones. */
1764            halfword p = node_next(node_next(q));
1765            if (box_width(q) == null_flag) {
1766                /*tex Nullify |width(q)| and the tabskip glue following this column. */
1767                box_width(q) = 0;
1768                tex_reset_glue_to_zero(node_next(q));
1769            }
1770            if (align_record_span_ptr(q) != end_span) {
1771                /*tex
1772
1773                    Merge the widths in the span nodes of |q| with those of |p|, destroying the
1774                    span nodes of |q|.
1775
1776                    Merging of two span-node lists is a typical exercise in the manipulation of
1777                    linearly linked data structures. The essential invariant in the following
1778                    |repeat| loop is that we want to dispense with node |r|, in |q|'s list, and
1779                    |u| is its successor; all nodes of |p|'s list up to and including |s| have
1780                    been processed, and the successor of |s| matches |r| or precedes |r| or follows
1781                    |r|, according as |link(r) = n| or |link(r) > n| or |link(r) < n|.
1782
1783                */
1784                halfword t = box_width(q) + glue_amount(node_next(q));
1785                halfword n = 1;
1786                halfword r = align_record_span_ptr(q);
1787                halfword s = end_span;
1788                align_record_span_ptr(s) = p;
1789                do {
1790                    halfword u = align_record_span_ptr(r);
1791                    span_width(r) -= t;
1792                    while (span_span(r) > n) {
1793                        s = align_record_span_ptr(s);
1794                        n = span_span(align_record_span_ptr(s)) + 1;
1795                    }
1796                    if (span_span(r) < n) {
1797                        align_record_span_ptr(r) = align_record_span_ptr(s);
1798                        align_record_span_ptr(s) = r;
1799                        --span_span(r);
1800                        s = r;
1801                    } else {
1802                        if (span_width(r) > span_width(align_record_span_ptr(s))) {
1803                            span_width(align_record_span_ptr(s)) = span_width(r);
1804                        }
1805                        tex_flush_node(r);
1806                    }
1807                    r = u;
1808                } while (r != end_span);
1809            }
1810            tex_aux_change_list_type(q, unset_node);
1811            box_glue_order(q) = normal_glue_order;
1812            box_glue_sign(q) = normal_glue_sign;
1813            box_height(q) = 0;
1814            box_depth(q) = 0;
1815            q = p;
1816        } while (q);
1817    }
1818    if (callback) {
1819        lmt_alignment_callback(cur_list.head, package_pass_alignment_context, lmt_alignment_state.callback, lmt_alignment_state.attr_list, preamble);
1820    }
1821    /*tex
1822
1823        Package the preamble list, to determine the actual tabskip glue amounts, and let |p| point
1824        to this prototype box.
1825
1826        Now the preamble list has been converted to a list of alternating unset boxes and tabskip
1827        glue, where the box widths are equal to the final column sizes. In case of |\valign|, we
1828        change the widths to heights, so that a correct error message will be produced if the
1829        alignment is overfull or underfull.
1830
1831    */
1832    if (cur_list.mode == internal_vmode) {
1833        halfword rule_save = overfull_rule_par;
1834        /*tex Prevent the rule from being packaged. */
1835        overfull_rule_par = 0; 
1836        preroll = tex_hpack(preamble, amount, mode, direction_unknown, holding_none_option, box_limit_none);
1837        overfull_rule_par = rule_save;
1838    } else {
1839        halfword unset = node_next(preamble);
1840        do {
1841            box_height(unset) = box_width(unset);
1842            box_width(unset) = 0;
1843            unset = node_next(node_next(unset));
1844        } while (unset);
1845        /* why filtered here ... */
1846        preroll = tex_filtered_vpack(preamble, amount, mode, max_depth_par, preamble_group, direction_unknown, 0, 0, 0, holding_none_option, NULL);
1847        /* ... so we'll do this soon instead: */
1848     /* preroll = tex_vpack(preamble, saved_value(saved_align_specification), saved_extra(saved_align_specification), max_depth_par, direction_unknown, migrate_all_option); */
1849        unset = node_next(preamble);
1850        do {
1851            box_width(unset) = box_height(unset);
1852            box_height(unset) = 0;
1853            unset = node_next(node_next(unset));
1854        } while (unset);
1855    }
1856    lmt_packaging_state.pack_begin_line = 0;
1857    /*tex
1858        Here we set the glue in all the unset boxes of the current list based on the prerolled
1859        preamble.
1860    */
1861    {
1862        halfword rowptr = node_next(cur_list.head);
1863        while (rowptr) {
1864            switch (node_type(rowptr)) {
1865                 case unset_node:
1866                    {
1867                        /*tex
1868                            We set the unset box |q| and the unset boxes in it. The unset box |q|
1869                            represents a row that contains one or more unset boxes, depending on
1870                            how soon |\cr| occurred in that row.
1871
1872                            We also reset some fields but this needs checking because we never set
1873                            set them in these unset boxes but in the preamble ones.
1874                        */
1875                        halfword preptr;
1876                        halfword colptr;
1877                        if (cur_list.mode == internal_vmode) {
1878                         /* tex_aux_change_list_type(rowptr, hlist_node); */ /* too much, needs checking */
1879                            node_type(rowptr) = hlist_node;
1880                            box_width(rowptr) = box_width(preroll);
1881                        } else {
1882                         /* tex_aux_change_list_type(rowptr, vlist_node); */ /* too much, needs checking */
1883                            node_type(rowptr) = vlist_node;
1884                            box_height(rowptr) = box_height(preroll);
1885                        }
1886                        node_subtype(rowptr) = align_row_list;
1887                        box_glue_order(rowptr) = box_glue_order(preroll);
1888                        box_glue_sign(rowptr) = box_glue_sign(preroll);
1889                        box_glue_set(rowptr) = box_glue_set(preroll);
1890                        box_shift_amount(rowptr) = offset;
1891                        colptr = box_list(rowptr);
1892                        preptr = box_list(preroll);
1893                        if (node_type(colptr) == glue_node) {
1894                            colptr = node_next(colptr);
1895                        }
1896                        if (node_type(preptr) == glue_node) {
1897                            preptr = node_next(preptr);
1898                        }
1899                        if (node_type(colptr) != unset_node) {
1900                            tex_formatted_error("alignment", "bad box");
1901                        }
1902                        do {
1903                            /*tex
1904                                We set the glue in node |r| and change it from an unset node. A box
1905                                made from spanned columns will be followed by tabskip glue nodes
1906                                and by empty boxes as if there were no spanning. This permits
1907                                perfect alignment of subsequent entries, and it prevents values
1908                                that depend on floating point arithmetic from entering into the
1909                                dimensions of any boxes.
1910                            */
1911                            halfword spans = box_span_count(colptr);
1912                            scaled total = box_width(preptr);
1913                            scaled width = total; /*tex The width of a column. */
1914                            halfword tail = hold_head;
1915                            int state = has_box_package_state(preptr, package_dimension_size_set);
1916                            /*tex
1917                                When we have a span we need to add dummies. We append tabskip glue
1918                                and an empty box to list |u|, and update |s| and |t| as the
1919                                prototype nodes are passed. We could shortcut some code when we
1920                                have zero skips but we seldom end up in this branch anyway.
1921                            */
1922                            while (spans > 0) {
1923                                --spans;
1924                                preptr = node_next(preptr);
1925                                if (node_subtype(preptr) != ignored_glue) {
1926                                 /* halfword glue = tex_new_glue_node(preptr, tab_skip_glue); */
1927                                    halfword glue = tex_new_glue_node(preptr, node_subtype(preptr));
1928                                    tex_try_couple_nodes(tail, glue);
1929                                    tex_attach_attribute_list_attribute(glue, lmt_alignment_state.attr_list);
1930                                    total += glue_amount(preptr);
1931                                    /*tex The |glueratio| case is redundant, anyway ... */
1932                                    switch (box_glue_sign(preroll)) {
1933                                        case stretching_glue_sign:
1934                                            if (glue_stretch_order(preptr) == box_glue_order(preroll)) {
1935                                                total += glueround((glueratio) (box_glue_set(preroll)) * (glueratio) (glue_stretch(preptr)));
1936                                            }
1937                                            break;
1938                                        case shrinking_glue_sign:
1939                                            if (glue_shrink_order(preptr) == box_glue_order(preroll)) {
1940                                                total -= glueround((glueratio) (box_glue_set(preroll)) * (glueratio) (glue_shrink(preptr)));
1941                                            }
1942                                            break;
1943                                    }
1944                                    tail = glue;
1945                                    /*tex Move on to the box. */
1946                                }
1947                                preptr = node_next(preptr);
1948                                {
1949                                    halfword box = tex_new_null_box_node(cur_list.mode == internal_vmode ? hlist_node : vlist_node, align_cell_list);
1950                                    tex_couple_nodes(tail, box);
1951                                    tex_attach_attribute_list_attribute(box, lmt_alignment_state.attr_list);
1952                                    total += box_width(preptr);
1953                                    if (cur_list.mode == internal_vmode) {
1954                                        box_width(box) = box_width(preptr);
1955                                    } else {
1956                                        box_height(box) = box_width(preptr);
1957                                    }
1958                                    tail = box;
1959                                }
1960                            }
1961                            if (cur_list.mode == internal_vmode) {
1962                                /*tex
1963                                    Make the unset node |r| into an |hlist_node| of width |w|,
1964                                    setting the glue as if the width were |t|.
1965                                */
1966                                box_height(colptr) = box_height(rowptr);
1967                                box_depth(colptr) = box_depth(rowptr);
1968                                if (! state) {
1969                                    if (total == box_width(colptr)) {
1970                                        box_glue_sign(colptr) = normal_glue_sign;
1971                                        box_glue_order(colptr) = normal_glue_order;
1972                                        box_glue_set(colptr) = 0.0;
1973                                    } else if (total > box_width(colptr)) {
1974                                        box_glue_sign(colptr) = stretching_glue_sign;
1975                                        if (box_glue_stretch(colptr) == 0) {
1976                                            box_glue_set(colptr) = 0.0;
1977                                        } else {
1978                                            box_glue_set(colptr) = (glueratio) ( ( (glueratio) total - (glueratio) box_width(colptr) ) / ( (glueratio) box_glue_stretch(colptr) ) );
1979                                        }
1980                                    } else {
1981                                        box_glue_order(colptr) = box_glue_sign(colptr);
1982                                        box_glue_sign(colptr) = shrinking_glue_sign;
1983                                        if (box_glue_shrink(colptr) == 0) {
1984                                            box_glue_set(colptr) = 0.0;
1985                                        } else if ((box_glue_order(colptr) == normal_glue_order) && (box_width(colptr) - total > box_glue_shrink(colptr))) {
1986                                            box_glue_set(colptr) = 1.0;
1987                                        } else {
1988                                            box_glue_set(colptr) = (glueratio) ( ( (glueratio) box_width(colptr) - (glueratio) total ) / ( (glueratio) box_glue_shrink(colptr) ) );
1989                                        }
1990                                    }
1991                                }
1992                                box_width(colptr) = width;
1993                                tex_aux_change_list_type(colptr, hlist_node);
1994                                node_subtype(colptr) = align_cell_list;
1995                            } else {
1996                                /*tex
1997                                    Make the unset node |r| into a |vlist_node| of height |w|,
1998                                    setting the glue as if the height were |t|.
1999                                */
2000                                box_width(colptr) = box_width(rowptr);
2001                                if (! state) {
2002                                    if (total == box_height(colptr)) {
2003                                        box_glue_sign(colptr) = normal_glue_sign;
2004                                        box_glue_order(colptr) = normal_glue_order;
2005                                        box_glue_set(colptr) = 0.0;
2006                                    } else if (total > box_height(colptr)) {
2007                                        box_glue_sign(colptr) = stretching_glue_sign;
2008                                        if (box_glue_stretch(colptr) == 0) {
2009                                            box_glue_set(colptr) = 0.0;
2010                                        } else {
2011                                            box_glue_set(colptr) = (glueratio) ( ( (glueratio) total - (glueratio) box_height(colptr) ) / ( (glueratio) box_glue_stretch(colptr) ) );
2012                                        }
2013                                    } else {
2014                                        box_glue_order(colptr) = box_glue_sign(colptr);
2015                                        box_glue_sign(colptr) = shrinking_glue_sign;
2016                                        if (box_glue_shrink(colptr) == 0) {
2017                                            box_glue_set(colptr) = 0.0;
2018                                        } else if ((box_glue_order(colptr) == normal_glue_order) && (box_height(colptr) - total > box_glue_shrink(colptr))) {
2019                                            box_glue_set(colptr) = 1.0;
2020                                        } else {
2021                                            box_glue_set(colptr) = (glueratio) ( ( (glueratio) box_height(colptr) - (glueratio) total) / ( (glueratio) box_glue_shrink(colptr) ) );
2022                                        }
2023                                    }
2024                                }
2025                                box_height(colptr) = width;
2026                                tex_aux_change_list_type(colptr, vlist_node);
2027                                node_subtype(colptr) = align_cell_list;
2028                            }
2029                            box_shift_amount(colptr) = 0;
2030                            if (tail != hold_head) {
2031                                /*tex Append blank boxes to account for spanned nodes. */
2032                                tex_try_couple_nodes(tail, node_next(colptr));
2033                                tex_try_couple_nodes(colptr, node_next(hold_head));
2034                                colptr = tail;
2035                            }
2036                            colptr = node_next(colptr);
2037                            preptr = node_next(preptr);
2038                            if (node_type(colptr) == glue_node) {
2039                                colptr = node_next(colptr);
2040                            }
2041                            if (node_type(preptr) == glue_node) {
2042                                preptr = node_next(preptr);
2043                            }
2044                        } while (colptr);
2045                        if (discard) {
2046                            tex_aux_strip_zero_tab_skips(rowptr);
2047                        }
2048                        if (reverse) {
2049                            box_list(rowptr) = tex_reversed_node_list(box_list(rowptr));
2050                        }
2051                        if (has_box_package_state(rowptr, package_dimension_size_set)) {
2052                            if (box_w_offset(rowptr) > box_width(rowptr)) {
2053                                box_width(rowptr) = box_w_offset(rowptr); 
2054                            }
2055                        }
2056                    }
2057                    break;
2058                case rule_node:
2059                    {
2060                        /*tex
2061                            Make the running dimensions in rule |q| extend to the boundaries of the
2062                            alignment.
2063                        */
2064                        if (rule_width(rowptr) == null_flag) {
2065                            rule_width(rowptr) = box_width(preroll);
2066                        }
2067                        if (rule_height(rowptr) == null_flag) {
2068                            rule_height(rowptr) = box_height(preroll);
2069                        }
2070                        if (rule_depth(rowptr) == null_flag) {
2071                            rule_depth(rowptr) = box_depth(preroll);
2072                        }
2073                        /*tex We could use offset fields in rule instead. */
2074                        if (offset) {
2075                            halfword prv = node_prev(rowptr);
2076                            halfword nxt = node_next(rowptr);
2077                            halfword box = null;
2078                            node_prev(rowptr) = null;
2079                            node_next(rowptr) = null;
2080                            box = tex_hpack(rowptr, 0, packing_additional, direction_unknown, holding_none_option, box_limit_none);
2081                            tex_attach_attribute_list_attribute(box, rowptr);
2082                            box_shift_amount(box) = offset;
2083                            node_subtype(box) = align_cell_list; /*tex This is not really a cell. */
2084                         // node_subtype(box) = unknown_list;    /*tex So maybe we will do this. */
2085                            tex_try_couple_nodes(prv, box);
2086                            tex_try_couple_nodes(box, nxt);
2087                            rowptr = box;
2088                        }
2089                    }
2090                    break;
2091                default:
2092                    /*tex
2093                        When we're in a |\halign| we get the rows (the |unset_node|s) while the
2094                        rules are horizontal ones. Furthermore we can get (vertical) glues and
2095                        whatever else got kicked in between the rows, but all that is (currently)
2096                        not processed.
2097                    */
2098                    break;
2099            }
2100            rowptr = node_next(rowptr);
2101        }
2102    }
2103    if (callback) {
2104        lmt_alignment_callback(cur_list.head, wrapup_pass_alignment_context, lmt_alignment_state.callback, lmt_alignment_state.attr_list, preamble);
2105    }
2106    tex_flush_node_list(preroll);
2107    delete_attribute_reference(lmt_alignment_state.attr_list);
2108    tex_aux_pop_alignment();
2109    /*tex
2110        We now have a completed alignment, in the list that starts at |cur_list.head| and ends at
2111        |cur_list.tail|. This list will be merged with the one that encloses it. (In case the
2112        enclosing mode is |mmode|, for displayed formulas, we will need to insert glue before and
2113        after the display; that part of the program will be deferred until we're more familiar with
2114        such operations.)
2115    */
2116    {
2117        scaled prevdepth = cur_list.prev_depth;
2118        halfword head = node_next(cur_list.head);
2119        halfword tail = cur_list.tail;
2120        tex_pop_nest();
2121        if (cur_list.mode == mmode) {
2122            tex_finish_display_alignment(head, tail, prevdepth);
2123        } else {
2124            cur_list.prev_depth = prevdepth;
2125            if (head) {
2126                tex_tail_append(head);
2127                cur_list.tail = tail;
2128            }
2129            if (cur_list.mode == vmode) {
2130                tex_build_page(alignment_page_context, 0);
2131            }
2132        }
2133    }
2134}
2135
2136/*tex
2137
2138    The token list |omit_template| just referred to is a constant token list that contains the
2139    special control sequence |\endtemplate| only.
2140
2141*/
2142
2143void tex_initialize_alignments(void)
2144{
2145    lmt_alignment_state.hold_token_head = tex_get_available_token(null);
2146    lmt_alignment_state.omit_template = tex_get_available_token(deep_frozen_end_template_token);
2147    span_span(end_span) = max_quarterword + 1;
2148    align_record_span_ptr(end_span) = null;
2149}
2150
2151/*tex
2152*
2153    We no longer store |hold_token_head| and |omit_template| in the format file. It is a bit
2154    cleaner to just initialize them. So we free them.
2155
2156*/
2157
2158void tex_cleanup_alignments(void)
2159{
2160    tex_put_available_token(lmt_alignment_state.hold_token_head);
2161    tex_put_available_token(lmt_alignment_state.omit_template);
2162    lmt_alignment_state.hold_token_head = null;
2163    lmt_alignment_state.omit_template = null;
2164    delete_attribute_reference(lmt_alignment_state.attr_list);
2165    lmt_alignment_state.attr_list = null;
2166}
2167
2168/*tex
2169
2170    We've now covered most of the abuses of |\halign| and |\valign|. Let's take a look at what
2171    happens when they are used correctly.
2172
2173    An |align_group| code is supposed to remain on the |save_stack| during an entire alignment,
2174    until |finish_align| removes it.
2175
2176    A devious user might force an |end_template| command to occur just about anywhere; we must
2177    defeat such hacks.
2178
2179*/
2180
2181void tex_run_alignment_end_template(void)
2182{
2183    lmt_input_state.base_ptr = lmt_input_state.input_stack_data.ptr;
2184    lmt_input_state.input_stack[lmt_input_state.base_ptr] = lmt_input_state.cur_input;
2185    while ((  lmt_input_state.input_stack[lmt_input_state.base_ptr].index != template_post_text)
2186        && (! lmt_input_state.input_stack[lmt_input_state.base_ptr].loc)
2187        && (  lmt_input_state.input_stack[lmt_input_state.base_ptr].state == token_list_state)) {
2188        --lmt_input_state.base_ptr;
2189    }
2190    if (lmt_input_state.input_stack[lmt_input_state.base_ptr].index != template_post_text) {
2191        tex_alignment_interwoven_error(2);
2192    } else if (lmt_input_state.input_stack[lmt_input_state.base_ptr].loc)  {
2193        tex_alignment_interwoven_error(3);
2194    } else if (lmt_input_state.input_stack[lmt_input_state.base_ptr].state != token_list_state) {
2195        tex_alignment_interwoven_error(4);
2196    } else if (cur_group == align_group) {
2197        if (! tex_wrapped_up_paragraph(align_par_context, 0)) { /* needs testing */
2198            tex_end_paragraph(align_group, align_par_context);
2199            if (tex_aux_finish_column()) {
2200                tex_aux_finish_row();
2201            }
2202        }
2203    } else {
2204        tex_off_save();
2205    }
2206}
2207
2208/*tex
2209
2210    When |\cr| or |\span| or a tab mark comes through the scanner into |main_control|, it might be
2211    that the user has foolishly inserted one of them into something that has nothing to do with
2212    alignment. But it is far more likely that a left brace or right brace has been omitted, since
2213    |get_next| takes actions appropriate to alignment only when |\cr| or |\span| or tab marks occur
2214    with |align_state = 0|. The following program attempts to make an appropriate recovery.
2215
2216    As an experiment we support nested |\noalign| usage but we do keep the braces so there is still
2217    grouping. We don't flag these groups as |no_align_group| because then we need to do more work
2218    and it's not worth the trouble. One can actually argue for not doing that anyway.
2219
2220    I might now rename the next one to |run_alignment| (and then also a companion as we have two
2221    cases of usage).
2222
2223*/
2224
2225void tex_run_alignment_error(void)
2226{
2227    int cmd = cur_cmd;
2228    int chr = cur_chr;
2229    if (cmd == alignment_cmd && chr == no_align_code) {
2230        if (tex_aux_nested_no_align()) {
2231            /* */
2232        } else { 
2233            tex_handle_error(
2234                normal_error_type,
2235                "Misplaced \\noalign",
2236                "I expect to see \\noalign only after the \\cr of an alignment. Proceed, and I'll\n"
2237                "ignore this case."
2238            );
2239        }
2240    } else if (abs(lmt_input_state.align_state) > 2) {
2241        /*tex
2242            Express consternation over the fact that no alignment is in progress. In traditional
2243            \TEX\ the ampersand case will show a specific tab help, while in case of another
2244            character a more generic message is shown.
2245
2246            We go for consistency here, so a little patch:
2247        */
2248        switch (cmd) {
2249            case alignment_tab_cmd:
2250                tex_handle_error(normal_error_type, "Misplaced %C", cmd, chr,
2251                    "I can't figure out why you would want to use a tab mark here. If some right brace\n"
2252                    "up above has ended a previous alignment prematurely, you're probably due for more\n"
2253                    "error messages."
2254                );
2255                break;
2256            default:
2257                tex_handle_error(normal_error_type, "Misplaced %C", cmd, chr,
2258                    "I can't figure out why you would want to use a tab mark or \\cr or \\span just\n"
2259                    "now. If something like a right brace up above has ended a previous alignment\n"
2260                    "prematurely, you're probably due for more error messages."
2261                );
2262                break;
2263        }
2264    } else {
2265        const char * helpinfo =
2266            "I've put in what seems to be necessary to fix the current column of the current\n"
2267            "alignment. Try to go on, since this might almost work.";
2268        tex_back_input(cur_tok);
2269        if (lmt_input_state.align_state < 0) {
2270            ++lmt_input_state.align_state;
2271            cur_tok = left_brace_token + '{';
2272            tex_handle_error(
2273                insert_error_type,
2274                "Missing { inserted",
2275                helpinfo
2276            );
2277        } else {
2278            --lmt_input_state.align_state;
2279            cur_tok = right_brace_token + '}';
2280            switch (cmd) {
2281                case alignment_cmd:
2282                    tex_handle_error(
2283                        insert_error_type,
2284                        "Missing } inserted, unexpected ",
2285                        cmd, chr,
2286                        helpinfo
2287                    );
2288                    break;
2289                case alignment_tab_cmd:
2290                    tex_handle_error(
2291                        insert_error_type,
2292                        "Missing } inserted, unexpected tab character (normally &)",
2293                        helpinfo
2294                    );
2295                    break;
2296            }
2297        }
2298    }
2299}
2300