diff options
Diffstat (limited to 'source/luametatex/source/tex/texalign.c')
-rw-r--r-- | source/luametatex/source/tex/texalign.c | 1854 |
1 files changed, 1854 insertions, 0 deletions
diff --git a/source/luametatex/source/tex/texalign.c b/source/luametatex/source/tex/texalign.c new file mode 100644 index 000000000..207895a6d --- /dev/null +++ b/source/luametatex/source/tex/texalign.c @@ -0,0 +1,1854 @@ +/* + See license.txt in the root of this project. +*/ + +# include "luametatex.h" + +/*tex + + It's sort of a miracle whenever |halign and |valign| work, because they cut across so many of + the control structures of \TEX. Therefore the present page is probably not the best place for + a beginner to start reading this program; it is better to master everything else first. + + Let us focus our thoughts on an example of what the input might be, in order to get some idea + about how the alignment miracle happens. The example doesn't do anything useful, but it is + sufficiently general to indicate all of the special cases that must be dealt with; please do + not be disturbed by its apparent complexity and meaninglessness. + + \starttyping + \tabskip 2pt plus 3pt + \halign to 300pt{u1#v1& + \hskip 50pt \tabskip 1pt plus 1fil u2#v2& + \hskip 50pt u3#v3\cr + \hskip 25pt a1&\omit a2&\vrule\cr + \hskip 25pt \noalign\{\vskip 3pt} + \hskip 25pt b1\span b2\cr + \hskip 25pt \omit&c2\span\omit\cr} + \stoptyping + + Here's what happens: + + \startitemize + + \startitem + When |\halign to 300pt {}| is scanned, the |scan_align_spec| routine places the 300pt + dimension onto the |save_stack|, and an |align_group| code is placed above it. This + will make it possible to complete the alignment when the matching right brace is found. + \stopitem + + \startitem + The preamble is scanned next. Macros in the preamble are not expanded, except as part + of a tabskip specification. For example, if |u2| had been a macro in the preamble above, + it would have been expanded, since \TEX\ must look for |minus ...| as part of the + tabskip glue. A preamble list is constructed based on the user's preamble; in our case + it contains the following seven items: + + \starttabulate + \NC \type{\glue 2pt plus 3pt} \NC the tabskip preceding column 1 \NC \NR + \NC \type{\alignrecord} of width $-\infty$ \NC preamble info for column 1 \NC \NR + \NC \type{\glue 2pt plus 3pt} \NC the tabskip between columns 1 and 2 \NC \NR + \NC \type{\alignrecord} of width $-\infty$ \NC preamble info for column 2 \NC \NR + \NC \type{\glue 1pt plus 1fil} \NC the tabskip between columns 2 and 3 \NC \NR + \NC \type{\alignrecord} of width $-\infty$ \NC preamble info for column 3 \NC \NR + \NC \type{\glue 1pt plus 1fil} \NC the tabskip following column 3 \NC \NR + \stoptabulate + + These \quote {alignrecord} entries have the same size as an |unset_node|, since they + will later be converted into such nodes. These alignrecord nodes have no |depth| field; + this is split into |u_part| and |v_part|, and they point to token lists for the + templates of the alignment. For example, the |u_part| field in the first alignrecord + points to the token list |u1|, i.e., the template preceding the \type {#} for column~1. + Furthermore, They have a |span_ptr| instead of a |node_attr| field, and these |span_ptr| + fields are initially set to the value |end_span|, for reasons explained below. + \stopitem + + \startitem + \TEX\ now looks at what follows the |\cr| that ended the preamble. It is not |\noalign| + or |\omit|, so this input is put back to be read again, and the template |u1| is fed to + the scanner. Just before reading |u1|, \TeX\ goes into restricted horizontal mode. Just + after reading |u1|, \TEX\ will see |a1|, and then (when the |&| is sensed) \TEX\ will + see |v1|. Then \TEX\ scans an |end_template| token, indicating the end of a column. At + this point an |unset_node| is created, containing the contents of the current hlist + (i.e., |u1a1v1|). The natural width of this unset node replaces the |width| field of + the alignrecord for column~1; in general, the alignrecords will record the maximum + natural width that has occurred so far in a given column. + \stopitem + + \startitem + Since |\omit| follows the |&|, the templates for column~2 are now bypassed. Again \TEX\ + goes into restricted horizontal mode and makes an |unset_node| from the resulting hlist; + but this time the hlist contains simply |a2|. The natural width of the new unset box is + remembered in the |width| field of the alignrecord for column~2. + \stopitem + + \startitem + A third |unset_node| is created for column 3, using essentially the mechanism that + worked for column~1; this unset box contains |u3\vrule v3|. The vertical rule in this + case has running dimensions that will later extend to the height and depth of the whole + first row, since each |unset_node| in a row will eventually inherit the height and depth + of its enclosing box. + \stopitem + + \startitem + The first row has now ended; it is made into a single unset box comprising the following + seven items: + + \starttyping + \glue 2pt plus 3pt + \unsetbox for 1 column: u1a1v1 + \glue 2pt plus 3pt + \unsetbox for 1 column: a2 + \glue 1pt plus 1fil + \unsetbox for 1 column: u3\vrule v3 + \glue 1pt plus 1fil + \stoptyping + + The width of this unset row is unimportant, but it has the correct height and depth, so + the correct baselineskip glue will be computed as the row is inserted into a vertical + list. + \stopitem + + \startitem + Since |\noalign| follows the current |\cr|, \TEX\ appends additional material (in this + case |\vskip 3pt|) to the vertical list. While processing this material, \TeX\ will be + in internal vertical mode, and |no_align_group| will be on |save_stack|. + \stopitem + + \startitem + The next row produces an unset box that looks like this: + + \starttyping + \glue 2pt plus 3pt + \unsetbox for 2 columns: u1b1v1u2b2v2 + \glue 1pt plus 1fil + \unsetbox for 1 column: {(empty)} + \glue 1pt plus 1fil + \stoptyping + + The natural width of the unset box that spans columns 1~and~2 is stored in a \quote + {span node}, which we will explain later; the |span_ptr| field of the alignrecord for + column~1 now points to the new span node, and the |span_ptr| of the span node points to + |end_span|. + \stopitem + + \startitem + + The final row produces the unset box + + \starttyping + \glue 2pt plus 3pt + \unsetbox for 1 column: (empty) + \glue 2pt plus 3pt + \unsetbox for 2 columns: u2c2v2 + \glue 1pt plus 1fil + \stoptyping + + A new span node is attached to the align record for column 2. + \stopitem + + \startitem + The last step is to compute the true column widths and to change all the unset boxes to + hboxes, appending the whole works to the vertical list that encloses the |\halign|. The + rules for deciding on the final widths of each unset column box will be explained below. + \stopitem + + \stopitemize + + Note that as |\halign| is being processed, we fearlessly give up control to the rest of \TEX. At + critical junctures, an alignment routine is called upon to step in and do some little action, but + most of the time these routines just lurk in the background. It's something like post-hypnotic + suggestion. + + We have mentioned that alignrecords contain no |height| or |depth| fields. Their |glue_sign| and + |glue_order| are pre-empted as well, since it is necessary to store information about what to do + when a template ends. This information is called the |extra_info| field. + + Alignments can occur within alignments, so a small stack is used to access the alignrecord + information. At each level we have a |preamble| pointer, indicating the beginning of the + preamble list; a |cur_align| pointer, indicating the current position in the preamble list; a + |cur_span| pointer, indicating the value of |cur_align| at the beginning of a sequence of + spanned columns; a |cur_loop| pointer, indicating the tabskip glue before an alignrecord that + should be copied next if the current list is extended; and the |align_state| variable, which + indicates the nesting of braces so that |\cr| and |\span| and tab marks are properly + intercepted. There also are pointers |cur_head| and |cur_tail| to the head and tail of a list + of adjustments being moved out from horizontal mode to vertical~mode, and alike |cur_pre_head| + and |cur_pre_tail| for pre-adjust lists. + + The current values of these nine quantities appear in global variables; when they have to be + pushed down, they are stored in 6-word nodes, and |align_ptr| points to the topmost such node. + +*/ + +/*tex + + So far, hardly anything has been added to the alignment code so the above, original \TEX\ + the program documentation still applies. Of course we have callbacks. Attributes are a bit + complicating here. I experimented with some row and cell specific ones but grouping will always + make it messy. One never knows what a preamble injects. So leaving it as-is is better than a + subtoptimal solution with side effects. To mention one aspect: we have unset nodes that use the + attribute fields for other purposes and get adapted later on anyway. I'll look into it again + at some point. + + Contrary to other mechanisms, there are not that many extensions. One is that we can nest + |\noalign| (so we don't need kludges at the macro level). The look ahead trickery has not been + changed but we might get some variants (we have protected macros so it's not as sensitive as + it was in the past. + + The |\tabsize| feature is experimental and possibly a prelude to more. I played with that + when a test file (korean font table) was allocating so many nodes that I wondered if we could + limit that (and redundant boxes and glue are the only things we can do here). It actually + also saves a bit of runtime. This feature has not been tested yet with |\span| and |\omit|. + +*/ + +/* + Todo: lefttabskip righttabskip middletabskip +*/ + +typedef struct alignment_state_info { + halfword cur_align; /*tex The current position in the preamble list. */ + halfword cur_span; /*tex The start of the currently spanned columns in the preamble list. */ + halfword cur_loop; /*tex A place to copy when extending a periodic preamble. */ + halfword align_ptr; /*tex The most recently pushed-down alignment stack node. */ + halfword cur_post_adjust_head; /*tex Adjustment list head pointer. */ + halfword cur_post_adjust_tail; /*tex Adjustment list tail pointer. */ + halfword cur_pre_adjust_head; /*tex Pre-adjustment list head pointer. */ + halfword cur_pre_adjust_tail; /*tex Pre-adjustment list tail pointer. */ + halfword cur_post_migrate_head; + halfword cur_post_migrate_tail; + halfword cur_pre_migrate_head; + halfword cur_pre_migrate_tail; + halfword hold_token_head; /*tex head of a temporary list of another kind */ + halfword omit_template; /*tex a constant token list */ + halfword no_align_level; + halfword no_tab_skips; + halfword attr_list; + halfword cell_source; + halfword wrap_source; + halfword callback; + // halfword reverse; // todo + // halfword discard_skips; // todo +} alignment_state_info ; + +static alignment_state_info lmt_alignment_state = { + .cur_align = null, + .cur_span = null, + .cur_loop = null, + .align_ptr = null, + .cur_post_adjust_head = null, + .cur_post_adjust_tail = null, + .cur_pre_adjust_head = null, + .cur_pre_adjust_tail = null, + .cur_post_migrate_head = null, + .cur_post_migrate_tail = null, + .cur_pre_migrate_head = null, + .cur_pre_migrate_tail = null, + .hold_token_head = null, /*tex head of a temporary list of another kind */ + .omit_template = null, /*tex a constant token list */ + .no_align_level = 0, + .no_tab_skips = 0, + .attr_list = null, + .cell_source = 0, + .wrap_source = 0, + .callback = 0, + // .reverse = 0, + // .discard_skips = 0, +}; + +/*tex We could as well save these in the alignment stack. */ + +typedef enum saved_align_items { + saved_align_specification, + saved_align_reverse, + saved_align_discard, + saved_align_noskips, /*tex Saving is not needed but it doesn't hurt either */ + saved_align_callback, + saved_align_n_of_items, +} saved_align_items; + +/*tex The current preamble list: */ + +# define preamble node_next(align_head) + +/*tex We use them before we define them: */ + +static void tex_aux_initialize_row (void); +static void tex_aux_initialize_column (void); +static void tex_aux_finish_row (void); +static int tex_aux_finish_column (void); +static void tex_aux_finish_align (void); + +/*tex + We get |alignment_record| into |unset_node| and |unset_node| into |[hv]list_node|. And because + we can access the fields later on w emake sure that we wipe them. The box orientation field kind + of protects reading them but still it's nicer this way. In general in \LUATEX\ and \LUAMETATEX\ + we need to be more careful because we expose fields. +*/ + +inline static void tex_aux_change_list_type(halfword n, quarterword type) +{ + node_type(n) = type; + box_w_offset(n) = 0; /* box_glue_stretch align_record_span_ptr */ + box_h_offset(n) = 0; /* box_glue_shrink align_record_extra_info */ + box_d_offset(n) = 0; /* box_span_count */ + box_x_offset(n) = 0; /* align_record_u_part */ + box_y_offset(n) = 0; /* align_record_v_part */ + // box_geometry(n) = 0; /* box_size */ + box_orientation(n) = 0; /* box_size */ +} + +/*tex + + The |align_state| and |preamble| variables are initialized elsewhere. Alignment stack + maintenance is handled by a pair of trivial routines called |push_alignment| and |pop_alignment|. + + It makes not much sense to add support for an |attr| keyword to |\halign| and |\valign| because + then we need to decide if we tag rows or cells or both or come up with |cellattr| and |rowattr| + and such. But then it even makes sense to have explicit commands (in addition to the seperator) + to tags individual cells. It's too much hassle for now and the advantages are not that large. + +*/ + +static void tex_aux_push_alignment(void) +{ + /*tex The new alignment stack node: */ + halfword p = tex_new_node(align_stack_node, 0); + align_stack_align_ptr(p) = lmt_alignment_state.align_ptr; + align_stack_cur_align(p) = lmt_alignment_state.cur_align; + align_stack_preamble(p) = preamble; + align_stack_cur_span(p) = lmt_alignment_state.cur_span; + align_stack_cur_loop(p) = lmt_alignment_state.cur_loop; + align_stack_align_state(p) = lmt_input_state.align_state; + align_stack_wrap_source(p) = lmt_alignment_state.wrap_source; + align_stack_no_align_level(p) = lmt_alignment_state.no_align_level; + align_stack_cur_post_adjust_head(p) = lmt_alignment_state.cur_post_adjust_head; + align_stack_cur_post_adjust_tail(p) = lmt_alignment_state.cur_post_adjust_tail; + align_stack_cur_pre_adjust_head(p) = lmt_alignment_state.cur_pre_adjust_head; + align_stack_cur_pre_adjust_tail(p) = lmt_alignment_state.cur_pre_adjust_tail; + align_stack_cur_post_migrate_head(p) = lmt_alignment_state.cur_post_migrate_head; + align_stack_cur_post_migrate_tail(p) = lmt_alignment_state.cur_post_migrate_tail; + align_stack_cur_pre_migrate_head(p) = lmt_alignment_state.cur_pre_migrate_head; + align_stack_cur_pre_migrate_tail(p) = lmt_alignment_state.cur_pre_migrate_tail; + align_stack_no_tab_skips(p) = lmt_alignment_state.no_tab_skips; + align_stack_attr_list(p) = lmt_alignment_state.attr_list; + lmt_alignment_state.align_ptr = p; + lmt_alignment_state.cur_post_adjust_head = tex_new_temp_node(); + lmt_alignment_state.cur_pre_adjust_head = tex_new_temp_node(); + lmt_alignment_state.cur_post_migrate_head = tex_new_temp_node(); + lmt_alignment_state.cur_pre_migrate_head = tex_new_temp_node(); + /* */ + lmt_alignment_state.cell_source = 0; + lmt_alignment_state.wrap_source = 0; +} + +static void tex_aux_pop_alignment(void) +{ + /*tex The top alignment stack node: */ + halfword p = lmt_alignment_state.align_ptr; + tex_flush_node(lmt_alignment_state.cur_post_adjust_head); + tex_flush_node(lmt_alignment_state.cur_pre_adjust_head); + tex_flush_node(lmt_alignment_state.cur_post_migrate_head); + tex_flush_node(lmt_alignment_state.cur_pre_migrate_head); + lmt_alignment_state.align_ptr = align_stack_align_ptr(p); + lmt_alignment_state.cur_align = align_stack_cur_align(p); + preamble = align_stack_preamble(p); + lmt_alignment_state.cur_span = align_stack_cur_span(p); + lmt_alignment_state.cur_loop = align_stack_cur_loop(p); + lmt_input_state.align_state = align_stack_align_state(p); + lmt_alignment_state.wrap_source = align_stack_wrap_source(p); + lmt_alignment_state.no_align_level = align_stack_no_align_level(p); + lmt_alignment_state.cur_post_adjust_head = align_stack_cur_post_adjust_head(p); + lmt_alignment_state.cur_post_adjust_tail = align_stack_cur_post_adjust_tail(p); + lmt_alignment_state.cur_pre_adjust_head = align_stack_cur_pre_adjust_head(p); + lmt_alignment_state.cur_pre_adjust_tail = align_stack_cur_pre_adjust_tail(p); + lmt_alignment_state.cur_post_migrate_head = align_stack_cur_post_migrate_head(p); + lmt_alignment_state.cur_post_migrate_tail = align_stack_cur_post_migrate_tail(p); + lmt_alignment_state.cur_pre_migrate_head = align_stack_cur_pre_migrate_head(p); + lmt_alignment_state.cur_pre_migrate_tail = align_stack_cur_pre_migrate_tail(p); + lmt_alignment_state.no_tab_skips = align_stack_no_tab_skips(p); + lmt_alignment_state.attr_list = align_stack_attr_list(p); + tex_flush_node(p); +} + +/*tex + + \TEX\ has eight procedures that govern alignments: |initialize_align| and |finish_align| are + used at the very beginning and the very end; |initialize_row| and |finish_row| are used at + the beginning and end of individual rows; |initialize_span| is used at the beginning of a + sequence of spanned columns (possibly involving only one column); |initialize_column| and + |finish_column| are used at the beginning and end of individual columns; and |align_peek| is + used after |\cr| to see whether the next item is |\noalign|. + + We shall consider these routines in the order they are first used during the course of a + complete |\halign|, namely |initialize_align|, |align_peek|, |initialize_row|, + |initialize_span|, |initialize_column|, |finish_column|, |finish_row|, |finish_align|. + + The preamble is copied directly, except that |\tabskip| causes a change to the tabskip glue, + thereby possibly expanding macros that immediately follow it. An appearance of |\span| also + causes such an expansion. + + Note that if the preamble contains |\global\tabskip|, the |\global| token survives in the + preamble and the |\tabskip| defines new tabskip glue (locally). + + We enter |\span| into |eqtb| with |tab_mark| as its command code, and with |span_code| as the + command modifier. This makes \TEX\ interpret it essentially the same as an alignment delimiter + like |&|, yet it is recognizably different when we need to distinguish it from a normal + delimiter. It also turns out to be useful to give a special |cr_code| to |\cr|, and an even + larger |cr_cr_code| to |\crcr|. + + The end of a template is represented by two frozen control sequences called |\endtemplate|. The + first has the command code |end_template|, which is |> outer_call|, so it will not easily + disappear in the presence of errors. The |get_x_token| routine converts the first into the + second, which has |endv| as its command code. + + The |cr_code| is distinct from |span_code| and from any character and |\crcr| differs from + |\cr|. +*/ + +/* + In \LUAMETATEX\ the code has been adapted a bit. Because we have some access to alignment + related properties (commands, lists, etc.) The command codes have been reshuffled and + combined. Instead of dedicated cmd codes, we have a shared cmd with subtypes. The logic + hasn't changed, just the triggering of actions. In theory there can be a performance penalty + (due to extra checking) but in practice that will not be noticed becasue this seldom happens. + The advange is that we have a uniform token interface. It also makes it possible to extend + the code. + +*/ + +static void tex_aux_get_preamble_token(void) +{ + RESTART: + tex_get_token(); + while (cur_cmd == alignment_cmd && cur_chr == span_code) { + /*tex This token will be expanded once. */ + tex_get_token(); + if (cur_cmd > max_command_cmd) { + tex_expand_current_token(); + tex_get_token(); + } + } + switch (cur_cmd) { + case end_template_cmd: + tex_alignment_interwoven_error(5); + break; + case internal_glue_cmd: + if (cur_chr == internal_glue_location(tab_skip_code)) { + halfword v = tex_scan_glue(glue_val_level, 1); + if (global_defs_par > 0) { + update_tex_tab_skip_global(v); + } else { + update_tex_tab_skip_local(v); + } + goto RESTART; + } else { + break; + } + case internal_dimen_cmd: + if (cur_chr == internal_dimen_location(tab_size_code)) { + scaled v = tex_scan_dimen(0, 0, 0, 1, NULL); + tex_word_define(global_defs_par > 0 ? global_flag_bit : 0, internal_dimen_location(tab_size_code), v); + goto RESTART; + } else { + break; + } + case call_cmd: + case protected_call_cmd: + case semi_protected_call_cmd: + case tolerant_call_cmd: + case tolerant_protected_call_cmd: + case tolerant_semi_protected_call_cmd: + if (has_eq_flag_bits(cur_cs, noaligned_flag_bit)) { + tex_expand_current_token(); + goto RESTART; + } else { + break; + } + } +} + +/*tex + + When |\halign| or |\valign| has been scanned in an appropriate mode, \TEX\ calls + |initialize_align|, whose task is to get everything off to a good start. This mostly involves + scanning the preamble and putting its information into the preamble list. + +*/ + +static void tex_aux_scan_align_spec(quarterword c) +{ + quarterword mode = packing_additional; + quarterword reverse = 0; + quarterword discard = 0; + quarterword noskips = 0; + quarterword callback = 0; + scaled amount = 0; + halfword attrlist = null; + int brace = 0; + while (1) { + cur_val = 0; /* why */ + switch (tex_scan_character("acdnrtsACDNRTS", 1, 1, 1)) { + case 0: + goto DONE; + case 'a': case 'A': + if (tex_scan_mandate_keyword("attr", 1)) { + halfword i = tex_scan_attribute_register_number(); + halfword v = tex_scan_int(1, NULL); + if (eq_value(register_attribute_location(i)) != v) { + if (attrlist) { + attrlist = tex_patch_attribute_list(attrlist, i, v); + } else { + attrlist = tex_copy_attribute_list_set(tex_current_attribute_list(), i, v); + } + } + } + break; + case 'c': case 'C': + if (tex_scan_mandate_keyword("callback", 1)) { + callback = 1; + } + break; + case 'd': case 'D': + if (tex_scan_mandate_keyword("discard", 1)) { + discard = 1; + } + break; + case 'n': case 'N': + if (tex_scan_mandate_keyword("noskips", 1)) { + noskips = 1; + } + break; + case 'r': case 'R': + if (tex_scan_mandate_keyword("reverse", 1)) { + reverse = 1; + } + break; + case 't': case 'T': + if (tex_scan_mandate_keyword("to", 1)) { + mode = packing_exactly; + amount = tex_scan_dimen(0, 0, 0, 0, NULL); + } + break; + case 's': case 'S': + if (tex_scan_mandate_keyword("spread", 1)) { + mode = packing_additional; + amount = tex_scan_dimen(0, 0, 0, 0, NULL); + } + break; + case '{': + brace = 1; + goto DONE; + default: + goto DONE; + } + } + DONE: + if (! attrlist) { + /* this alse sets the reference when not yet set */ + attrlist = tex_current_attribute_list(); + } + /*tex Now we're referenced. We need to preserve this over the group. */ + add_attribute_reference(attrlist); + tex_set_saved_record(saved_align_specification, saved_box_spec, mode, amount); + /* We save them but could put them in the state as we do for some anyway. */ + tex_set_saved_record(saved_align_reverse, saved_box_reverse, reverse, 0); + tex_set_saved_record(saved_align_discard, saved_box_discard, noskips ? 0 : discard, 0); + tex_set_saved_record(saved_align_noskips, saved_box_noskips, noskips, 0); + tex_set_saved_record(saved_align_callback, saved_box_callback, callback, 0); + lmt_save_state.save_stack_data.ptr += saved_align_n_of_items; + tex_new_save_level(c); + if (! brace) { + tex_scan_left_brace(); + } + lmt_alignment_state.no_tab_skips = noskips; + lmt_alignment_state.attr_list = attrlist; + lmt_alignment_state.callback = callback; +} + +/*tex + + The tricky part about alignments is getting the templates into the scanner at the right time, + and recovering control when a row or column is finished. + + We usually begin a row after each |\cr| has been sensed, unless that |\cr| is followed by + |\noalign| or by the right brace that terminates the alignment. The |align_peek| routine is + used to look ahead and do the right thing; it either gets a new row started, or gets a + |\noalign} started, or finishes off the alignment. + +*/ + +static void tex_aux_align_peek(void); + +static void tex_aux_trace_no_align(const char *s) +{ + if (tracing_alignments_par > 0) { + tex_begin_diagnostic(); + tex_print_format("[alignment: %s noalign, level %i]", s, lmt_alignment_state.no_align_level); + tex_end_diagnostic(); + } +} + +static void tex_aux_run_no_align(void) +{ + tex_scan_left_brace(); + tex_new_save_level(no_align_group); + ++lmt_alignment_state.no_align_level; + tex_aux_trace_no_align("entering"); + if (cur_list.mode == -vmode) { + tex_normal_paragraph(no_align_par_context); + } +} +static int tex_aux_nested_no_align(void) +{ + int state = lmt_alignment_state.no_align_level > 0; + if (state) { + tex_scan_left_brace(); + tex_new_save_level(no_align_group); + ++lmt_alignment_state.no_align_level; + tex_aux_trace_no_align("entering"); + if (cur_list.mode == -vmode) { + tex_normal_paragraph(no_align_par_context); + } + } + return state; +} + +void tex_finish_no_alignment_group(void) +{ + if (! tex_wrapped_up_paragraph(no_align_par_context)) { /* needs testing */ + tex_end_paragraph(no_align_group, no_align_par_context); + tex_aux_trace_no_align("leaving"); + --lmt_alignment_state.no_align_level; + tex_unsave(); + if (lmt_alignment_state.no_align_level == 0) { + tex_aux_align_peek(); + } + } +} + +static void tex_aux_align_peek(void) +{ + RESTART: + lmt_input_state.align_state = 1000000; + AGAIN: + tex_get_x_or_protected(); + switch (cur_cmd) { + case spacer_cmd: + goto AGAIN; + case right_brace_cmd: + tex_aux_finish_align(); + break; + case call_cmd: + case protected_call_cmd: + case semi_protected_call_cmd: + case tolerant_call_cmd: + case tolerant_protected_call_cmd: + case tolerant_semi_protected_call_cmd: + if (has_eq_flag_bits(cur_cs, noaligned_flag_bit)) { + tex_expand_current_token(); + goto RESTART; + } else { + goto NEXTROW; + } + case alignment_cmd: + switch (cur_chr) { + case cr_cr_code: + /*tex Ignore |\crcr|. */ + goto RESTART; + case no_align_code: + tex_aux_run_no_align(); + return; + } + // fall through + default: + NEXTROW: + /*tex Start a new row. */ + tex_aux_initialize_row(); + /*tex Start a new column and replace what we peeked at. */ + tex_aux_initialize_column(); + break; + } +} + +/*tex +* + Magick numbers are used to indicate the level of alignment. However, keep in mind that in + \LUANETATEX\ the fundamental parts of the rendering are separated. Contrary to traditional + \TEX\ we don't have the interwoven hyphenation, ligature building, kerning, etc.\ code. + + In the end we have a list starting and ending with tabskips and align records seperated by + such skips. + +*/ + +void tex_run_alignment_initialize(void) +{ + halfword saved_cs = cur_cs; + tex_aux_push_alignment(); + lmt_input_state.align_state = -1000000; + /*tex + When |\halign| is used as a displayed formula, there should be no other pieces of mlists + present. + */ + if (cur_list.mode == mmode && ((cur_list.tail != cur_list.head) || cur_list.incomplete_noad)) { + tex_handle_error( + normal_error_type, + "Improper \\halign inside math mode", + "Displays can use special alignments (like \\eqalignno) only if nothing but the\n" + "alignment itself is in math mode. So I've deleted the formulas that preceded this\n" + "alignment." + ); + tex_flush_math(); + } + /*tex We enter a new semantic level. */ + tex_push_nest(); + /*tex + In vertical modes, |prev_depth| already has the correct value. But if we are in |mmode| + (displayed formula mode), we reach out to the enclosing vertical mode for the |prev_depth| + value that produces the correct baseline calculations. + */ + if (cur_list.mode == mmode) { + cur_list.mode = -vmode; + cur_list.prev_depth = lmt_nest_state.nest[lmt_nest_state.nest_data.ptr - 2].prev_depth; + } else if (cur_list.mode > 0) { + cur_list.mode = -cur_list.mode; + } + /*tex This one also saves some in the state. */ + tex_aux_scan_align_spec(align_group); + /*tex + Scan the preamble. Even when we ignore zero tabskips, we do store them in the list because + the machinery later on steps over them and checking for present glue makes the code + horrible. The overhead is small because it's only the preamble where we waste glues then. + */ + preamble = null; + lmt_alignment_state.cur_align = align_head; + lmt_alignment_state.cur_loop = null; + lmt_input_state.scanner_status = scanner_is_aligning; + lmt_input_state.warning_index = saved_cs; + lmt_input_state.align_state = -1000000; + /*tex At this point, |cur_cmd = left_brace|. */ + while (1) { + /*tex Append the current tabskip glue to the preamble list. */ + halfword glue = tex_new_param_glue_node(tab_skip_code, tab_skip_glue); + if (lmt_alignment_state.no_tab_skips && tex_glue_is_zero(glue)) { + node_subtype(glue) = ignored_glue; + } + tex_couple_nodes(lmt_alignment_state.cur_align, glue); + lmt_alignment_state.cur_align = glue; + if (cur_cmd == alignment_cmd && (cur_chr == cr_code || cur_chr == cr_cr_code)) { /* Also cr_cr here? */ + /*tex A |\cr| ends the preamble. */ + break; + } else { + /*tex + Scan preamble text until |cur_cmd| is |tab_mark| or |car_ret| and then scan the + template |u_j|, putting the resulting token list in |hold_token_head|. Spaces are + eliminated from the beginning of a template. + */ + halfword record = null; + halfword current = lmt_alignment_state.hold_token_head; + token_link(current) = null; + while (1) { + tex_aux_get_preamble_token(); + if (cur_cmd == parameter_cmd || (cur_cmd == alignment_cmd && cur_chr == align_content_code)) { + break; + } else if ((cur_cmd == alignment_cmd || cur_cmd == alignment_tab_cmd) && (lmt_input_state.align_state == -1000000)) { + if ((current == lmt_alignment_state.hold_token_head) && (! lmt_alignment_state.cur_loop) && (cur_cmd == alignment_tab_cmd)) { + lmt_alignment_state.cur_loop = lmt_alignment_state.cur_align; + } else { + tex_back_input(cur_tok); + tex_handle_error( + normal_error_type, + "Missing # inserted in alignment preamble", + "There should be exactly one # between &'s, when an \\halign or \\valign is being\n" + "set up. In this case you had none, so I've put one in; maybe that will work." + ); + break; + } + } else if (cur_cmd != spacer_cmd || current != lmt_alignment_state.hold_token_head) { + current = tex_store_new_token(current, cur_tok); + } + } + /*tex A new align record: */ + record = tex_new_node(align_record_node, 0); + tex_couple_nodes(lmt_alignment_state.cur_align, record); + lmt_alignment_state.cur_align = record; + align_record_span_ptr(record) = end_span; + box_width(record) = null_flag; + align_record_pre_part(record) = token_link(lmt_alignment_state.hold_token_head); + /*tex Scan the template |v_j|, putting the resulting token list in |hold_token_head|. */ + current = lmt_alignment_state.hold_token_head; + token_link(current) = null; + while (1) { + tex_aux_get_preamble_token(); + if ((cur_cmd == alignment_cmd || cur_cmd == alignment_tab_cmd) && (lmt_input_state.align_state == -1000000)) { + break; + } else if (cur_cmd == parameter_cmd || (cur_cmd == alignment_cmd && cur_chr == align_content_code)) { + tex_handle_error( + normal_error_type, + "Only one # is allowed per tab", + "There should be exactly one # between &'s, when an \\halign or \\valign is being\n" + "set up. In this case you had more than one, so I'm ignoring all but the first." + ); + } else { + current = tex_store_new_token(current, cur_tok); + } + } + if (tab_size_par > 0) { + box_size(record) = tab_size_par; + set_box_package_state(record, package_dimension_size_set); + } else { + box_width(record) = null_flag; + } + /*tex Put |\endtemplate| at the end: */ + current = tex_store_new_token(current, deep_frozen_end_template_1_token); + align_record_post_part(lmt_alignment_state.cur_align) = token_link(lmt_alignment_state.hold_token_head); + } + } + if (tracing_alignments_par > 1) { + tex_print_levels(); + tex_print_str("<alignment preamble>"); + tex_show_node_list(preamble, max_integer, max_integer); + } + if (lmt_alignment_state.callback) { + lmt_alignment_callback(cur_list.head, preamble_pass_alignment_context, lmt_alignment_state.attr_list, preamble); + } + lmt_input_state.scanner_status = scanner_is_normal; + tex_new_save_level(align_group); + if (every_cr_par) { + tex_begin_token_list(every_cr_par, every_cr_text); + } + /*tex Look for |\noalign| or |\omit|. */ + tex_aux_align_peek(); +} + +void tex_finish_alignment_group(void) +{ + tex_back_input(cur_tok); + cur_tok = deep_frozen_cr_token; + tex_handle_error( + insert_error_type, + "Missing \\cr inserted", + "I'm guessing that you meant to end an alignment here." + ); +} + +/*tex + + The parameter to |initialize_span| is a pointer to the alignrecord where the next column or group + of columns will begin. A new semantic level is entered, so that the columns will generate a list + for subsequent packaging. + +*/ + +static void tex_aux_initialize_span(halfword p) +{ + tex_push_nest(); + if (cur_list.mode == -hmode) { + cur_list.space_factor = 1000; + } else { + cur_list.prev_depth = ignore_depth; + tex_normal_paragraph(span_par_context); + } + lmt_alignment_state.cur_span = p; +} + +/*tex + + To start a row (i.e., a \quote {row} that rhymes with \quote {dough} but not with \quote + {bough}), we enter a new semantic level, copy the first tabskip glue, and change from internal + vertical mode to restricted horizontal mode or vice versa. The |space_factor| and |prev_depth| + are not used on this semantic level, but we clear them to zero just to be tidy. + +*/ + +static void tex_aux_initialize_row(void) +{ + tex_push_nest(); + cur_list.mode = (- hmode - vmode) - cur_list.mode; /* weird code */ + if (cur_list.mode == -hmode) { + cur_list.space_factor = 0; + } else { + cur_list.prev_depth = 0; + } + lmt_alignment_state.cur_align = preamble; + if (node_subtype(preamble) != ignored_glue) { + halfword glue = tex_new_glue_node(preamble, tab_skip_glue); + tex_tail_append(glue); + tex_attach_attribute_list_attribute(glue, lmt_alignment_state.attr_list); + } + lmt_alignment_state.cur_align = node_next(preamble); + lmt_alignment_state.cur_post_adjust_tail = lmt_alignment_state.cur_post_adjust_head; + lmt_alignment_state.cur_pre_adjust_tail = lmt_alignment_state.cur_pre_adjust_head; + lmt_alignment_state.cur_post_migrate_tail = lmt_alignment_state.cur_post_migrate_head; + lmt_alignment_state.cur_pre_migrate_tail = lmt_alignment_state.cur_pre_migrate_head; + tex_aux_initialize_span(lmt_alignment_state.cur_align); +} + +/*tex + + When a column begins, we assume that |cur_cmd| is either |omit| or else the current token should + be put back into the input until the \<u_j> template has been scanned. Note that |cur_cmd| might + be |tab_mark| or |car_ret|. We also assume that |align_state| is approximately 1000000 at this + time. We remain in the same mode, and start the template if it is called for. + +*/ + +static void tex_aux_initialize_column(void) +{ + align_record_cmd(lmt_alignment_state.cur_align) = cur_cmd; + align_record_chr(lmt_alignment_state.cur_align) = cur_chr; + if (cur_cmd == alignment_cmd && cur_chr == omit_code) { + lmt_input_state.align_state = 0; + } else { + tex_back_input(cur_tok); + if (every_tab_par) { + tex_begin_token_list(every_tab_par, every_tab_text); + } + tex_begin_token_list(align_record_pre_part(lmt_alignment_state.cur_align), template_pre_text); + } + /*tex Now |align_state = 1000000|, one of these magic numbers. */ +} + +/*tex + + The scanner sets |align_state| to zero when the |u_j| template ends. When a subsequent |\cr| + or |\span| or tab mark occurs with |align_state=0|, the scanner activates the following code, + which fires up the |v_j| template. We need to remember the |cur_chr|, which is either + |cr_cr_code|, |cr_code|, |span_code|, or a character code, depending on how the column text has + ended. + + This part of the program had better not be activated when the preamble to another alignment is + being scanned, or when no alignment preamble is active. + +*/ + +void tex_insert_alignment_template(void) +{ + if (lmt_input_state.scanner_status == scanner_is_aligning || ! lmt_alignment_state.cur_align) { + tex_alignment_interwoven_error(6); + } else { + /*tex in case of an |\omit| the gets discarded and is nowhere else referenced. */ + halfword cmd = align_record_cmd(lmt_alignment_state.cur_align); + halfword chr = align_record_chr(lmt_alignment_state.cur_align); + halfword tok = (cmd == alignment_cmd && chr == omit_code) ? lmt_alignment_state.omit_template : align_record_post_part(lmt_alignment_state.cur_align); + align_record_cmd(lmt_alignment_state.cur_align) = cur_cmd; + align_record_chr(lmt_alignment_state.cur_align) = cur_chr; + tex_begin_token_list(tok, template_post_text); + lmt_input_state.align_state = 1000000; + lmt_alignment_state.cell_source = alignment_cell_source_par; + if (alignment_wrap_source_par) { + lmt_alignment_state.wrap_source = alignment_wrap_source_par; + } + } +} + +/*tex Determine the stretch or shrink order */ + +inline static halfword tex_aux_determine_order(scaled *total) +{ + if (total[filll_glue_order]) return filll_glue_order; + else if (total[fill_glue_order]) return fill_glue_order; + else if (total[fil_glue_order]) return fil_glue_order; + else if (total[fi_glue_order]) return fi_glue_order; + else return normal_glue_order; +} + +/*tex + + A span node is a 3-word record containing |width|, |span_span|, and |span_ptr| fields. The + |span_span| field indicates the number of spanned columns; the |span_ptr| field points to a + span node for the same starting column, having a greater extent of spanning, or to |end_span|, + which has the largest possible |span_span| field; the |width| field holds the largest natural + width corresponding to a particular set of spanned columns. + + A list of the maximum widths so far, for spanned columns starting at a given column, begins + with the |span_ptr| field of the alignrecord for that column. The code has to make sure that + there is room for |span_ptr| in both the align record and the span nodes, which is why + |span_ptr| replaces |node_attr|. + +*/ + +static halfword tex_aux_new_span_node(halfword n, int s, scaled w) +{ + halfword p = tex_new_node(span_node, 0); + span_ptr(p) = n; /*tex This one overlaps with |alignment_record_ptr|. */ + span_span(p) = s; + span_width(p) = w; + return p; +} + +/*tex + + When the |end_template| command at the end of a |v_j| template comes through the scanner, + things really start to happen; and it is the |finialize_column| routine that makes them happen. + This routine returns |true| if a row as well as a column has been finished. + +*/ + +void tex_alignment_interwoven_error(int n) +{ + tex_formatted_error("alignment", "interwoven preambles are not allowed, case %d", n); +} + +halfword tex_alignment_hold_token_head(void) +{ + return lmt_alignment_state.hold_token_head; +} + +static int tex_aux_finish_column(void) +{ + if (! lmt_alignment_state.cur_align) { + tex_confusion("end template, case 1"); + } else { + halfword q = node_next(lmt_alignment_state.cur_align); + if (! q) { + tex_confusion("end template, case 2"); + } else if (lmt_input_state.align_state < 500000) { + tex_alignment_interwoven_error(1); + } else { + /*tex A few state variables. */ + halfword cmd = align_record_cmd(lmt_alignment_state.cur_align); + halfword chr = align_record_chr(lmt_alignment_state.cur_align); + /*tex + We check the alignrecord after the current one. If the preamble list has been + traversed, check that the row has ended. + */ + halfword record = node_next(q); + if (alignment_wrap_source_par) { + lmt_alignment_state.wrap_source = alignment_wrap_source_par; + } + if (! record && ! ((cmd == alignment_cmd) && (chr == cr_code || chr == cr_cr_code))) { + if (lmt_alignment_state.cur_loop) { + /*tex Lengthen the preamble periodically. A new align record: */ + record = tex_new_node(align_record_node, 0); + tex_couple_nodes(q, record); + align_record_span_ptr(record) = end_span; + box_width(record) = null_flag; + lmt_alignment_state.cur_loop = node_next(lmt_alignment_state.cur_loop); + /*tex Copy the templates from node |cur_loop| into node |p|. */ + { + halfword q = lmt_alignment_state.hold_token_head; + halfword r = align_record_pre_part(lmt_alignment_state.cur_loop); + while (r) { + q = tex_store_new_token(q, token_info(r)); + r = token_link(r); + } + token_link(q) = null; + align_record_pre_part(record) = token_link(lmt_alignment_state.hold_token_head); + } + { + halfword q = lmt_alignment_state.hold_token_head; + halfword r = align_record_post_part(lmt_alignment_state.cur_loop); + while (r) { + q = tex_store_new_token(q, token_info(r)); + r = token_link(r); + } + token_link(q) = null; + align_record_post_part(record) = token_link(lmt_alignment_state.hold_token_head); + } + lmt_alignment_state.cur_loop = node_next(lmt_alignment_state.cur_loop); + { + halfword glue = tex_new_glue_node(lmt_alignment_state.cur_loop, tab_skip_glue); + if (lmt_alignment_state.no_tab_skips && tex_glue_is_zero(glue)) { + node_subtype(glue) = ignored_glue; + } + tex_couple_nodes(record, glue); + } + } else { + chr = cr_code; + align_record_chr(lmt_alignment_state.cur_align) = chr; + tex_handle_error( + normal_error_type, + "Extra alignment tab has been changed to \\cr", + "You have given more \\span or & marks than there were in the preamble to the\n" + "\\halign or \\valign now in progress. So I'll assume that you meant to type \\cr\n" + "instead." + ); + } + } + if (! (cmd == alignment_cmd && chr == span_code)) { + /*tex a new unset box */ + halfword cell = null; + /*tex natural width */ + scaled width = 0; + scaled size = 0; + int state = 0; + int packing = packing_additional; + /*tex The span counter. */ + halfword spans = 0; + tex_unsave(); + tex_new_save_level(align_group); + /*tex Package an unset box for the current column and record its width. */ + state = has_box_package_state(lmt_alignment_state.cur_align, package_dimension_size_set); + if (state) { + size = box_size(lmt_alignment_state.cur_align); + packing = packing_exactly; + } + if (cur_list.mode == -hmode) { + lmt_packaging_state.post_adjust_tail = lmt_alignment_state.cur_post_adjust_tail; + lmt_packaging_state.pre_adjust_tail = lmt_alignment_state.cur_pre_adjust_tail; + lmt_packaging_state.post_migrate_tail = lmt_alignment_state.cur_post_migrate_tail; + lmt_packaging_state.pre_migrate_tail = lmt_alignment_state.cur_pre_migrate_tail; + cell = tex_filtered_hpack(cur_list.head, cur_list.tail, size, packing, align_set_group, direction_unknown, 0, null, 0, 0); + width = box_width(cell); + lmt_alignment_state.cur_post_adjust_tail = lmt_packaging_state.post_adjust_tail; + lmt_alignment_state.cur_pre_adjust_tail = lmt_packaging_state.pre_adjust_tail; + lmt_alignment_state.cur_post_migrate_tail = lmt_packaging_state.post_migrate_tail; + lmt_alignment_state.cur_pre_migrate_tail = lmt_packaging_state.pre_migrate_tail; + lmt_packaging_state.post_adjust_tail = null; + lmt_packaging_state.pre_adjust_tail = null; + lmt_packaging_state.post_migrate_tail = null; + lmt_packaging_state.pre_migrate_tail = null; + } else { + cell = tex_filtered_vpack(node_next(cur_list.head), size, packing, 0, align_set_group, direction_unknown, 0, null, 0, 0); + width = box_height(cell); + } + if (lmt_alignment_state.cell_source) { + box_source_anchor(cell) = lmt_alignment_state.cell_source; + tex_set_box_geometry(cell, anchor_geometry); + } + tex_attach_attribute_list_attribute(cell, lmt_alignment_state.attr_list); + if (lmt_alignment_state.cur_span != lmt_alignment_state.cur_align) { + /*tex Update width entry for spanned columns. */ + halfword ptr = lmt_alignment_state.cur_span; + do { + ++spans; + ptr = node_next(node_next(ptr)); + } while (ptr != lmt_alignment_state.cur_align); + if (spans > max_quarterword) { + /*tex This can happen, but won't. */ + tex_confusion("too many spans"); + } + ptr = lmt_alignment_state.cur_span; + while (span_span(align_record_span_ptr(ptr)) < spans) { + ptr = align_record_span_ptr(ptr); + } + if (span_span(align_record_span_ptr(ptr)) > spans) { + halfword span = tex_aux_new_span_node(align_record_span_ptr(ptr), spans, width); + align_record_span_ptr(ptr) = span; + } else if (span_width(align_record_span_ptr(ptr)) < width) { + span_width(align_record_span_ptr(ptr)) = width; + } + } else if (width > box_width(lmt_alignment_state.cur_align)) { + box_width(lmt_alignment_state.cur_align) = width; + } + tex_aux_change_list_type(cell, unset_node); + box_span_count(cell) = spans; + if (! state) { + halfword order = tex_aux_determine_order(lmt_packaging_state.total_stretch); + box_glue_order(cell) = order; + box_glue_stretch(cell) = lmt_packaging_state.total_stretch[order]; + order = tex_aux_determine_order(lmt_packaging_state.total_shrink); + box_glue_sign(cell) = order; /* hm, sign */ + box_glue_shrink(cell) = lmt_packaging_state.total_shrink[order]; + } + tex_pop_nest(); + tex_tail_append(cell); + /*tex Copy the tabskip glue between columns. */ + if (node_subtype(node_next(lmt_alignment_state.cur_align)) != ignored_glue) { + halfword glue = tex_new_glue_node(node_next(lmt_alignment_state.cur_align), tab_skip_glue); + tex_attach_attribute_list_attribute(cell, lmt_alignment_state.attr_list); + tex_tail_append(glue); + } + if (cmd == alignment_cmd && (chr == cr_code || chr == cr_cr_code)) { + return 1; + } else { + tex_aux_initialize_span(record); + } + } + lmt_input_state.align_state = 1000000; + do { + tex_get_x_or_protected(); + } while (cur_cmd == spacer_cmd); + lmt_alignment_state.cur_align = record; + tex_aux_initialize_column(); + } + } + return 0; +} + +/*tex + + At the end of a row, we append an unset box to the current vlist (for |\halign|) or the current + hlist (for |\valign|). This unset box contains the unset boxes for the columns, separated by + the tabskip glue. Everything will be set later. + +*/ + +static void tex_aux_finish_row(void) +{ + halfword row; + if (cur_list.mode == -hmode) { + row = tex_filtered_hpack(cur_list.head, cur_list.tail, 0, packing_additional, finish_row_group, direction_unknown, 0, null, 0, 0); + tex_pop_nest(); + if (lmt_alignment_state.cur_pre_adjust_head != lmt_alignment_state.cur_pre_adjust_tail) { + tex_inject_adjust_list(lmt_alignment_state.cur_pre_adjust_head, 0, null, NULL); + } + if (lmt_alignment_state.cur_pre_migrate_head != lmt_alignment_state.cur_pre_migrate_tail) { + tex_append_list(lmt_alignment_state.cur_pre_migrate_head, lmt_alignment_state.cur_pre_migrate_tail); + } + tex_append_to_vlist(row, lua_key_index(alignment), NULL); + if (lmt_alignment_state.cur_post_migrate_head != lmt_alignment_state.cur_post_migrate_tail) { + tex_append_list(lmt_alignment_state.cur_post_migrate_head, lmt_alignment_state.cur_post_migrate_tail); + } + if (lmt_alignment_state.cur_post_adjust_head != lmt_alignment_state.cur_post_adjust_tail) { + tex_inject_adjust_list(lmt_alignment_state.cur_post_adjust_head, 0, null, NULL); + } + } else { + row = tex_filtered_vpack(node_next(cur_list.head), 0, packing_additional, max_depth_par, finish_row_group, direction_unknown, 0, null, 0, 0); + tex_pop_nest(); + tex_tail_append(row); + cur_list.space_factor = 1000; + } + if (lmt_alignment_state.wrap_source) { + box_source_anchor(row) = lmt_alignment_state.wrap_source; + tex_set_box_geometry(row, anchor_geometry); + } + tex_aux_change_list_type(row, unset_node); + tex_attach_attribute_list_attribute(row, lmt_alignment_state.attr_list); + if (every_cr_par) { + tex_begin_token_list(every_cr_par, every_cr_text); + } + tex_aux_align_peek(); + /*tex Note that |glue_shrink(p) = 0| since |glue_shrink == shift_amount|. */ +} + +/*tex + + Finally, we will reach the end of the alignment, and we can breathe a sigh of relief that + memory hasn't overflowed. All the unset boxes will now be set so that the columns line up, + taking due account of spanned columns. + + Normalizing by stripping zero tabskips makes the lists a little smaller which then is easier + on later processing. But is is an option. We could actually not inject zero skips at all but + then the code starts deviating too much. In some cases it can save a lot of zero glue nodes + but we allocate them initially anyway. We don't save runtime here. (Some day I'll play a bit + more with this and then probably also implement some pending extensions.) + +*/ + +static void tex_aux_strip_zero_tab_skips(halfword q) +{ + halfword h = box_list(q); + halfword t = h; + while (t) { + halfword n = node_next(t); + if (node_type(t) == glue_node && node_subtype(t) == tab_skip_glue && tex_glue_is_zero(t)) { + tex_try_couple_nodes(node_prev(t),n); + if (t == h) { + /*tex We only come here once. */ + h = n; + box_list(q) = h; + } + tex_flush_node(t); + } + t = n; + } +} + +static void tex_aux_finish_align(void) +{ + /*tex a shared register for the list operations (others are localized) */ + halfword preroll; + /*tex shift offset for unset boxes */ + scaled offset = 0; + /*tex something new */ + halfword reverse = 0; + halfword callback = lmt_alignment_state.callback; + halfword discard = normalize_line_mode_permitted(normalize_line_mode_par, discard_zero_tab_skips_mode); + /*tex The |align_group| was for individual entries: */ + if (cur_group != align_group) { + tex_confusion("align, case 1"); + } + tex_unsave(); + /*tex The |align_group| was for the whole alignment: */ + if (cur_group != align_group) { + tex_confusion("align, case 2"); + } + tex_unsave(); + if (lmt_nest_state.nest[lmt_nest_state.nest_data.ptr - 1].mode == mmode) { + offset = display_indent_par; + } + lmt_save_state.save_stack_data.ptr -= saved_align_n_of_items; + lmt_packaging_state.pack_begin_line = -cur_list.mode_line; + reverse = saved_level(saved_align_reverse); /* we can as well save these in the state */ + discard = discard || saved_level(saved_align_discard); /* we can as well save these in the state */ + /*tex + All content is available now so this is a perfect spot for some processing. However, we + cannot mess with the unset boxes (as these can have special properties). The main reason + for some postprocessing can be to align (vertically) at a specific location in a cell + but then we also need to process twice (and adapt the width in the preamble record). + + We flush the tokenlists so that in principle we can access the align record nodes as normal + lists. + */ + { + halfword q = node_next(preamble); + do { + tex_flush_token_list(align_record_pre_part(q)); + tex_flush_token_list(align_record_post_part(q)); + align_record_pre_part(q) = null; + align_record_post_part(q) = null; + q = node_next(node_next(q)); + } while (q); + } + if (callback) { + lmt_alignment_callback(cur_list.head, preroll_pass_alignment_context, lmt_alignment_state.attr_list, preamble); + } + /*tex + + Go through the preamble list, determining the column widths and changing the alignrecords + to dummy unset boxes. + + It's time now to dismantle the preamble list and to compute the column widths. Let $w_{ij}$ + be the maximum of the natural widths of all entries that span columns $i$ through $j$, + inclusive. The alignrecord for column~$i$ contains $w_{ii}$ in its |width| field, and there + is also a linked list of the nonzero $w_{ij}$ for increasing $j$, accessible via the |info| + field; these span nodes contain the value $j-i+|min_quarterword|$ in their |link| fields. + The values of $w_{ii}$ were initialized to |null_flag|, which we regard as $-\infty$. + + The final column widths are defined by the formula $$ w_j = \max_{1\L i\L j} \biggl( w_{ij} + - \sum_{i\L k < j}(t_k + w_k) \biggr), $$ where $t_k$ is the natural width of the tabskip + glue between columns $k$ and~$k + 1$. However, if $w_{ij} = -\infty$ for all $i$ in the + range $1 <= i <= j$ (i.e., if every entry that involved column~$j$ also involved column~$j + + 1$), we let $w_j = 0$, and we zero out the tabskip glue after column~$j$. + + \TEX\ computes these values by using the following scheme: First $w_1 = w_{11}$. Then + replace $w_{2j}$ by $\max(w_{2j}, w_{1j} - t_1 - w_1)$, for all $j > 1$. Then $w_2 = + w_{22}$. Then replace $w_{3j}$ by $\max(w_{3j}, w_{2j} - t_2 - w_2)$ for all $j > 2$; and + so on. If any $w_j$ turns out to be $-\infty$, its value is changed to zero and so is the + next tabskip. + + */ + { + halfword q = node_next(preamble); + do { + /* So |q| and |p| point to alignment nodes that become unset ones. */ + halfword p = node_next(node_next(q)); + if (box_width(q) == null_flag) { + /*tex Nullify |width(q)| and the tabskip glue following this column. */ + box_width(q) = 0; + tex_reset_glue_to_zero(node_next(q)); + } + if (align_record_span_ptr(q) != end_span) { + /*tex + + Merge the widths in the span nodes of |q| with those of |p|, destroying the + span nodes of |q|. + + Merging of two span-node lists is a typical exercise in the manipulation of + linearly linked data structures. The essential invariant in the following + |repeat| loop is that we want to dispense with node |r|, in |q|'s list, and + |u| is its successor; all nodes of |p|'s list up to and including |s| have + been processed, and the successor of |s| matches |r| or precedes |r| or follows + |r|, according as |link(r) = n| or |link(r) > n| or |link(r) < n|. + + */ + halfword t = box_width(q) + glue_amount(node_next(q)); + halfword n = 1; + halfword r = align_record_span_ptr(q); + halfword s = end_span; + align_record_span_ptr(s) = p; + do { + halfword u = align_record_span_ptr(r); + span_width(r) -= t; + while (span_span(r) > n) { + s = align_record_span_ptr(s); + n = span_span(align_record_span_ptr(s)) + 1; + } + if (span_span(r) < n) { + align_record_span_ptr(r) = align_record_span_ptr(s); + align_record_span_ptr(s) = r; + --span_span(r); + s = r; + } else { + if (span_width(r) > span_width(align_record_span_ptr(s))) { + span_width(align_record_span_ptr(s)) = span_width(r); + } + tex_flush_node(r); + } + r = u; + } while (r != end_span); + } + tex_aux_change_list_type(q, unset_node); + box_glue_order(q) = normal_glue_order; + box_glue_sign(q) = normal_glue_sign; + box_height(q) = 0; + box_depth(q) = 0; + q = p; + } while (q); + } + if (callback) { + lmt_alignment_callback(cur_list.head, package_pass_alignment_context, lmt_alignment_state.attr_list, preamble); + } + /*tex + + Package the preamble list, to determine the actual tabskip glue amounts, and let |p| point + to this prototype box. + + Now the preamble list has been converted to a list of alternating unset boxes and tabskip + glue, where the box widths are equal to the final column sizes. In case of |\valign|, we + change the widths to heights, so that a correct error message will be produced if the + alignment is overfull or underfull. + + */ + if (cur_list.mode == -vmode) { + halfword rule_save = overfull_rule_par; + /*tex Prevent the rule from being packaged. */ + overfull_rule_par = 0; + preroll = tex_hpack(preamble, saved_value(saved_align_specification), saved_extra(saved_align_specification), direction_unknown, holding_none_option); + overfull_rule_par = rule_save; + } else { + halfword unset = node_next(preamble); + do { + box_height(unset) = box_width(unset); + box_width(unset) = 0; + unset = node_next(node_next(unset)); + } while (unset); + /* why filtered here ... */ + preroll = tex_filtered_vpack(preamble, saved_value(saved_align_specification), saved_extra(saved_align_specification), max_depth_par, preamble_group, direction_unknown, 0, 0, 0, holding_none_option); + /* ... so we'll do this soon instead: */ + /* preroll = tex_vpack(preamble, saved_value(saved_align_specification), saved_extra(saved_align_specification), max_depth_par, direction_unknown, migrate_all_option); */ + unset = node_next(preamble); + do { + box_width(unset) = box_height(unset); + box_height(unset) = 0; + unset = node_next(node_next(unset)); + } while (unset); + } + lmt_packaging_state.pack_begin_line = 0; + /*tex + Here we set the glue in all the unset boxes of the current list based on the prerolled + preamble. + */ + { + halfword rowptr = node_next(cur_list.head); + while (rowptr) { + switch (node_type(rowptr)) { + case unset_node: + { + /*tex + We set the unset box |q| and the unset boxes in it. The unset box |q| + represents a row that contains one or more unset boxes, depending on + how soon |\cr| occurred in that row. + + We also reset some fields but this needs checking because we never set + set them in these unset boxes but in the preamble ones. + */ + halfword preptr; + halfword colptr; + if (cur_list.mode == -vmode) { + tex_aux_change_list_type(rowptr, hlist_node); + box_width(rowptr) = box_width(preroll); + } else { + tex_aux_change_list_type(rowptr, vlist_node); + box_height(rowptr) = box_height(preroll); + } + node_subtype(rowptr) = align_row_list; + box_glue_order(rowptr) = box_glue_order(preroll); + box_glue_sign(rowptr) = box_glue_sign(preroll); + box_glue_set(rowptr) = box_glue_set(preroll); + box_shift_amount(rowptr) = offset; + colptr = box_list(rowptr); + preptr = box_list(preroll); + if (node_type(colptr) == glue_node) { + colptr = node_next(colptr); + } + if (node_type(preptr) == glue_node) { + preptr = node_next(preptr); + } + if (node_type(colptr) != unset_node) { + tex_formatted_error("alignment", "bad box"); + } + do { + /*tex + We set the glue in node |r| and change it from an unset node. A box + made from spanned columns will be followed by tabskip glue nodes + and by empty boxes as if there were no spanning. This permits + perfect alignment of subsequent entries, and it prevents values + that depend on floating point arithmetic from entering into the + dimensions of any boxes. + */ + halfword spans = box_span_count(colptr); + scaled total = box_width(preptr); + scaled width = total; /*tex The width of a column. */ + halfword tail = hold_head; + int state = has_box_package_state(preptr, package_dimension_size_set); + /*tex + When we have a span we need to add dummies. We append tabskip glue + and an empty box to list |u|, and update |s| and |t| as the + prototype nodes are passed. We could shortcut some code when we + have zero skips but we seldom end up in this branch anyway. + */ + while (spans > 0) { + --spans; + preptr = node_next(preptr); + if (node_subtype(preptr) != ignored_glue) { + /* halfword glue = tex_new_glue_node(preptr, tab_skip_glue); */ + halfword glue = tex_new_glue_node(preptr, node_subtype(preptr)); + tex_try_couple_nodes(tail, glue); + tex_attach_attribute_list_attribute(glue, lmt_alignment_state.attr_list); + total += glue_amount(preptr); + /*tex The |glueratio| case is redundant, anyway ... */ + switch (box_glue_sign(preroll)) { + case stretching_glue_sign: + if (glue_stretch_order(preptr) == box_glue_order(preroll)) { + total += glueround((glueratio) (box_glue_set(preroll)) * (glueratio) (glue_stretch(preptr))); + } + break; + case shrinking_glue_sign: + if (glue_shrink_order(preptr) == box_glue_order(preroll)) { + total -= glueround((glueratio) (box_glue_set(preroll)) * (glueratio) (glue_shrink(preptr))); + } + break; + } + tail = glue; + /*tex Move on to the box. */ + } + preptr = node_next(preptr); + { + halfword box = tex_new_null_box_node(cur_list.mode == -vmode ? hlist_node : vlist_node, align_cell_list); + tex_couple_nodes(tail, box); + tex_attach_attribute_list_attribute(box, lmt_alignment_state.attr_list); + total += box_width(preptr); + if (cur_list.mode == -vmode) { + box_width(box) = box_width(preptr); + } else { + box_height(box) = box_width(preptr); + } + tail = box; + } + } + if (cur_list.mode == -vmode) { + /*tex + Make the unset node |r| into an |hlist_node| of width |w|, + setting the glue as if the width were |t|. + */ + box_height(colptr) = box_height(rowptr); + box_depth(colptr) = box_depth(rowptr); + if (! state) { + if (total == box_width(colptr)) { + box_glue_sign(colptr) = normal_glue_sign; + box_glue_order(colptr) = normal_glue_order; + box_glue_set(colptr) = 0.0; + } else if (total > box_width(colptr)) { + box_glue_sign(colptr) = stretching_glue_sign; + if (box_glue_stretch(colptr) == 0) { + box_glue_set(colptr) = 0.0; + } else { + box_glue_set(colptr) = (glueratio) ( ( (glueratio) total - (glueratio) box_width(colptr) ) / ( (glueratio) box_glue_stretch(colptr) ) ); + } + } else { + box_glue_order(colptr) = box_glue_sign(colptr); + box_glue_sign(colptr) = shrinking_glue_sign; + if (box_glue_shrink(colptr) == 0) { + box_glue_set(colptr) = 0.0; + } else if ((box_glue_order(colptr) == normal_glue_order) && (box_width(colptr) - total > box_glue_shrink(colptr))) { + box_glue_set(colptr) = 1.0; + } else { + box_glue_set(colptr) = (glueratio) ( ( (glueratio) box_width(colptr) - (glueratio) total ) / ( (glueratio) box_glue_shrink(colptr) ) ); + } + } + } + box_width(colptr) = width; + tex_aux_change_list_type(colptr, hlist_node); + node_subtype(colptr) = align_cell_list; + } else { + /*tex + Make the unset node |r| into a |vlist_node| of height |w|, + setting the glue as if the height were |t|. + */ + box_width(colptr) = box_width(rowptr); + if (! state) { + if (total == box_height(colptr)) { + box_glue_sign(colptr) = normal_glue_sign; + box_glue_order(colptr) = normal_glue_order; + box_glue_set(colptr) = 0.0; + } else if (total > box_height(colptr)) { + box_glue_sign(colptr) = stretching_glue_sign; + if (box_glue_stretch(colptr) == 0) { + box_glue_set(colptr) = 0.0; + } else { + box_glue_set(colptr) = (glueratio) ( ( (glueratio) total - (glueratio) box_height(colptr) ) / ( (glueratio) box_glue_stretch(colptr) ) ); + } + } else { + box_glue_order(colptr) = box_glue_sign(colptr); + box_glue_sign(colptr) = shrinking_glue_sign; + if (box_glue_shrink(colptr) == 0) { + box_glue_set(colptr) = 0.0; + } else if ((box_glue_order(colptr) == normal_glue_order) && (box_height(colptr) - total > box_glue_shrink(colptr))) { + box_glue_set(colptr) = 1.0; + } else { + box_glue_set(colptr) = (glueratio) ( ( (glueratio) box_height(colptr) - (glueratio) total) / ( (glueratio) box_glue_shrink(colptr) ) ); + } + } + } + box_height(colptr) = width; + tex_aux_change_list_type(colptr, vlist_node); + node_subtype(colptr) = align_cell_list; + } + box_shift_amount(colptr) = 0; + if (tail != hold_head) { + /*tex Append blank boxes to account for spanned nodes. */ + tex_try_couple_nodes(tail, node_next(colptr)); + tex_try_couple_nodes(colptr, node_next(hold_head)); + colptr = tail; + } + colptr = node_next(colptr); + preptr = node_next(preptr); + if (node_type(colptr) == glue_node) { + colptr = node_next(colptr); + } + if (node_type(preptr) == glue_node) { + preptr = node_next(preptr); + } + } while (colptr); + if (discard) { + tex_aux_strip_zero_tab_skips(rowptr); + } + if (reverse) { + box_list(rowptr) = tex_reversed_node_list(box_list(rowptr)); + } + } + break; + case rule_node: + { + /*tex + Make the running dimensions in rule |q| extend to the boundaries of the + alignment. + */ + if (rule_width(rowptr) == null_flag) { + rule_width(rowptr) = box_width(preroll); + } + if (rule_height(rowptr) == null_flag) { + rule_height(rowptr) = box_height(preroll); + } + if (rule_depth(rowptr) == null_flag) { + rule_depth(rowptr) = box_depth(preroll); + } + /*tex We could use offset fields in rule instead. */ + if (offset) { + halfword prv = node_prev(rowptr); + halfword nxt = node_next(rowptr); + halfword box = null; + node_prev(rowptr) = null; + node_next(rowptr) = null; + box = tex_hpack(rowptr, 0, packing_additional, direction_unknown, holding_none_option); + tex_attach_attribute_list_attribute(box, rowptr); + box_shift_amount(box) = offset; + node_subtype(box) = align_cell_list; /*tex This is not really a cell. */ + // node_subtype(box) = unknown_list; /*tex So maybe we will do this. */ + tex_try_couple_nodes(prv, box); + tex_try_couple_nodes(box, nxt); + rowptr = box; + } + } + break; + default: + /*tex + When we're in a |\halign| we get the rows (the |unset_node|s) while the + rules are horizontal ones. Furthermore we can get (vertical) glues and + whatever else got kicked in between the rows, but all that is (currently) + not processed. + */ + break; + } + rowptr = node_next(rowptr); + } + } + if (callback) { + lmt_alignment_callback(cur_list.head, wrapup_pass_alignment_context, lmt_alignment_state.attr_list, preamble); + } + tex_flush_node_list(preroll); + delete_attribute_reference(lmt_alignment_state.attr_list); + tex_aux_pop_alignment(); + /*tex + We now have a completed alignment, in the list that starts at |cur_list.head| and ends at + |cur_list.tail|. This list will be merged with the one that encloses it. (In case the + enclosing mode is |mmode|, for displayed formulas, we will need to insert glue before and + after the display; that part of the program will be deferred until we're more familiar with + such operations.) + */ + { + scaled prevdepth = cur_list.prev_depth; + halfword head = node_next(cur_list.head); + halfword tail = cur_list.tail; + tex_pop_nest(); + if (cur_list.mode == mmode) { + tex_finish_display_alignment(head, tail, prevdepth); + } else { + cur_list.prev_depth = prevdepth; + if (head) { + tex_tail_append(head); + cur_list.tail = tail; + } + if (cur_list.mode == vmode) { + if (! lmt_page_builder_state.output_active) { + lmt_page_filter_callback(alignment_page_context, 0); + } + tex_build_page(); + } + } + } +} + +/*tex + + The token list |omit_template| just referred to is a constant token list that contains the + special control sequence |\endtemplate| only. + +*/ + +void tex_initialize_alignments(void) +{ + lmt_alignment_state.hold_token_head = tex_get_available_token(null); + lmt_alignment_state.omit_template = tex_get_available_token(deep_frozen_end_template_1_token); + span_span(end_span) = max_quarterword + 1; + align_record_span_ptr(end_span) = null; +} + +/*tex +* + We no longer store |hold_token_head| and |omit_template| in the format file. It is a bit + cleaner to just initialize them. So we free them. + +*/ + +void tex_cleanup_alignments(void) +{ + tex_put_available_token(lmt_alignment_state.hold_token_head); + tex_put_available_token(lmt_alignment_state.omit_template); + lmt_alignment_state.hold_token_head = null; + lmt_alignment_state.omit_template = null; +} + +/*tex + + We've now covered most of the abuses of |\halign| and |\valign|. Let's take a look at what + happens when they are used correctly. + + An |align_group| code is supposed to remain on the |save_stack| during an entire alignment, + until |finish_align| removes it. + + A devious user might force an |end_template| command to occur just about anywhere; we must + defeat such hacks. + +*/ + +void tex_run_alignment_end_template(void) +{ + lmt_input_state.base_ptr = lmt_input_state.input_stack_data.ptr; + lmt_input_state.input_stack[lmt_input_state.base_ptr] = lmt_input_state.cur_input; + while (( lmt_input_state.input_stack[lmt_input_state.base_ptr].index != template_post_text ) + && (! lmt_input_state.input_stack[lmt_input_state.base_ptr].loc) + && ( lmt_input_state.input_stack[lmt_input_state.base_ptr].state == token_list_state)) { + --lmt_input_state.base_ptr; + } + if (lmt_input_state.input_stack[lmt_input_state.base_ptr].index != template_post_text ) { + tex_alignment_interwoven_error(2); + } else if (lmt_input_state.input_stack[lmt_input_state.base_ptr].loc) { + tex_alignment_interwoven_error(3); + } else if (lmt_input_state.input_stack[lmt_input_state.base_ptr].state != token_list_state) { + tex_alignment_interwoven_error(4); + } else if (cur_group == align_group) { + if (! tex_wrapped_up_paragraph(align_par_context)) { /* needs testing */ + tex_end_paragraph(align_group, align_par_context); + if (tex_aux_finish_column()) { + tex_aux_finish_row(); + } + } + } else { + tex_off_save(); + } +} + +/*tex + + When |\cr| or |\span| or a tab mark comes through the scanner into |main_control|, it might be + that the user has foolishly inserted one of them into something that has nothing to do with + alignment. But it is far more likely that a left brace or right brace has been omitted, since + |get_next| takes actions appropriate to alignment only when |\cr| or |\span| or tab marks occur + with |align_state = 0|. The following program attempts to make an appropriate recovery. + + As an experiment we support nested |\noalign| usage but we do keep the braces so there is still + grouping. We don't flag these groups as |no_align_group| because then we need to do more work + and it's not worth the trouble. One can actually argue for not doing that anyway. + + I might now rename the next one to |run_alignment| (and then also a companion as we have two + cases of usage). + +*/ + +void tex_run_alignment_error(void) +{ + int cmd = cur_cmd; + int chr = cur_chr; + if (cmd == alignment_cmd && chr == no_align_code) { + if (! tex_aux_nested_no_align()) { + tex_handle_error( + normal_error_type, + "Misplaced \\noalign", + "I expect to see \\noalign only after the \\cr of an alignment. Proceed, and I'll\n" + "ignore this case." + ); + } + } else if (abs(lmt_input_state.align_state) > 2) { + /*tex + Express consternation over the fact that no alignment is in progress. In traditional + \TEX\ the ampersand case will show a specific tab help, while in case of another + character a more generic message is shown. + + We go for consistency here, so a little patch: + */ + switch (cmd) { + case alignment_tab_cmd: + tex_handle_error(normal_error_type, "Misplaced %C", cmd, chr, + "I can't figure out why you would want to use a tab mark here. If some right brace\n" + "up above has ended a previous alignment prematurely, you're probably due for more\n" + "error messages." + ); + break; + default: + tex_handle_error(normal_error_type, "Misplaced %C", cmd, chr, + "I can't figure out why you would want to use a tab mark or \\cr or \\span just\n" + "now. If something like a right brace up above has ended a previous alignment\n" + "prematurely, you're probably due for more error messages." + ); + break; + } + } else { + const char * helpinfo = + "I've put in what seems to be necessary to fix the current column of the current\n" + "alignment. Try to go on, since this might almost work."; + tex_back_input(cur_tok); + if (lmt_input_state.align_state < 0) { + ++lmt_input_state.align_state; + cur_tok = left_brace_token + '{'; + tex_handle_error( + insert_error_type, + "Missing { inserted", + helpinfo + ); + } else { + --lmt_input_state.align_state; + cur_tok = right_brace_token + '}'; + switch (cmd) { + case alignment_cmd: + tex_handle_error( + insert_error_type, + "Missing } inserted, unexpected ", + cmd, chr, + helpinfo + ); + break; + case alignment_tab_cmd: + tex_handle_error( + insert_error_type, + "Missing } inserted, unexpected tab character (normally &)", + helpinfo + ); + break; + } + } + } +} |