% nesting.w % % Copyright 2009-2010 Taco Hoekwater % This file is part of LuaTeX. % LuaTeX is free software; you can redistribute it and/or modify it under % the terms of the GNU General Public License as published by the Free % Software Foundation; either version 2 of the License, or (at your % option) any later version. % LuaTeX is distributed in the hope that it will be useful, but WITHOUT % ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or % FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public % License for more details. % You should have received a copy of the GNU General Public License along % with LuaTeX; if not, see . @ @c #include "ptexlib.h" static const char _svn_version[] = "$Id: nesting.w 3615 2010-04-13 21:59:59Z oneiros $ " "$URL: http://foundry.supelec.fr/svn/luatex/tags/beta-0.70.1/source/texk/web2c/luatexdir/tex/nesting.w $"; @ these are for |show_activities| @c #define page_goal page_so_far[0] #define pdf_ignored_dimen dimen_par(pdf_ignored_dimen_code) #define count(A) eqtb[count_base+(A)].cint @ \TeX\ is typically in the midst of building many lists at once. For example, when a math formula is being processed, \TeX\ is in math mode and working on an mlist; this formula has temporarily interrupted \TeX\ from being in horizontal mode and building the hlist of a paragraph; and this paragraph has temporarily interrupted \TeX\ from being in vertical mode and building the vlist for the next page of a document. Similarly, when a \.{\\vbox} occurs inside of an \.{\\hbox}, \TeX\ is temporarily interrupted from working in restricted horizontal mode, and it enters internal vertical mode. The ``semantic nest'' is a stack that keeps track of what lists and modes are currently suspended. At each level of processing we are in one of six modes: \yskip\hang|vmode| stands for vertical mode (the page builder); \hang|hmode| stands for horizontal mode (the paragraph builder); \hang|mmode| stands for displayed formula mode; \hang|-vmode| stands for internal vertical mode (e.g., in a \.{\\vbox}); \hang|-hmode| stands for restricted horizontal mode (e.g., in an \.{\\hbox}); \hang|-mmode| stands for math formula mode (not displayed). \yskip\noindent The mode is temporarily set to zero while processing \.{\\write} texts in the |ship_out| routine. Numeric values are assigned to |vmode|, |hmode|, and |mmode| so that \TeX's ``big semantic switch'' can select the appropriate thing to do by computing the value |abs(mode)+cur_cmd|, where |mode| is the current mode and |cur_cmd| is the current command code. @c static const char *string_mode(int m) { /* prints the mode represented by |m| */ if (m > 0) { switch (m / (max_command_cmd + 1)) { case 0: return "vertical mode"; break; case 1: return "horizontal mode"; break; case 2: return "display math mode"; break; default: break; } } else if (m == 0) { return "no mode"; } else { switch ((-m) / (max_command_cmd + 1)) { case 0: return "internal vertical mode"; break; case 1: return "restricted horizontal mode"; break; case 2: return "math mode"; break; default: break; } } return "unknown mode"; } @ @c void print_mode(int m) { /* prints the mode represented by |m| */ tprint(string_mode(m)); } @ @c void print_in_mode(int m) { /* prints the mode represented by |m| */ tprint("' in "); tprint(string_mode(m)); } @ @c int get_mode_id(void) { /* returns the mode represented by |m| */ int m = cur_list.mode_field; if (m > 0) { switch (m / (max_command_cmd + 1)) { case 0: return 'v'; break; case 1: return 'h'; break; case 2: return 'm'; break; default: return '\0'; break; } } else if (m == 0) { return 'n';; } else { switch ((-m) / (max_command_cmd + 1)) { case 0: return 'V'; break; case 1: return 'H'; break; case 2: return 'M'; break; default: return '\0'; break; } } } @ The state of affairs at any semantic level can be represented by five values: \yskip\hang|mode| is the number representing the semantic mode, as just explained. \yskip\hang|head| is a |pointer| to a list head for the list being built; |link(head)| therefore points to the first element of the list, or to |null| if the list is empty. \yskip\hang|tail| is a |pointer| to the final node of the list being built; thus, |tail=head| if and only if the list is empty. \yskip\hang|prev_graf| is the number of lines of the current paragraph that have already been put into the present vertical list. \yskip\hang|aux| is an auxiliary |memory_word| that gives further information that is needed to characterize the situation. \yskip\noindent In vertical mode, |aux| is also known as |prev_depth|; it is the scaled value representing the depth of the previous box, for use in baseline calculations, or it is |<=-1000|pt if the next box on the vertical list is to be exempt from baseline calculations. In horizontal mode, |aux| is also known as |space_factor|; it holds the current space factor used in spacing calculations. In math mode, |aux| is also known as |incompleat_noad|; if not |null|, it points to a record that represents the numerator of a generalized fraction for which the denominator is currently being formed in the current list. There is also a sixth quantity, |mode_line|, which correlates the semantic nest with the user's input; |mode_line| contains the source line number at which the current level of nesting was entered. The negative of this line number is the |mode_line| at the level of the user's output routine. A seventh quantity, |eTeX_aux|, is used by the extended features eTeX. In math mode it is known as |delim_ptr| and points to the most recent |fence_noad| of a |math_left_group|. In horizontal mode, the |prev_graf| field is used for initial language data. The semantic nest is an array called |nest| that holds the |mode|, |head|, |tail|, |prev_graf|, |aux|, and |mode_line| values for all semantic levels below the currently active one. Information about the currently active level is kept in the global quantities |mode|, |head|, |tail|, |prev_graf|, |aux|, and |mode_line|, which live in a struct that is ready to be pushed onto |nest| if necessary. The math field is used by various bits and pieces in |texmath.w| @ This implementation of \TeX\ uses two different conventions for representing sequential stacks. @^stack conventions@>@^conventions for representing stacks@> \yskip\hang 1) If there is frequent access to the top entry, and if the stack is essentially never empty, then the top entry is kept in a global variable (even better would be a machine register), and the other entries appear in the array $\\{stack}[0\to(\\{ptr}-1)]$. The semantic stack is handled this way. \yskip\hang 2) If there is infrequent top access, the entire stack contents are in the array $\\{stack}[0\to(\\{ptr}-1)]$. For example, the |save_stack| is treated this way, as we have seen. @c list_state_record *nest; int nest_ptr; /* first unused location of |nest| */ int max_nest_stack; /* maximum of |nest_ptr| when pushing */ int shown_mode; /* most recent mode shown by \.{\\tracingcommands} */ halfword save_tail; /* save |tail| so we can examine whether we have an auto kern before a glue */ @ We will see later that the vertical list at the bottom semantic level is split into two parts; the ``current page'' runs from |page_head| to |page_tail|, and the ``contribution list'' runs from |contrib_head| to |tail| of semantic level zero. The idea is that contributions are first formed in vertical mode, then ``contributed'' to the current page (during which time the page-breaking decisions are made). For now, we don't need to know any more details about the page-building process. @c void initialize_nesting(void) { nest_ptr = 0; max_nest_stack = 0; shown_mode = 0; cur_list.mode_field = vmode; cur_list.head_field = contrib_head; cur_list.tail_field = contrib_head; cur_list.eTeX_aux_field = null; cur_list.prev_depth_field = ignore_depth; cur_list.space_factor_field = 1000; cur_list.incompleat_noad_field = null; cur_list.ml_field = 0; cur_list.pg_field = 0; cur_list.dirs_field = null; init_math_fields(); } @ Here is a common way to make the current list grow: @c void tail_append(halfword p) { couple_nodes(cur_list.tail_field, p); cur_list.tail_field = vlink(cur_list.tail_field); } @ @c halfword pop_tail(void) { halfword n, r; if (cur_list.tail_field != cur_list.head_field) { r = cur_list.tail_field; if (vlink(alink(cur_list.tail_field)) == cur_list.tail_field) { n = alink(cur_list.tail_field); } else { n = cur_list.head_field; while (vlink(n) != cur_list.tail_field) n = vlink(n); } flush_node(cur_list.tail_field); cur_list.tail_field = n; vlink(n) = null; return r; } else { return null; } } @ When \TeX's work on one level is interrupted, the state is saved by calling |push_nest|. This routine changes |head| and |tail| so that a new (empty) list is begun; it does not change |mode| or |aux|. @c void push_nest(void) { /* enter a new semantic level, save the old */ if (nest_ptr > max_nest_stack) { max_nest_stack = nest_ptr; if (nest_ptr == nest_size) overflow("semantic nest size", (unsigned) nest_size); } incr(nest_ptr); cur_list.mode_field = nest[nest_ptr - 1].mode_field; cur_list.head_field = new_node(temp_node, 0); cur_list.tail_field = cur_list.head_field; cur_list.eTeX_aux_field = null; cur_list.ml_field = line; cur_list.pg_field = 0; cur_list.dirs_field = null; cur_list.prev_depth_field = nest[nest_ptr - 1].prev_depth_field; cur_list.space_factor_field = nest[nest_ptr - 1].space_factor_field; cur_list.incompleat_noad_field = nest[nest_ptr - 1].incompleat_noad_field; init_math_fields(); } @ Conversely, when \TeX\ is finished on the current level, the former state is restored by calling |pop_nest|. This routine will never be called at the lowest semantic level, nor will it be called unless |head| is a node that should be returned to free memory. @c void pop_nest(void) { /* leave a semantic level, re-enter the old */ flush_node(cur_list.head_field); decr(nest_ptr); } @ Here is a procedure that displays what \TeX\ is working on, at all levels. @c void show_activities(void) { int p; /* index into |nest| */ int m; /* mode */ halfword q, r; /* for showing the current page */ int t; /* ditto */ tprint_nl(""); print_ln(); for (p = nest_ptr; p >= 0; p--) { m = nest[p].mode_field; tprint_nl("### "); print_mode(m); tprint(" entered at line "); print_int(abs(nest[p].ml_field)); /* we dont do this any more */ #if 0 if (m == hmode) if (nest[p].pg_field != 040600000) { tprint(" (language"); print_int(nest[p].pg_field % 0200000); tprint(":hyphenmin"); print_int(nest[p].pg_field / 020000000); print_char(','); print_int((nest[p].pg_field / 0200000) % 0100); print_char(')'); } #endif if (nest[p].ml_field < 0) tprint(" (\\output routine)"); if (p == 0) { /* Show the status of the current page */ if (page_head != page_tail) { tprint_nl("### current page:"); if (output_active) tprint(" (held over for next output)"); show_box(vlink(page_head)); if (page_contents > empty) { tprint_nl("total height "); print_totals(); tprint_nl(" goal height "); print_scaled(page_goal); r = vlink(page_ins_head); while (r != page_ins_head) { print_ln(); tprint_esc("insert"); t = subtype(r); print_int(t); tprint(" adds "); if (count(t) == 1000) t = height(r); else t = x_over_n(height(r), 1000) * count(t); print_scaled(t); if (type(r) == split_up_node) { q = page_head; t = 0; do { q = vlink(q); if ((type(q) == ins_node) && (subtype(q) == subtype(r))) incr(t); } while (q != broken_ins(r)); tprint(", #"); print_int(t); tprint(" might split"); } r = vlink(r); } } } if (vlink(contrib_head) != null) tprint_nl("### recent contributions:"); } show_box(vlink(nest[p].head_field)); /* Show the auxiliary field, |a| */ switch (abs(m) / (max_command_cmd + 1)) { case 0: tprint_nl("prevdepth "); if (nest[p].prev_depth_field <= pdf_ignored_dimen) tprint("ignored"); else print_scaled(nest[p].prev_depth_field); if (nest[p].pg_field != 0) { tprint(", prevgraf "); print_int(nest[p].pg_field); if (nest[p].pg_field != 1) tprint(" lines"); else tprint(" line"); } break; case 1: tprint_nl("spacefactor "); print_int(nest[p].space_factor_field); /* we dont do this any more, this was aux.rh originally */ #if 0 if (m > 0) { if (nest[p].current_language_field > 0) { tprint(", current language "); print_int(nest[p].current_language_field); } } #endif break; case 2: if (nest[p].incompleat_noad_field != null) { tprint("this will be denominator of:"); show_box(nest[p].incompleat_noad_field); } } /* there are no other cases */ } }