texnesting.c /size: 30 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 These are for |show_activities|: */
8
9# define page_goal lmt_page_builder_state.goal
10
11/*tex
12
13    \TEX\ is typically in the midst of building many lists at once. For example, when a math formula
14    is being processed, \TEX\ is in math mode and working on an mlist; this formula has temporarily
15    interrupted \TEX\ from being in horizontal mode and building the hlist of a paragraph; and this
16    paragraph has temporarily interrupted \TEX\ from being in vertical mode and building the vlist
17    for the next page of a document. Similarly, when a |\vbox| occurs inside of an |\hbox|, \TEX\ is
18    temporarily interrupted from working in restricted horizontal mode, and it enters internal
19    vertical mode. The \quote {semantic nest} is a stack that keeps track of what lists and modes
20    are currently suspended.
21
22    At each level of processing we are in one of six modes:
23
24    \startitemize[n]
25        \startitem
26            |vmode| stands for vertical mode (the page builder);
27        \stopitem
28        \startitem
29            |hmode| stands for horizontal mode (the paragraph builder);
30        \stopitem
31        \startitem
32            |mmode| stands for displayed formula mode;
33        \stopitem
34        \startitem
35            |-vmode| stands for internal vertical mode (e.g., in a |\vbox|);
36        \stopitem
37        \startitem
38            |-hmode| stands for restricted horizontal mode (e.g., in an |\hbox|);
39        \stopitem
40        \startitem
41            |-mmode| stands for math formula mode (not displayed).
42        \stopitem
43    \stopitemize
44
45    The mode is temporarily set to zero while processing |\write| texts in the |ship_out| routine.
46
47    Numeric values are assigned to |vmode|, |hmode|, and |mmode| so that \TEX's \quote {big semantic
48    switch} can select the appropriate thing to do by computing the value |abs(mode) + cur_cmd|,
49    where |mode| is the current mode and |cur_cmd| is the current command code.
50
51    Per end December 2022 we no longer use the larg emode numbers that also encode the command at 
52    hand. That code is in the archive. 
53
54*/
55
56const char *tex_string_mode(int m)
57{
58    switch (m) {
59        case nomode          : return "no mode";
60        case vmode           : return "vertical mode";
61        case hmode           : return "horizontal mode";
62        case mmode           : return "display math mode";
63        case internal_vmode  : return "internal vertical mode";
64        case restricted_hmode: return "restricted horizontal mode";
65        case inline_mmode    : return "inline math mode";
66        default              : return "unknown mode";
67    }
68}
69
70/*tex
71
72    The state of affairs at any semantic level can be represented by five values:
73
74    \startitemize
75        \startitem
76            |mode| is the number representing the semantic mode, as just explained.
77        \stopitem
78        \startitem
79            |head| is a |pointer| to a list head for the list being built; |link(head)| therefore
80            points to the first element of the list, or to |null| if the list is empty.
81        \stopitem
82        \startitem
83            |tail| is a |pointer| to the final node of the list being built; thus, |tail=head| if
84            and only if the list is empty.
85        \stopitem
86        \startitem
87            |prev_graf| is the number of lines of the current paragraph that have already been put
88            into the present vertical list.
89        \stopitem
90        \startitem
91            |aux| is an auxiliary |memoryword| that gives further information that is needed to
92            characterize the situation.
93        \stopitem
94    \stopitemize
95
96    In vertical mode, |aux| is also known as |prev_depth|; it is the scaled value representing the
97    depth of the previous box, for use in baseline calculations, or it is |<= -1000pt| if the next
98    box on the vertical list is to be exempt from baseline calculations. In horizontal mode, |aux|
99    is also known as |space_factor|; it holds the current space factor used in spacing calculations.
100    In math mode, |aux| is also known as |incompleat_noad|; if not |null|, it points to a record
101    that represents the numerator of a generalized fraction for which the denominator is currently
102    being formed in the current list.
103
104    There is also a sixth quantity, |mode_line|, which correlates the semantic nest with the
105    user's input; |mode_line| contains the source line number at which the current level of nesting
106    was entered. The negative of this line number is the |mode_line| at the level of the user's
107    output routine.
108
109    A seventh quantity, |eTeX_aux|, is used by the extended features eTeX. In math mode it is known
110    as |delim_ptr| and points to the most recent |fence_noad| of a |math_left_group|.
111
112    In horizontal mode, the |prev_graf| field is used for initial language data.
113
114    The semantic nest is an array called |nest| that holds the |mode|, |head|, |tail|, |prev_graf|,
115    |aux|, and |mode_line| values for all semantic levels below the currently active one.
116    Information about the currently active level is kept in the global quantities |mode|, |head|,
117    |tail|, |prev_graf|, |aux|, and |mode_line|, which live in a struct that is ready to be pushed
118    onto |nest| if necessary.
119
120    The math field is used by various bits and pieces in |texmath.w|
121
122    This implementation of \TEX\ uses two different conventions for representing sequential stacks.
123
124    \startitemize[n]
125
126        \startitem
127            If there is frequent access to the top entry, and if the stack is essentially never
128            empty, then the top entry is kept in a global variable (even better would be a machine
129            register), and the other entries appear in the array |stack[0 .. (ptr-1)]|. The semantic
130            stack is handled this way.
131        \stopitem
132
133        \startitem
134            If there is infrequent top access, the entire stack contents are in the array |stack[0
135            .. (ptr - 1)]|. For example, the |save_stack| is treated this way, as we have seen.
136        \stopitem
137
138    \stopitemize
139
140    In |nest_ptr| we have the first unused location of |nest|, and |max_nest_stack| has the maximum
141    of |nest_ptr| when pushing. In |shown_mode| we store the most recent mode shown by
142    |\tracingcommands| and with |save_tail| we can examine whether we have an auto kern before a
143    glue.
144
145*/
146
147nest_state_info lmt_nest_state = {
148    .nest       = NULL,
149    .nest_data  = {
150        .minimum   = min_nest_size,
151        .maximum   = max_nest_size,
152        .size      = siz_nest_size,
153        .step      = stp_nest_size,
154        .allocated = 0,
155        .itemsize  = sizeof(list_state_record),
156        .top       = 0,
157        .ptr       = 0,
158        .initial   = memory_data_unset,
159        .offset    = 0,
160        .extra     = 0, 
161    },
162    .shown_mode = 0,
163    .math_mode  = 0,
164};
165
166/*tex
167
168    We will see later that the vertical list at the bottom semantic level is split into two parts;
169    the \quote {current page} runs from |page_head| to |page_tail|, and the \quote {contribution
170    list} runs from |contribute_head| to |tail| of semantic level zero. The idea is that contributions
171    are first formed in vertical mode, then \quote {contributed} to the current page (during which
172    time the page|-|breaking decisions are made). For now, we don't need to know any more details
173    about the page-building process.
174
175*/
176
177# define reserved_nest_slots 0
178
179void tex_initialize_nest_state(void)
180{
181    int size = lmt_nest_state.nest_data.minimum;
182    lmt_nest_state.nest = aux_allocate_clear_array(sizeof(list_state_record), size, reserved_nest_slots);
183    if (lmt_nest_state.nest) {
184        lmt_nest_state.nest_data.allocated = size;
185    } else {
186        tex_overflow_error("nest", size);
187    }
188}
189
190static int tex_aux_room_on_nest_stack(void) /* quite similar to save_stack checker so maybe share */
191{
192    int top = lmt_nest_state.nest_data.ptr;
193    if (top > lmt_nest_state.nest_data.top) {
194        lmt_nest_state.nest_data.top = top;
195        if (top > lmt_nest_state.nest_data.allocated) {
196            list_state_record *tmp = NULL;
197            top = lmt_nest_state.nest_data.allocated + lmt_nest_state.nest_data.step;
198            if (top > lmt_nest_state.nest_data.size) {
199                top = lmt_nest_state.nest_data.size;
200            }
201            if (top > lmt_nest_state.nest_data.allocated) {
202                lmt_nest_state.nest_data.allocated = top;
203                tmp = aux_reallocate_array(lmt_nest_state.nest, sizeof(list_state_record), top, reserved_nest_slots);
204                lmt_nest_state.nest = tmp;
205            }
206            lmt_run_memory_callback("nest", tmp ? 1 : 0);
207            if (! tmp) {
208                tex_overflow_error("nest", top);
209                return 0;
210            }
211        }
212    }
213    return 1;
214}
215
216void tex_initialize_nesting(void)
217{
218    lmt_nest_state.nest_data.ptr = 0;
219    lmt_nest_state.nest_data.top = 0;
220# if 1
221    lmt_nest_state.shown_mode = 0;
222    lmt_nest_state.math_mode = 0;
223    cur_list.mode = vmode;
224    cur_list.head = contribute_head;
225    cur_list.tail = contribute_head;
226    cur_list.delimiter = null;
227    cur_list.prev_graf = 0;
228    cur_list.mode_line = 0;
229    cur_list.prev_depth = ignore_depth; /*tex |ignore_depth_criterion_par| is not yet available! */
230    cur_list.space_factor = default_space_factor;
231    cur_list.incomplete_noad = null;
232    cur_list.direction_stack = null;
233    cur_list.math_dir = 0;
234    cur_list.math_style = -1;
235    cur_list.math_main_style = -1;
236    cur_list.math_parent_style = -1;
237    cur_list.math_flatten = 1;
238    cur_list.math_begin = unset_noad_class;
239    cur_list.math_end = unset_noad_class;
240    cur_list.math_mode = 0;
241    cur_list.options = 0;
242# else 
243    cur_list = (list_state_record) {
244        .mode              = vmode,
245        .head              = contribute_head,
246        .tail              = contribute_head,
247        .delimiter         = null,
248        .prev_graf         = 0,
249        .mode_line         = 0,
250        .prev_depth        = ignore_depth, /*tex |ignore_depth_criterion_par| is not yet available! */
251        .space_factor      = default_space_factor,
252        .incomplete_noad   = null,
253        .direction_stack   = null,
254        .math_dir          = 0,
255        .math_style        = -1,
256        .math_main_style   = -1,
257        .math_parent_style = -1,
258        .math_flatten      = 1,
259        .math_begin        = unset_noad_class,
260        .math_end          = unset_noad_class,
261        .math_mode         = 0,
262        .options           = 0,
263    };
264# endif 
265}
266
267halfword tex_pop_tail(void)
268{
269    if (cur_list.tail != cur_list.head) {
270        halfword r = cur_list.tail;
271        halfword n = node_prev(r);
272        if (node_next(n) != r) {
273            n = cur_list.head;
274            while (node_next(n) != r) {
275                n = node_next(n);
276            }
277        }
278        cur_list.tail = n;
279        node_prev(r) = null;
280        node_next(n) = null;
281        return r;
282    } else {
283        return null;
284    }
285}
286
287/*tex
288
289    When \TEX's work on one level is interrupted, the state is saved by calling |push_nest|. This
290    routine changes |head| and |tail| so that a new (empty) list is begun; it does not change
291    |mode| or |aux|.
292
293*/
294
295void tex_push_nest(void)
296{
297    list_state_record *top = &lmt_nest_state.nest[lmt_nest_state.nest_data.ptr];
298    lmt_nest_state.nest_data.ptr += 1;
299 // lmt_nest_state.shown_mode = 0; // needs checking 
300    lmt_nest_state.math_mode = 0;
301    if (tex_aux_room_on_nest_stack()) {
302# if 1
303            cur_list.mode = top->mode;
304            cur_list.head = tex_new_temp_node();
305            cur_list.tail = cur_list.head;
306            cur_list.delimiter = null;
307            cur_list.prev_graf = 0;
308            cur_list.mode_line = lmt_input_state.input_line;
309            cur_list.prev_depth = top->prev_depth;
310            cur_list.space_factor = top->space_factor;
311            cur_list.incomplete_noad = top->incomplete_noad;
312            cur_list.direction_stack = null;
313            cur_list.math_dir = 0;
314            cur_list.math_style = -1;
315            cur_list.math_main_style = top->math_main_style;
316            cur_list.math_parent_style = top->math_parent_style;
317            cur_list.math_flatten = 1;
318            cur_list.math_begin = unset_noad_class;
319            cur_list.math_end = unset_noad_class;
320         // cur_list.math_begin = top->math_begin;
321         // cur_list.math_end = top->math_end;
322            cur_list.math_mode = 0;
323            cur_list.options = 0;
324# else
325            cur_list = (list_state_record) {
326                .mode              = top->mode,
327                .head              = null,
328                .tail              = null,
329                .delimiter         = null,
330                .prev_graf         = 0,
331                .mode_line         = lmt_input_state.input_line,
332                .prev_depth        = top->prev_depth,
333                .space_factor      = top->space_factor,
334                .incomplete_noad   = top->incomplete_noad,
335                .direction_stack   = null,
336                .math_dir          = 0,
337                .math_style        = -1,
338                .math_main_style   = top->math_main_style,
339                .math_parent_style = top->math_parent_style,
340                .math_flatten      = 1,
341                .math_begin        = unset_noad_class,
342                .math_end          = unset_noad_class,
343             // .math_begin        = top->math_begin,
344             // .math_end          = top->math_end,
345                .math_mode         = 0,
346                .options           = 0,
347            };
348            cur_list.head = tex_new_temp_node(),
349            cur_list.tail = cur_list.head;
350# endif
351
352    } else {
353        tex_overflow_error("semantic nest size", lmt_nest_state.nest_data.size);
354    }
355}
356
357/*tex
358
359    Conversely, when \TEX\ is finished on the current level, the former state is restored by
360    calling |pop_nest|. This routine will never be called at the lowest semantic level, nor will
361    it be called unless |head| is a node that should be returned to free memory.
362
363*/
364
365void tex_pop_nest(void)
366{
367    if (cur_list.head) {
368        /* tex_free_node(cur_list.head, temp_node_size); */ /* looks fragile */
369        tex_flush_node(cur_list.head);
370        /*tex Just to be sure, in case we access from \LUA: */
371     // cur_list.head = null;
372     // cur_list.tail = null;
373    }
374    --lmt_nest_state.nest_data.ptr;
375}
376
377/*tex Here is a procedure that displays what \TEX\ is working on, at all levels. */
378
379void tex_show_activities(void)
380{
381    tex_print_nlp();
382    for (int p = lmt_nest_state.nest_data.ptr; p >= 0; p--) {
383        list_state_record n = lmt_nest_state.nest[p];
384        tex_print_format("%l[%M entered at line %i%s]", n.mode, abs(n.mode_line), n.mode_line < 0 ? " (output routine)" : ""); // %L
385        if (p == 0) {
386            /*tex Show the status of the current page */
387            if (page_head != lmt_page_builder_state.page_tail) {
388                tex_print_format("%l[current page:%s]", lmt_page_builder_state.output_active ? " (held over for next output)" : "");
389                tex_show_box(node_next(page_head));
390                if (lmt_page_builder_state.contents != contribute_nothing) {
391                    halfword r;
392                    tex_print_format("%l[total height %P, goal height %p]",
393                        page_total, page_stretch, page_filstretch, page_fillstretch, page_filllstretch, page_shrink,
394                        page_goal
395                    );
396                    r = node_next(page_insert_head);
397                    while (r != page_insert_head) {
398                        halfword index = insert_index(r);
399                        halfword multiplier = tex_get_insert_multiplier(index);
400                        halfword size = multiplier == scaling_factor ? insert_total_height(r) : tex_x_over_n(insert_total_height(r), scaling_factor) * multiplier;
401                        if (node_type(r) == split_node && node_subtype(r) == insert_split_subtype) {
402                            halfword q = page_head;
403                            halfword n = 0;
404                            do {
405                                q = node_next(q);
406                                if (node_type(q) == insert_node && split_insert_index(q) == insert_index(r)) {
407                                    ++n;
408                                }
409                            } while (q != split_broken_insert(r));
410                            tex_print_format("%l[insert %i adds %p, might split to %i]", index, size, n);
411                        } else {
412                            tex_print_format("%l[insert %i adds %p]", index, size);
413                        }
414                        r = node_next(r);
415                    }
416                }
417            }
418            if (node_next(contribute_head)) {
419                tex_print_format("%l[recent contributions:]");
420            }
421        }
422        tex_print_format("%l[begin list]");
423        tex_show_box(node_next(n.head));
424        tex_print_format("%l[end list]");
425        /*tex Show the auxiliary field, |a|. */
426        switch (n.mode) {
427            case vmode:
428            case internal_vmode:
429                {
430                    if (n.prev_depth <= ignore_depth_criterion_par) {
431                        tex_print_format("%l[prevdepth ignored");
432                    } else {
433                        tex_print_format("%l[prevdepth %p", n.prev_depth);
434                    }
435                    if (n.prev_graf != 0) {
436                        tex_print_format(", prevgraf %i line%s", n.prev_graf, n.prev_graf == 1 ? "" : "s");
437                    }
438                    tex_print_char(']');
439                    break;
440                }
441            case mmode:
442            case inline_mmode:
443                {
444                    if (n.incomplete_noad) {
445                        tex_print_format("%l[this will be denominator of:]");
446                        tex_print_format("%l[begin list]");
447                        tex_show_box(n.incomplete_noad);
448                        tex_print_format("%l[end list]");
449                    }
450                    break;
451                }
452        }
453    }
454}
455
456int tex_vmode_nest_index(void)
457{
458    int p = lmt_nest_state.nest_data.ptr; /* index into |nest| */
459    while (! is_v_mode(lmt_nest_state.nest[p].mode)) {
460        --p;
461    }
462    return p;
463}
464
465void tex_tail_prepend(halfword n) 
466{
467    tex_couple_nodes(node_prev(cur_list.tail), n);
468    tex_couple_nodes(n, cur_list.tail);
469    if (cur_list.tail == cur_list.head) {
470        cur_list.head = n;
471    }
472}
473
474void tex_tail_append(halfword p)
475{
476    node_next(cur_list.tail) = p;
477    node_prev(p) = cur_list.tail;
478    cur_list.tail = p;
479}
480
481/*tex
482
483    In the end this is nicer than the ugly look back and set extensible properties on a last node, 
484    although that is a bit more generic. So we're back at an old \MKIV\ feature that looks ahead 
485    but this time selective and therefore currently only for a few math node types. Math has a 
486    synchronization issue: we can do the same with a node list handler but then we need to plug 
487    into |mlist_to_hlist| which is nto pretty either.  Eventually I might do that anyway and then 
488    this will disappear. This more \quote {immediate} approach also has the benefit that we can 
489    cleanup immediately. (It could be used a bit like runtime captures in \LUA.)
490
491*/
492
493halfword tex_tail_fetch_callback(void)
494{
495    halfword tail = cur_list.tail;
496    if (node_type(tail) == boundary_node && node_subtype(tail) == lua_boundary) { 
497        cur_list.tail = node_prev(tail);
498        node_next(cur_list.tail) = null;
499        node_prev(tail) = null;
500        node_next(tail) = null;
501        return tail;
502    } else {
503        return null;
504    }
505}
506
507halfword tex_tail_apply_callback(halfword p, halfword c)
508{
509    if (p && c) { 
510        int callback_id = lmt_callback_defined(tail_append_callback);
511        if (callback_id > 0) {
512            lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "Ndd->N", p, boundary_data(c), boundary_reserved(c), &p);
513        }
514        tex_flush_node(c);
515    }
516    return p;
517}
518
519void tex_tail_append_list(halfword p)
520{
521    if (p) { 
522        node_next(cur_list.tail) = p;
523        node_prev(p) = cur_list.tail;
524        cur_list.tail = tex_tail_of_node_list(p);
525    }
526}
527
528void tex_tail_append_callback(halfword p)
529{
530    halfword c = tex_tail_fetch_callback();
531    if (c) { 
532        p = tex_tail_apply_callback(p, c);
533    }
534    tex_tail_append_list(p);
535}
536
537/*tex 
538    This is an experiment. We reserve slot 0 for special purposes. 
539*/
540
541
542/* 
543    todo: stack so that we can nest 
544    todo: handle prev_depth 
545    todo: less fields needed, so actually we can have a 'register' or maybe even use a box 
546    todo: check if at the outer level 
547*/
548
549/* 
550    contribute_head : nest[0].head : temp node 
551    contribute_tail : nest[0].tail 
552*/
553
554mvl_state_info lmt_mvl_state = {
555    .mvl       = NULL,
556    .mvl_data  = {
557        .minimum   = min_mvl_size,
558        .maximum   = max_mvl_size,
559        .size      = memory_data_unset,
560        .step      = stp_mvl_size,
561        .allocated = 0,
562        .itemsize  = sizeof(list_state_record),
563        .top       = 0,
564        .ptr       = 0,
565        .initial   = memory_data_unset,
566        .offset    = 0,
567        .extra     = 0, 
568    },
569};
570
571static void tex_aux_reset_mvl(int i) 
572{
573    lmt_mvl_state.mvl[i] = (list_state_record) {
574        .mode              = vmode,
575        .head              = null,
576        .tail              = null,
577        .delimiter         = null,
578        .prev_graf         = 0,
579        .mode_line         = 0,
580        .prev_depth        = ignore_depth,   
581        .space_factor      = default_space_factor,
582        .incomplete_noad   = null,
583        .direction_stack   = null,
584        .math_dir          = 0,
585        .math_style        = -1,
586        .math_main_style   = -1,
587        .math_parent_style = -1,
588        .math_flatten      = 1,
589        .math_begin        = unset_noad_class,
590        .math_end          = unset_noad_class,
591        .options           = 0,
592    };
593}
594
595# define reserved_mvl_slots 0
596
597void tex_initialize_mvl_state(void)
598{
599    list_state_record *tmp = aux_allocate_clear_array(sizeof(list_state_record), lmt_mvl_state.mvl_data.minimum, 1);
600    if (tmp) {
601        lmt_mvl_state.mvl = tmp;
602        lmt_mvl_state.mvl_data.allocated = lmt_mvl_state.mvl_data.minimum;
603        lmt_mvl_state.mvl_data.top = lmt_mvl_state.mvl_data.minimum;
604        lmt_mvl_state.mvl_data.ptr = 0;
605    } else {
606        tex_overflow_error("mvl", lmt_mvl_state.mvl_data.minimum);
607    }
608    tex_aux_reset_mvl(0);
609    lmt_mvl_state.slot = 0;
610}
611
612static int tex_valid_mvl_id(halfword n)
613{ 
614 // if (lmt_nest_state.nest_data.ptr > 0) {
615 //     tex_handle_error(
616 //         normal_error_type,
617 //         "An mlv command can only be used at the outer level.",
618 //         "You cannot use an mlv command inside a box."
619 //     );
620 // } else 
621    if (n <= lmt_mvl_state.mvl_data.ptr) {
622        return 1;
623    } else if (n < lmt_mvl_state.mvl_data.top) {
624        lmt_mvl_state.mvl_data.ptr = n;
625        return 1;
626    } else if (n < lmt_mvl_state.mvl_data.maximum && lmt_mvl_state.mvl_data.top < lmt_mvl_state.mvl_data.maximum) {
627        list_state_record *tmp = NULL;
628        int top = n + lmt_mvl_state.mvl_data.step;
629        if (top > lmt_mvl_state.mvl_data.maximum) {
630            top = lmt_mvl_state.mvl_data.maximum;
631        }
632        tmp = aux_reallocate_array(lmt_mvl_state.mvl, sizeof(list_state_record), top, 1); // 1 slack reserved_mvl_slots
633        if (tmp) {
634            size_t extra = ((size_t) top - lmt_mvl_state.mvl_data.top) * sizeof(list_state_record);
635            memset(&tmp[lmt_mvl_state.mvl_data.top + 1], 0, extra);
636            lmt_mvl_state.mvl = tmp;
637            lmt_mvl_state.mvl_data.allocated = top;
638            lmt_mvl_state.mvl_data.top = top;
639            lmt_mvl_state.mvl_data.ptr = n;
640            return 1;
641        }
642    }
643    tex_overflow_error("mvl", lmt_mvl_state.mvl_data.maximum);
644    return 0;
645}
646
647void tex_start_mvl(void)
648{
649    halfword index = 0; 
650    halfword options = 0;
651    halfword prevdepth = max_dimen;
652    while (1) {
653        switch (tex_scan_character("iopIOP", 0, 1, 0)) {
654            case 'i': case 'I':
655                if (tex_scan_mandate_keyword("index", 1)) {
656                    index = tex_scan_integer(0, NULL, NULL);
657                }
658                break;
659            case 'o': case 'O':
660                if (tex_scan_mandate_keyword("options", 1)) {
661                    options = tex_scan_integer(0, NULL, NULL);
662                }
663                break;
664            case 'p': case 'P':
665                if (tex_scan_mandate_keyword("prevdepth", 1)) {
666                    prevdepth = tex_scan_dimension(0, 0, 0, 0, NULL, NULL);
667                }
668                break;
669            default:
670                goto DONE;
671        }
672    }
673  DONE:
674    if (! index) { 
675        index = tex_scan_integer(0, NULL, NULL);
676    }
677    if (lmt_mvl_state.slot) {
678        /*tex We're already collecting. */
679    } else if (index <= 0) {
680        /*tex We have an invalid id. */
681    } else if (! tex_valid_mvl_id(index)) {
682        /*tex We're in trouble. */
683    } else { 
684        /*tex We're can start collecting. */
685        list_state_record *mvl = &lmt_mvl_state.mvl[index];
686        int start = ! mvl->head;
687        if (options & mvl_ignore_prev_depth) { 
688            prevdepth = ignore_depth_criterion_par;
689        } else if (options & mvl_no_prev_depth) { 
690            prevdepth = 0;
691        } else if (prevdepth == max_dimen) { 
692            prevdepth = lmt_mvl_state.mvl[index].prev_depth;
693        }
694        if (tracing_mvl_par) { 
695            tex_begin_diagnostic();
696            tex_print_format("[mvl: index %i, options %x, prevdepth %p, %s]", index, options, prevdepth, start ? "start" : "restart");
697            tex_end_diagnostic();
698        }
699        if (start) { 
700            mvl->head = tex_new_temp_node();
701            mvl->tail = mvl->head;
702        }
703        mvl->options = options;
704        lmt_mvl_state.mvl[0].prev_depth = lmt_nest_state.nest[0].prev_depth;
705        lmt_nest_state.nest[0].prev_depth = prevdepth;
706        lmt_mvl_state.slot = index;
707    }
708}
709
710void tex_stop_mvl(void)
711{
712    halfword index = lmt_mvl_state.slot;
713    if (index) {
714        list_state_record *mvl = &lmt_mvl_state.mvl[index];
715        int something = mvl->tail != mvl->head;
716        if (tracing_mvl_par) { 
717            tex_begin_diagnostic();
718            tex_print_format("[mvl: index %i, options %x, stop with%s contributions]", index, mvl->options, something ? "" : "out");
719            tex_end_diagnostic();
720        }
721        if (something && (mvl->options & mvl_discard_bottom)) {
722            halfword last = mvl->tail;
723            while (last) {
724                if (non_discardable(last)) {
725                    break;
726                } else if (node_type(last) == kern_node && ! (node_subtype(last) == explicit_kern_subtype)) {
727                    break;
728                } else { 
729                    halfword preceding = node_prev(last);
730                    node_next(preceding) = null;
731                    tex_flush_node(last);
732                    mvl->tail = preceding;
733                    if (mvl->head == preceding) { 
734                        break;
735                    } else {
736                        last = preceding;
737                    }
738                }
739            }
740        }
741        mvl->prev_depth = lmt_nest_state.nest[0].prev_depth;
742        lmt_nest_state.nest[0].prev_depth = mvl->prev_depth;
743        lmt_mvl_state.slot = 0;
744    }
745}
746
747# define page_callback 1
748
749halfword tex_flush_mvl(halfword index)
750{
751 // halfword index = tex_scan_integer(0, NULL);
752    if (lmt_mvl_state.slot) { 
753        /*tex We're collecting. */
754        return null; 
755    } else if (! tex_valid_mvl_id(index)) {
756        /*tex We're in trouble. */
757        return null;
758    } else if (! lmt_mvl_state.mvl[index].tail || lmt_mvl_state.mvl[index].tail == lmt_mvl_state.mvl[index].head) {
759        /*tex We collected nothing or are invalid. */
760        return null;
761    } else { 
762        /*tex We collected something. */
763        halfword head = node_next(lmt_mvl_state.mvl[index].head);
764        tex_flush_node(lmt_mvl_state.mvl[index].head);
765        tex_aux_reset_mvl(index);
766        if (tracing_mvl_par) { 
767            tex_begin_diagnostic();
768            tex_print_format("[mvl: index %i, %s]", index, "flush");
769            tex_end_diagnostic();
770        }
771        node_prev(head) = null;
772# if page_callback
773        return tex_vpack(head, 0, packing_additional, max_dimension, 0, holding_none_option, NULL);
774# else 
775        if (head) {
776            return tex_filtered_vpack(head, 0, packing_additional, max_dimension, 0, 0, 0, node_attr(head), 0, 0, NULL);
777        } else {
778            return tex_vpack(head, 0, packing_additional, max_dimension, 0, holding_none_option, NULL);
779        }
780# endif
781    }
782}
783
784int tex_appended_mvl(halfword context, halfword boundary)
785{
786    if (! lmt_mvl_state.slot) {
787        /*tex We're not collecting. */
788        return 0;
789    } else { 
790# if page_callback
791        if (! lmt_page_builder_state.output_active) {
792            lmt_page_filter_callback(context, boundary);
793        } 
794# endif 
795        if (node_next(contribute_head) && ! lmt_page_builder_state.output_active) {
796            halfword first = node_next(contribute_head); 
797            int assign = lmt_mvl_state.mvl[lmt_mvl_state.slot].tail == lmt_mvl_state.mvl[lmt_mvl_state.slot].head;
798            if (assign && (lmt_mvl_state.mvl[lmt_mvl_state.slot].options & mvl_discard_top)) {
799                while (first) {
800                    if (non_discardable(first)) {
801                        break;
802                    } else if (node_type(first) == kern_node && ! (node_subtype(first) == explicit_kern_subtype)) {
803                        break;
804                    } else { 
805                        halfword following = node_next(first);
806                        node_prev(following) = null;
807                        tex_flush_node(first);
808                        first = following;
809                    }
810                }
811            }
812            if (contribute_head != contribute_tail && first) {
813                if (tracing_mvl_par) { 
814                    tex_begin_diagnostic();
815                    tex_print_format("[mvl: index %i, %s]", lmt_mvl_state.slot, assign ? "assign" : "append");
816                    tex_end_diagnostic();
817                }
818                if (assign) { 
819                    node_next(lmt_mvl_state.mvl[lmt_mvl_state.slot].head) = first;
820                    /* what with prev */
821                } else { 
822                    tex_couple_nodes(lmt_mvl_state.mvl[lmt_mvl_state.slot].tail, first);
823                }
824                lmt_mvl_state.mvl[lmt_mvl_state.slot].tail = contribute_tail;
825            }
826            node_next(contribute_head) = null;
827            contribute_tail = contribute_head;
828        }
829        return 1;
830    }
831}
832
833int tex_current_mvl(halfword *head, halfword *tail)
834{
835    if (lmt_mvl_state.slot == 0) { 
836        if (head && tail) {
837            *head = node_next(page_head);
838            *tail = lmt_page_builder_state.page_tail;
839        }
840        return 0; 
841    } else if (lmt_mvl_state.slot > 0) {
842        if (head && tail) {
843            *head = lmt_mvl_state.mvl[lmt_mvl_state.slot].head;
844            *tail = lmt_mvl_state.mvl[lmt_mvl_state.slot].tail;
845        }
846        return lmt_mvl_state.slot; 
847    } else { 
848        if (head && tail) {
849            *head = null;
850            *tail = null;
851        }
852        return 0;
853    } 
854}
855