texlive[43454] Build/source/texk/web2c: sync with luatex r.6287 and

commits+lscarso at tug.org commits+lscarso at tug.org
Sat Mar 11 01:31:06 CET 2017


Revision: 43454
          http://tug.org/svn/texlive?view=revision&revision=43454
Author:   lscarso
Date:     2017-03-11 01:31:06 +0100 (Sat, 11 Mar 2017)
Log Message:
-----------
sync with luatex r.6287 and mpost r.2125

Modified Paths:
--------------
    trunk/Build/source/texk/web2c/ac/web2c.ac
    trunk/Build/source/texk/web2c/luatexdir/am/libluatex.am
    trunk/Build/source/texk/web2c/luatexdir/am/luatex.am
    trunk/Build/source/texk/web2c/luatexdir/font/luafont.w
    trunk/Build/source/texk/web2c/luatexdir/lang/texlang.w
    trunk/Build/source/texk/web2c/luatexdir/lua/lauxlib_bridge.h
    trunk/Build/source/texk/web2c/luatexdir/lua/lepdflib.cc
    trunk/Build/source/texk/web2c/luatexdir/lua/limglib.c
    trunk/Build/source/texk/web2c/luatexdir/lua/liolibext.c
    trunk/Build/source/texk/web2c/luatexdir/lua/lnewtokenlib.c
    trunk/Build/source/texk/web2c/luatexdir/lua/loslibext.c
    trunk/Build/source/texk/web2c/luatexdir/lua/lstatslib.c
    trunk/Build/source/texk/web2c/luatexdir/lua/lstrlibext.c
    trunk/Build/source/texk/web2c/luatexdir/lua/luainit.w
    trunk/Build/source/texk/web2c/luatexdir/lua/luastuff.w
    trunk/Build/source/texk/web2c/luatexdir/lua/luatex-api.h
    trunk/Build/source/texk/web2c/luatexdir/luaffi/README
    trunk/Build/source/texk/web2c/luatexdir/luaffi/ffi.c
    trunk/Build/source/texk/web2c/luatexdir/luaffi/ffi.h
    trunk/Build/source/texk/web2c/luatexdir/luafontloader/fontforge/fontforge/configure-pfaedit.h
    trunk/Build/source/texk/web2c/luatexdir/luatex.c
    trunk/Build/source/texk/web2c/luatexdir/ptexlib.h
    trunk/Build/source/texk/web2c/luatexdir/tex/commands.w
    trunk/Build/source/texk/web2c/luatexdir/tex/dumpdata.w
    trunk/Build/source/texk/web2c/luatexdir/tex/equivalents.h
    trunk/Build/source/texk/web2c/luatexdir/tex/linebreak.w
    trunk/Build/source/texk/web2c/luatexdir/tex/maincontrol.w
    trunk/Build/source/texk/web2c/luatexdir/tex/mlist.w
    trunk/Build/source/texk/web2c/luatexdir/tex/postlinebreak.w
    trunk/Build/source/texk/web2c/luatexdir/tex/texmath.w
    trunk/Build/source/texk/web2c/luatexdir/tex/texnodes.h
    trunk/Build/source/texk/web2c/luatexdir/tex/texnodes.w
    trunk/Build/source/texk/web2c/luatexdir/tex/textoken.h
    trunk/Build/source/texk/web2c/luatexdir/tex/textoken.w
    trunk/Build/source/texk/web2c/luatexdir/utils/utils.w
    trunk/Build/source/texk/web2c/mplibdir/am/libmplib.am
    trunk/Build/source/texk/web2c/mplibdir/am/mplib.am
    trunk/Build/source/texk/web2c/mplibdir/mp.w
    trunk/Build/source/texk/web2c/mplibdir/mpmath.w
    trunk/Build/source/texk/web2c/mplibdir/mpmathdouble.w
    trunk/Build/source/texk/web2c/mplibdir/pngout.w
    trunk/Build/source/texk/web2c/mplibdir/svgout.w

Added Paths:
-----------
    trunk/Build/source/texk/web2c/luatexdir/lua/helpers.w
    trunk/Build/source/texk/web2c/luatexdir/lua/luatex-core.c
    trunk/Build/source/texk/web2c/luatexdir/lua/luatex-core.lua
    trunk/Build/source/texk/web2c/luatexdir/lua/mplibstuff.w

Modified: trunk/Build/source/texk/web2c/ac/web2c.ac
===================================================================
--- trunk/Build/source/texk/web2c/ac/web2c.ac	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/ac/web2c.ac	2017-03-11 00:31:06 UTC (rev 43454)
@@ -35,8 +35,8 @@
 [[euptex],    [yes], [yes], [e-upTeX],    [ptexenc]],
 [[aleph],     [yes], [],    [Aleph],      []],
 [[pdftex],    [yes], [yes], [pdfTeX],     [xpdf libpng]],
-[[luatex],    [yes], [],    [LuaTeX],     [poppler mpfr cairo libpng zziplib lua52]],
-[[luajittex], [yes], [],    [LuaJITTeX],  [poppler mpfr cairo libpng zziplib luajit]],
+[[luatex],    [yes], [],    [LuaTeX],     [poppler mpfr libpng zziplib lua52]],
+[[luajittex], [yes], [],    [LuaJITTeX],  [poppler mpfr libpng zziplib luajit]],
 [[mp],        [yes], [],    [MetaPost],   [mpfr cairo libpng]],
 [[pmp],       [yes], [],    [pMetaPost],  [mpfr cairo libpng ptexenc]],
 [[upmp],      [yes], [],    [upMetaPost], [mpfr cairo libpng ptexenc]],

Modified: trunk/Build/source/texk/web2c/luatexdir/am/libluatex.am
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/am/libluatex.am	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/am/libluatex.am	2017-03-11 00:31:06 UTC (rev 43454)
@@ -34,20 +34,22 @@
 dist_libluatex_a_SOURCES = \
 	luatexdir/lua/lstrlibext.c
 nodist_libluatex_a_SOURCES = \
-	luastuff.c texluac.c \
+	helpers.c luastuff.c texluac.c \
 	$(dist_libluatex_sources) \
 	$(nodist_libluatex_sources)
 dist_libluajittex_a_SOURCES = \
 	luatexdir/lua/lauxlib_bridge.h \
-	luatexdir/lua/lstrlibextjit.c
+	luatexdir/lua/lstrlibext.c
 nodist_libluajittex_a_SOURCES = \
-	luajitstuff.c texluajitc.c \
+	luastuff.c texluajitc.c \
 	$(dist_libluatex_sources) \
 	$(nodist_libluatex_sources)
 
-$(libluatex_a_OBJECTS): libff.a libmplib.a libluamisc.a $(POPPLER_DEPEND)
-$(libluajittex_a_OBJECTS): libff.a libmplib.a libluajitmisc.a $(POPPLER_DEPEND)
+## mplib "stub" backends are in mplibstuff.c
+$(libluatex_a_OBJECTS): libff.a libmplibcore.a libluamisc.a $(POPPLER_DEPEND)
+$(libluajittex_a_OBJECTS): libff.a libmplibcore.a libluajitmisc.a $(POPPLER_DEPEND)
 
+
 ## from luatexdir
 ##
 libluatex_web =
@@ -214,10 +216,12 @@
 ##
 luatex_lua_ctangle = $(ctangle_silent)CWEBINPUTS=$(srcdir)/luatexdir/lua $(ctangle)
 
+helpers.c: ctangle$(EXEEXT) luatexdir/lua/helpers.w
+	$(luatex_lua_ctangle) helpers.w
 luainit.c: ctangle$(EXEEXT) luatexdir/lua/luainit.w
 	$(luatex_lua_ctangle) luainit.w
-luajitstuff.c: ctangle$(EXEEXT) luatexdir/lua/luajitstuff.w
-	$(luatex_lua_ctangle) luajitstuff.w
+#luajitstuff.c: ctangle$(EXEEXT) luatexdir/lua/luajitstuff.w
+#	$(luatex_lua_ctangle) luajitstuff.w
 luanode.c: ctangle$(EXEEXT) luatexdir/lua/luanode.w
 	$(luatex_lua_ctangle) luanode.w
 luastuff.c: ctangle$(EXEEXT) luatexdir/lua/luastuff.w
@@ -224,19 +228,24 @@
 	$(luatex_lua_ctangle) luastuff.w
 luatoken.c: ctangle$(EXEEXT) luatexdir/lua/luatoken.w
 	$(luatex_lua_ctangle) luatoken.w
+mplibstuff.c: ctangle$(EXEEXT) luatexdir/lua/mplibstuff.w
+	$(luatex_lua_ctangle) mplibstuff.w
 texluac.c: ctangle$(EXEEXT) luatexdir/lua/texluac.w
 	$(luatex_lua_ctangle) texluac.w
 texluajitc.c: ctangle$(EXEEXT) luatexdir/lua/texluajitc.w
 	$(luatex_lua_ctangle) texluajitc.w
 
-libluatex_web += luatexdir/lua/luainit.w luatexdir/lua/luajitstuff.w
+libluatex_web += luatexdir/lua/helpers.w
+#libluatex_web += luatexdir/lua/luainit.w luatexdir/lua/luajitstuff.w
+libluatex_web += luatexdir/lua/luainit.w 
 libluatex_web += luatexdir/lua/luanode.w luatexdir/lua/luastuff.w luatexdir/lua/luatoken.w
+libluatex_web += luatexdir/lua/mplibstuff.w
 libluatex_web += luatexdir/lua/texluac.w luatexdir/lua/texluajitc.w
 
 nodist_libluatex_sources +=  luainit.c luanode.c luatoken.c
+nodist_libluatex_sources +=  mplibstuff.c
 
 
-
 dist_libluatex_sources += \
 	luatexdir/lua/lcallbacklib.c \
 	luatexdir/lua/lfontlib.c \
@@ -255,7 +264,8 @@
 	luatexdir/lua/ltexiolib.c \
 	luatexdir/lua/ltexlib.c \
 	luatexdir/lua/lnewtokenlib.c \
-	luatexdir/lua/luatex-api.h
+	luatexdir/lua/luatex-api.h \
+	luatexdir/lua/luatex-core.c
 
 ## from luatexdir/pdf
 ##

Modified: trunk/Build/source/texk/web2c/luatexdir/am/luatex.am
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/am/luatex.am	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/am/luatex.am	2017-03-11 00:31:06 UTC (rev 43454)
@@ -48,7 +48,7 @@
 luatex_LDFLAGS = -export-dynamic
 luajittex_LDFLAGS = -export-dynamic $(LUAJIT_LDEXTRA)
 
-luatex_postldadd = libmplib.a $(MPFR_LIBS) $(GMP_LIBS) $(CAIRO_LIBS) $(PIXMAN_LIBS)
+luatex_postldadd = libmplibcore.a $(MPFR_LIBS) $(GMP_LIBS) 
 luatex_postldadd += $(ZZIPLIB_LIBS) $(LIBPNG_LIBS) $(ZLIB_LIBS) $(POPPLER_LIBS)
 luatex_postldadd += $(LDADD) libmputil.a libunilib.a libmd5.a $(lua_socketlibs)
 

Modified: trunk/Build/source/texk/web2c/luatexdir/font/luafont.w
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/font/luafont.w	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/font/luafont.w	2017-03-11 00:31:06 UTC (rev 43454)
@@ -1,2222 +1,2222 @@
-% luafont.w
-%
-% Copyright 2006-2011 Taco Hoekwater <taco@@luatex.org>
-%
-% 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 <http://www.gnu.org/licenses/>.
-
-@ @c
-#include "ptexlib.h"
-#include "lua/luatex-api.h"
-
-#define noVERBOSE
-
-/* todo: also keys */
-
-const char *font_type_strings[] = {
-    "unknown", "virtual", "real", NULL
-};
-
-const char *font_writingmode_strings[] = {
-    "unknown", "horizontal", "vertical", NULL
-};
-
-const char *font_identity_strings[] = {
-    "unknown", "horizontal", "vertical", NULL
-};
-
-const char *font_format_strings[] = {
-    "unknown", "type1", "type3", "truetype", "opentype", NULL
-};
-
-const char *font_embedding_strings[] = {
-    "unknown", "no", "subset", "full", NULL
-};
-
-const char *ligature_type_strings[] = {
-    "=:", "=:|", "|=:", "|=:|", "", "=:|>", "|=:>", "|=:|>", "", "", "", "|=:|>>", NULL
-};
-
-const char *MATH_param_names[] = {
-    "nil",
-    "ScriptPercentScaleDown",
-    "ScriptScriptPercentScaleDown",
-    "DelimitedSubFormulaMinHeight",
-    "DisplayOperatorMinHeight",
-    "MathLeading",
-    "AxisHeight",
-    "AccentBaseHeight",
-    "FlattenedAccentBaseHeight",
-    "SubscriptShiftDown",
-    "SubscriptTopMax",
-    "SubscriptBaselineDropMin",
-    "SuperscriptShiftUp",
-    "SuperscriptShiftUpCramped",
-    "SuperscriptBottomMin",
-    "SuperscriptBaselineDropMax",
-    "SubSuperscriptGapMin",
-    "SuperscriptBottomMaxWithSubscript",
-    "SpaceAfterScript",
-    "UpperLimitGapMin",
-    "UpperLimitBaselineRiseMin",
-    "LowerLimitGapMin",
-    "LowerLimitBaselineDropMin",
-    "StackTopShiftUp",
-    "StackTopDisplayStyleShiftUp",
-    "StackBottomShiftDown",
-    "StackBottomDisplayStyleShiftDown",
-    "StackGapMin",
-    "StackDisplayStyleGapMin",
-    "StretchStackTopShiftUp",
-    "StretchStackBottomShiftDown",
-    "StretchStackGapAboveMin",
-    "StretchStackGapBelowMin",
-    "FractionNumeratorShiftUp",
-    "FractionNumeratorDisplayStyleShiftUp",
-    "FractionDenominatorShiftDown",
-    "FractionDenominatorDisplayStyleShiftDown",
-    "FractionNumeratorGapMin",
-    "FractionNumeratorDisplayStyleGapMin",
-    "FractionRuleThickness",
-    "FractionDenominatorGapMin",
-    "FractionDenominatorDisplayStyleGapMin",
-    "SkewedFractionHorizontalGap",
-    "SkewedFractionVerticalGap",
-    "OverbarVerticalGap",
-    "OverbarRuleThickness",
-    "OverbarExtraAscender",
-    "UnderbarVerticalGap",
-    "UnderbarRuleThickness",
-    "UnderbarExtraDescender",
-    "RadicalVerticalGap",
-    "RadicalDisplayStyleVerticalGap",
-    "RadicalRuleThickness",
-    "RadicalExtraAscender",
-    "RadicalKernBeforeDegree",
-    "RadicalKernAfterDegree",
-    "RadicalDegreeBottomRaisePercent",
-    "MinConnectorOverlap",
-    "SubscriptShiftDownWithSuperscript",
-    "FractionDelimiterSize",
-    "FractionDelimiterDisplayStyleSize",
-    "NoLimitSubFactor",
-    "NoLimitSupFactor",
-    NULL,
-};
-
-/* here for now, may be useful elsewhere */
-
-int ff_checkoption (lua_State *L, int narg, const char *def, const char *const lst[]);
-
-int ff_checkoption (lua_State *L, int narg, const char *def, const char *const lst[]) {
-    const char *name = (def) ? luaL_optstring(L, narg, def) : luaL_checkstring(L, narg);
-    int i;
-    for (i=0; lst[i]; i++)
-    if (strcmp(lst[i], name) == 0)
-        return i;
-    return -1;
-}
-
-#define dump_intfield(L,n,c) \
-    lua_push_string_by_name(L,n); \
-    lua_pushinteger(L, c); \
-    lua_rawset(L, -3); \
-
-#define dump_stringfield(L,n,c) \
-    lua_push_string_by_name(L,n); \
-    lua_pushstring(L, c); \
-    lua_rawset(L, -3);
-
-#define dump_booleanfield(L,n,c) \
-    lua_push_string_by_name(L,n); \
-    lua_pushboolean(L, c); \
-    lua_rawset(L, -3);
-
-static void dump_math_kerns(lua_State * L, charinfo * co, int l, int id)
-{
-    int i;
-    for (i = 0; i < l; i++) {
-        lua_newtable(L);
-        if (id==top_left_kern) {
-            dump_intfield(L, height, co->top_left_math_kern_array[(2*i)]);
-            dump_intfield(L, kern,   co->top_left_math_kern_array[(2*i)+1]);
-        } else if (id==top_right_kern) {
-            dump_intfield(L, height, co->top_right_math_kern_array[(2*i)]);
-            dump_intfield(L, kern,   co->top_right_math_kern_array[(2*i)+1]);
-        } else if (id==bottom_right_kern) {
-            dump_intfield(L, height, co->bottom_right_math_kern_array[(2*i)]);
-            dump_intfield(L, kern,   co->bottom_right_math_kern_array[(2*i)+1]);
-        } else if (id==bottom_left_kern) {
-            dump_intfield(L, height, co->bottom_left_math_kern_array[(2*i)]);
-            dump_intfield(L, kern,   co->bottom_left_math_kern_array[(2*i)+1]);
-        }
-        lua_rawseti(L, -2, (i + 1));
-    }
-}
-
-static void font_char_to_lua(lua_State * L, internal_font_number f, charinfo * co)
-{
-    liginfo *l;
-    kerninfo *ki;
-
-    lua_createtable(L, 0, 10);
-
-    dump_intfield(L,width,get_charinfo_width(co));
-    dump_intfield(L,height,get_charinfo_height(co));
-    dump_intfield(L,depth,get_charinfo_depth(co));
-
-    if (get_charinfo_italic(co) != 0) {
-       dump_intfield(L,italic,get_charinfo_italic(co));
-    }
-    if (get_charinfo_vert_italic(co) != 0) {
-       dump_intfield(L,vert_italic,get_charinfo_vert_italic(co));
-    }
-    if (get_charinfo_top_accent(co) !=0 && get_charinfo_top_accent(co) != INT_MIN) {
-       dump_intfield(L,top_accent,get_charinfo_top_accent(co));
-    }
-    if (get_charinfo_bot_accent(co) != 0 && get_charinfo_bot_accent(co) != INT_MIN) {
-       dump_intfield(L,bot_accent,get_charinfo_bot_accent(co));
-    }
-    if (get_charinfo_ef(co) != 1000) {
-        dump_intfield(L,expansion_factor,get_charinfo_ef(co));
-    }
-    if (get_charinfo_lp(co) != 0) {
-        dump_intfield(L,left_protruding,get_charinfo_lp(co));
-    }
-    if (get_charinfo_rp(co) != 0) {
-        dump_intfield(L,right_protruding,get_charinfo_rp(co));
-    }
-    if (font_encodingbytes(f) == 2) {
-        dump_intfield(L,index,get_charinfo_index(co));
-    }
-    if (get_charinfo_name(co) != NULL) {
-        dump_stringfield(L,name,get_charinfo_name(co));
-    }
-    if (get_charinfo_tounicode(co) != NULL) {
-        dump_stringfield(L,tounicode,get_charinfo_tounicode(co));
-    }
-    if (get_charinfo_tag(co) == list_tag) {
-        dump_intfield(L,next,get_charinfo_remainder(co));
-    }
-    if (get_charinfo_used(co)) {
-        dump_booleanfield(L,used,(get_charinfo_used(co) ? true : false));
-    }
-    if (get_charinfo_tag(co) == ext_tag) {
-        extinfo *h;
-        h = get_charinfo_hor_variants(co);
-        if (h != NULL) {
-            int i = 1;
-            lua_push_string_by_name(L,horiz_variants);
-            lua_newtable(L);
-            while (h != NULL) {
-                lua_createtable(L, 0, 5);
-                dump_intfield(L, glyph, h->glyph);
-                dump_intfield(L, extender, h->extender);
-                dump_intfield(L, start, h->start_overlap);
-                dump_intfield(L, end, h->end_overlap);
-                dump_intfield(L, advance, h->advance);
-                lua_rawseti(L, -2, i);
-                i++;
-                h = h->next;
-            }
-            lua_rawset(L, -3);
-        }
-        h = get_charinfo_vert_variants(co);
-        if (h != NULL) {
-            int i = 1;
-            lua_push_string_by_name(L,vert_variants);
-            lua_newtable(L);
-            while (h != NULL) {
-                lua_createtable(L, 0, 5);
-                dump_intfield(L, glyph, h->glyph);
-                dump_intfield(L, extender, h->extender);
-                dump_intfield(L, start, h->start_overlap);
-                dump_intfield(L, end, h->end_overlap);
-                dump_intfield(L, advance, h->advance);
-                lua_rawseti(L, -2, i);
-                i++;
-                h = h->next;
-            }
-            lua_rawset(L, -3);
-        }
-    }
-    ki = get_charinfo_kerns(co);
-    if (ki != NULL) {
-        int i;
-        lua_push_string_by_name(L,kerns);
-        lua_createtable(L, 10, 1);
-        for (i = 0; !kern_end(ki[i]); i++) {
-            if (kern_disabled(ki[i])) {
-                /* skip like in lookup */
-            } else {
-                lua_rawgeti(L, -1, kern_char(ki[i]));
-                if (lua_type(L,-1) == LUA_TNIL) {
-                    lua_pop(L,1);
-                    if (kern_char(ki[i]) == right_boundarychar) {
-                        lua_push_string_by_name(L,right_boundary);
-                    } else {
-                        lua_pushinteger(L, kern_char(ki[i]));
-                    }
-                    lua_pushinteger(L, kern_kern(ki[i]));
-                    lua_rawset(L, -3);
-                } else {
-                    /* first one wins */
-                    lua_pop(L,1);
-                }
-            }
-        }
-        lua_rawset(L, -3);
-    }
-    l = get_charinfo_ligatures(co);
-    if (l != NULL) {
-        int i;
-        lua_push_string_by_name(L,ligatures);
-        lua_createtable(L, 10, 1);
-        for (i = 0; !lig_end(l[i]); i++) {
-            if (lig_char(l[i]) == right_boundarychar) {
-                lua_push_string_by_name(L,right_boundary);
-            } else {
-                lua_pushinteger(L, lig_char(l[i]));
-            }
-            lua_createtable(L, 0, 2);
-            lua_push_string_by_name(L,type);
-            lua_pushinteger(L, lig_type(l[i]));
-            lua_rawset(L, -3);
-            lua_push_string_by_name(L,char);
-            lua_pushinteger(L, lig_replacement(l[i]));
-            lua_rawset(L, -3);
-            lua_rawset(L, -3);
-        }
-        lua_rawset(L, -3);
-    }
-
-    lua_push_string_by_name(L,mathkern);
-    lua_newtable(L);
-    {
-    int i, j;
-    i = get_charinfo_math_kerns(co, top_right_kern);
-    j = 0;
-    if (i > 0) {
-        j++;
-        lua_push_string_by_name(L,top_right);
-        lua_newtable(L);
-        dump_math_kerns(L, co, i, top_right_kern);
-        lua_rawset(L, -3);
-    }
-    i = get_charinfo_math_kerns(co, top_left_kern);
-    if (i > 0) {
-        j++;
-        lua_push_string_by_name(L,top_left);
-        lua_newtable(L);
-        dump_math_kerns(L, co, i, top_left_kern);
-        lua_rawset(L, -3);
-    }
-    i = get_charinfo_math_kerns(co, bottom_right_kern);
-    if (i > 0) {
-        j++;
-        lua_push_string_by_name(L,bottom_right);
-        lua_newtable(L);
-        dump_math_kerns(L, co, i, bottom_right_kern);
-        lua_rawset(L, -3);
-    }
-    i = get_charinfo_math_kerns(co, bottom_left_kern);
-    if (i > 0) {
-        j++;
-        lua_push_string_by_name(L,bottom_left);
-        lua_newtable(L);
-        dump_math_kerns(L, co, i, bottom_left_kern);
-        lua_rawset(L, -3);
-    }
-    if (j > 0)
-        lua_rawset(L, -3);
-    else
-        lua_pop(L, 2);
-    }
-}
-
-static void write_lua_parameters(lua_State * L, int f)
-{
-    int k;
-    lua_push_string_by_name(L,parameters);
-    lua_newtable(L);
-    for (k = 1; k <= font_params(f); k++) {
-        switch (k) {
-            case slant_code:
-                dump_intfield(L,slant,font_param(f, k));
-                break;
-            case space_code:
-                dump_intfield(L,space,font_param(f, k));
-                break;
-            case space_stretch_code:
-                dump_intfield(L,space_stretch,font_param(f, k));
-                break;
-            case space_shrink_code:
-                dump_intfield(L,space_shrink,font_param(f, k));
-                break;
-            case x_height_code:
-                dump_intfield(L,x_height,font_param(f, k));
-                break;
-            case quad_code:
-                dump_intfield(L,quad,font_param(f, k));
-                break;
-            case extra_space_code:
-                dump_intfield(L,extra_space,font_param(f, k));
-                break;
-            default:
-                lua_pushinteger(L, font_param(f, k));
-                lua_rawseti(L, -2, k);
-        }
-    }
-    lua_rawset(L, -3);
-}
-
-@ @c
-static void write_lua_math_parameters(lua_State * L, int f)
-{
-    int k;
-    lua_push_string_by_name(L,MathConstants);
-    lua_newtable(L);
-    for (k = 1; k <= font_math_params(f); k++) {
-        lua_pushinteger(L, font_math_param(f, k));
-        if (k <= MATH_param_max) {
-            lua_setfield(L, -2, MATH_param_names[k]);
-        } else {
-            lua_rawseti(L, -2, k);
-        }
-    }
-    lua_rawset(L, -3);
-}
-
-int font_to_lua(lua_State * L, int f)
-{
-    int k;
-    charinfo *co;
-    if (font_cache_id(f) > 0) {
-        /* fetch the table from the registry if it was
-           saved there by |font_from_lua()| */
-        lua_rawgeti(L, LUA_REGISTRYINDEX, font_cache_id(f));
-        /* fontdimens can be changed from tex code */
-        write_lua_parameters(L, f);
-        return 1;
-    }
-
-    lua_newtable(L);
-    lua_push_string_by_name(L,name);
-    lua_pushstring(L, font_name(f));
-    lua_rawset(L, -3);
-    if (font_area(f) != NULL) {
-        dump_stringfield(L,area,font_area(f));
-    }
-    if (font_filename(f) != NULL) {
-        dump_stringfield(L,filename,font_filename(f));
-    }
-    if (font_fullname(f) != NULL) {
-        dump_stringfield(L,fullname,font_fullname(f));
-    }
-    if (font_psname(f) != NULL) {
-        dump_stringfield(L,psname,font_psname(f));
-    }
-    if (font_encodingname(f) != NULL) {
-        dump_stringfield(L,encodingname,font_encodingname(f));
-    }
-
-    dump_booleanfield(L,used,(font_used(f) ? true : false));
-    dump_stringfield(L,type,font_type_strings[font_type(f)]);
-    dump_stringfield(L,format,font_format_strings[font_format(f)]);
-    dump_stringfield(L,writingmode,font_writingmode_strings[font_writingmode(f)]);
-    dump_stringfield(L,identity,font_identity_strings[font_identity(f)]);
-    dump_stringfield(L,embedding,font_embedding_strings[font_embedding(f)]);
-
-    dump_intfield(L,units_per_em,font_units_per_em(f));
-    dump_intfield(L,size,font_size(f));
-    dump_intfield(L,designsize,font_dsize(f));
-    dump_intfield(L,checksum,font_checksum(f));
-    dump_intfield(L,slant,font_slant(f));
-    dump_intfield(L,extend,font_extend(f));
-    dump_intfield(L,direction,font_natural_dir(f));
-    dump_intfield(L,encodingbytes,font_encodingbytes(f));
-    dump_booleanfield(L,oldmath,font_oldmath(f));
-    dump_intfield(L,tounicode,font_tounicode(f));
-
-    /* the next one is read only */
-    if (font_max_shrink(f) != 0) {
-        dump_intfield(L,shrink,font_max_shrink(f));
-    }
-    if (font_max_stretch(f) != 0) {
-        dump_intfield(L,stretch,font_max_stretch(f));
-    }
-    if (font_step(f) != 0) {
-        dump_intfield(L,step,font_step(f));
-    }
-    if (font_auto_expand(f) != 0) {
-        dump_booleanfield(L,auto_expand,font_auto_expand(f));
-    }
-    if (pdf_font_attr(f) != 0) {
-        char *s = makecstring(pdf_font_attr(f));
-        dump_stringfield(L,attributes,s);
-        free(s);
-    }
-
-    /* params */
-    write_lua_parameters(L, f);
-    write_lua_math_parameters(L, f);
-
-    /* chars */
-    lua_push_string_by_name(L,characters);
-    lua_createtable(L, font_tables[f]->charinfo_size, 0);       /* all characters */
-    if (has_left_boundary(f)) {
-        co = get_charinfo(f, left_boundarychar);
-        lua_push_string_by_name(L,left_boundary);
-        font_char_to_lua(L, f, co);
-        lua_rawset(L, -3);
-    }
-    if (has_right_boundary(f)) {
-        co = get_charinfo(f, right_boundarychar);
-        lua_push_string_by_name(L,right_boundary);
-        font_char_to_lua(L, f, co);
-        lua_rawset(L, -3);
-    }
-    for (k = font_bc(f); k <= font_ec(f); k++) {
-        if (quick_char_exists(f, k)) {
-            lua_pushinteger(L, k);
-            co = get_charinfo(f, k);
-            font_char_to_lua(L, f, co);
-            lua_rawset(L, -3);
-        }
-    }
-    lua_rawset(L, -3);
-
-    if (font_cache_id(f) == 0) {        /* renew */
-        int r;
-        lua_pushvalue(L, -1);
-        r = luaL_ref(L, LUA_REGISTRYINDEX);     /* pops the table */
-        set_font_cache_id(f, r);
-    }
-    return 1;
-}
-
-#define count_hash_items(L,name,n) \
-    n = 0; \
-    lua_key_rawgeti(name); \
-    if (lua_type(L, -1) == LUA_TTABLE) { \
-        lua_pushnil(L); \
-        while (lua_next(L, -2) != 0) { \
-            n++; \
-            lua_pop(L, 1); \
-        } \
-    } \
-    if (n) { \
-        /* keep table on stack */ \
-    } else{ \
-        lua_pop(L, 1); \
-    }
-
-@ @c
-#define streq(a,b) (strcmp(a,b)==0)
-
-#define append_packet(k) { *(cp++) = (eight_bits) (k); }
-
-#define do_store_four(l) {                 \
-    append_packet((l & 0xFF000000) >> 24); \
-    append_packet((l & 0x00FF0000) >> 16); \
-    append_packet((l & 0x0000FF00) >> 8);  \
-    append_packet((l & 0x000000FF));       \
-}
-
-@ @c
-static void append_float(eight_bits ** cpp, float a)
-{
-    unsigned int i;
-    eight_bits *cp = *cpp;
-    union U {
-        float a;
-        eight_bits b[sizeof(float)];
-    } u;
-    u.a = a;
-    for (i = 0; i < sizeof(float); i++)
-        append_packet(u.b[i]);
-    *cpp = cp;
-}
-
-static int n_enum_field(lua_State * L, int name_index, int dflt, const char **values)
-{
-    int k, t;
-    const char *s;
-    int i = dflt;
-    lua_rawgeti(L, LUA_REGISTRYINDEX, name_index);      /* fetch the stringptr */
-    lua_rawget(L, -2);
-    t = lua_type(L,-1);
-    if (t == LUA_TNUMBER) {
-        i = (int) lua_tointeger(L, -1);
-    } else if (t == LUA_TSTRING) {
-        s = lua_tostring(L, -1);
-        k = 0;
-        while (values[k] != NULL) {
-            if (strcmp(values[k], s) == 0) {
-                i = k;
-                break;
-            }
-            k++;
-        }
-    }
-    lua_pop(L, 1);
-    return i;
-}
-
-static int n_boolean_field(lua_State * L, int name_index, int dflt)
-{
-    int i = dflt;
-    lua_rawgeti(L, LUA_REGISTRYINDEX, name_index);      /* fetch the stringptr */
-    lua_rawget(L, -2);
-    if (lua_isboolean(L, -1)) {
-        i = lua_toboolean(L, -1);
-    }
-    lua_pop(L, 1);
-    return i;
-}
-
-static char *n_string_field_copy(lua_State * L, int name_index, const char *dflt)
-{
-    char *i;
-    lua_rawgeti(L, LUA_REGISTRYINDEX, name_index);      /* fetch the stringptr */
-    lua_rawget(L, -2);
-    if (lua_type(L,-1) == LUA_TSTRING) {
-        i = xstrdup(lua_tostring(L, -1));
-    } else if (dflt == NULL) {
-        i = NULL;
-    } else {
-        i = xstrdup(dflt);
-    }
-    lua_pop(L, 1);
-    return i;
-}
-
-static const char *n_string_field(lua_State * L, int name_index)
-{
-    lua_rawgeti(L, LUA_REGISTRYINDEX, name_index);      /* fetch the stringptr */
-    lua_rawget(L, -2);
-    return lua_tostring(L,-1);
-}
-
-static int n_some_field(lua_State * L, int name_index)
-{
-    lua_rawgeti(L, LUA_REGISTRYINDEX, name_index);      /* fetch the stringptr */
-    lua_rawget(L, -2);
-    return lua_type(L,-1);
-}
-
-/*static void init_font_string_pointers(lua_State * L){}*/
-
-static int count_char_packet_bytes(lua_State * L)
-{
-    register int i;
-    register int l = 0;
-    int ff = 0;
-    for (i = 1; i <= (int) lua_rawlen(L, -1); i++) {
-        lua_rawgeti(L, -1, i);
-        if (lua_istable(L, -1)) {
-            lua_rawgeti(L, -1, 1);
-            if (lua_type(L,-1) == LUA_TSTRING) {
-                const char *s = lua_tostring(L, -1);
-                if (lua_key_eq(s, font)) {
-                    l += 5;
-                    ff = 1;
-                } else if (lua_key_eq(s, char)) {
-                    if (ff == 0) {
-                        l += 5;
-                    }
-                    l += 5;
-                    ff = 1;
-                } else if (lua_key_eq(s, slot)) {
-                    l += 10;
-                    ff = 1;
-                } else if (lua_key_eq(s, comment) || lua_key_eq(s, nop)) {
-                    ;
-                } else if (lua_key_eq(s, push) || lua_key_eq(s, pop)) {
-                    l++;
-                } else if (lua_key_eq(s, rule)) {
-                    l += 9;
-                } else if (lua_key_eq(s, right) || lua_key_eq(s, node)
-                           || lua_key_eq(s, down) || lua_key_eq(s, image)) {
-                    l += 5;
-                } else if (lua_key_eq(s, scale)) {
-                    l += sizeof(float) + 1;
-                } else if (lua_key_eq(s, special) || lua_key_eq(s, lua)) {
-                    size_t len;
-                    lua_rawgeti(L, -2, 2);
-                    if (lua_type(L,-1) == LUA_TSTRING) {
-                        (void) lua_tolstring(L, -1, &len);
-                        lua_pop(L, 1);
-                        if (len > 0) {
-                            l = (int) (l + 5 + (int) len);
-                        }
-                    } else {
-                        lua_pop(L, 1);
-                        normal_error("vf command","invalid packet special");
-                        /* fprintf(stdout, "invalid packet special!\n"); */
-                    }
-                } else {
-                    normal_error("vf command","unknown packet command");
-                    /* fprintf(stdout, "unknown packet command %s!\n", s); */
-                }
-            } else {
-                normal_error("vf command","no packet command");
-             /* fprintf(stdout, "no packet command!\n"); */
-            }
-            lua_pop(L, 1);      /* command name */
-        }
-        lua_pop(L, 1);          /* item */
-    }
-    return l;
-}
-
-static scaled sp_to_dvi(halfword sp, halfword atsize)
-{
-    double result, mult;
-    mult = (double) (atsize / 65536.0);
-    result = (double) (sp * 16.0);
-    return floor(result / mult);
-}
-
-@ @c
-static void read_char_packets(lua_State * L, int *l_fonts, charinfo * co, internal_font_number f, int atsize)
-{
-    int i, n, m;
-    size_t l;
-    int cmd;
-    const char *s;
-    eight_bits *cpackets, *cp;
-    int ff = 0;
-    int max_f = 0;
-    int pc = count_char_packet_bytes(L);
-    if (pc <= 0)
-        return;
-    while (l_fonts[(max_f + 1)] != 0)
-        max_f++;
-    cp = cpackets = xmalloc((unsigned) (pc + 1));
-    for (i = 1; i <= (int) lua_rawlen(L, -1); i++) {
-        lua_rawgeti(L, -1, i);
-        if (lua_istable(L, -1)) {
-            /* fetch the command code */
-            lua_rawgeti(L, -1, 1);
-            if (lua_type(L,-1) == LUA_TSTRING) {
-                s = lua_tostring(L, -1);
-                cmd = 0;
-                if (lua_key_eq(s, font)) {
-                    cmd = packet_font_code;
-                } else if (lua_key_eq(s, char)) {
-                    cmd = packet_char_code;
-                    if (ff == 0) {
-                        append_packet(packet_font_code);
-                        ff = l_fonts[1];
-                        do_store_four(ff);
-                    }
-                } else if (lua_key_eq(s, slot)) {
-                    cmd = packet_nop_code;
-                    lua_rawgeti(L, -2, 2);
-                    n = (int) luaL_checkinteger(L, -1);
-                    if (n ==0) {
-                        ff = f;
-                    } else {
-                        ff = (n > max_f ? l_fonts[1] : l_fonts[n]);
-                    }
-                    lua_rawgeti(L, -3, 3);
-                    n = (int) luaL_checkinteger(L, -1);
-                    lua_pop(L, 2);
-                    append_packet(packet_font_code);
-                    do_store_four(ff);
-                    append_packet(packet_char_code);
-                    do_store_four(n);
-                } else if (lua_key_eq(s, comment) || lua_key_eq(s, nop)) {
-                    cmd = packet_nop_code;
-                } else if (lua_key_eq(s, node)) {
-                    cmd = packet_node_code;
-                } else if (lua_key_eq(s, push)) {
-                    cmd = packet_push_code;
-                } else if (lua_key_eq(s, pop)) {
-                    cmd = packet_pop_code;
-                } else if (lua_key_eq(s, rule)) {
-                    cmd = packet_rule_code;
-                } else if (lua_key_eq(s, right)) {
-                    cmd = packet_right_code;
-                } else if (lua_key_eq(s, down)) {
-                    cmd = packet_down_code;
-                } else if (lua_key_eq(s, special)) {
-                    cmd = packet_special_code;
-                } else if (lua_key_eq(s, image)) {
-                    cmd = packet_image_code;
-                } else if (lua_key_eq(s, scale)) {
-                    cmd = packet_scale_code;
-                } else if (lua_key_eq(s, lua)) {
-                    cmd = packet_lua_code;
-                }
-                switch (cmd) {
-                    case packet_push_code:
-                    case packet_pop_code:
-                        append_packet(cmd);
-                        break;
-                    case packet_font_code:
-                        append_packet(cmd);
-                        lua_rawgeti(L, -2, 2);
-                        n = (int) luaL_checkinteger(L, -1);
-                        if (n == 0) {
-                            ff = n;
-                        } else {
-                            ff = (n > max_f ? l_fonts[1] : l_fonts[n]);
-                        }
-                        do_store_four(ff);
-                        lua_pop(L, 1);
-                        break;
-                    case packet_node_code:
-                        append_packet(cmd);
-                        lua_rawgeti(L, -2, 2);
-                        n = copy_node_list(nodelist_from_lua(L));
-                        do_store_four(n);
-                        lua_pop(L, 1);
-                        break;
-                    case packet_char_code:
-                        append_packet(cmd);
-                        lua_rawgeti(L, -2, 2);
-                        n = (int) luaL_checkinteger(L, -1);
-                        do_store_four(n);
-                        lua_pop(L, 1);
-                        break;
-                    case packet_right_code:
-                    case packet_down_code:
-                        append_packet(cmd);
-                        lua_rawgeti(L, -2, 2);
-                        n = (int) luaL_checkinteger(L, -1);
-                        do_store_four(sp_to_dvi(n, atsize));
-                        lua_pop(L, 1);
-                        break;
-                    case packet_rule_code:
-                        append_packet(cmd);
-                        lua_rawgeti(L, -2, 2);
-                        n = (int) luaL_checkinteger(L, -1);
-                        do_store_four(sp_to_dvi(n, atsize));
-                        lua_rawgeti(L, -3, 3);
-                        n = (int) luaL_checkinteger(L, -1);
-                        do_store_four(sp_to_dvi(n, atsize));
-                        lua_pop(L, 2);
-                        break;
-                    case packet_special_code:
-                    case packet_lua_code:
-                        append_packet(cmd);
-                        lua_rawgeti(L, -2, 2);
-                        s = luaL_checklstring(L, -1, &l);
-                        if (l > 0) {
-                            do_store_four(l);
-                            m = (int) l;
-                            while (m > 0) {
-                                n = *s++;
-                                m--;
-                                append_packet(n);
-                            }
-                        }
-                        lua_pop(L, 1);
-                        break;
-                    case packet_image_code:
-                        append_packet(cmd);
-                        lua_rawgeti(L, -2, 2);      /* img/imgtable? ... */
-                        if (lua_istable(L, -1)) {   /* imgtable ... */
-                            lua_getglobal(L, "img");        /* imglib imgtable ... */
-                            lua_pushstring(L, "new");       /* `new' imglib imgtable ... */
-                            lua_gettable(L, -2);    /* f imglib imgtable ... */
-                            lua_insert(L, -3);      /* imglib imgtable f ... */
-                            lua_pop(L, 1);  /* imgtable f ... */
-                            lua_call(L, 1, 1);
-                        }           /* img ... */
-                        luaL_checkudata(L, -1, TYPE_IMG);   /* img ... --- just typecheck */
-                        n = luaL_ref(L, LUA_REGISTRYINDEX);  /* ... */
-                        do_store_four(n);
-                        break;
-                    case packet_nop_code:
-                        break;
-                    case packet_scale_code:
-                        append_packet(cmd);
-                        lua_rawgeti(L, -2, 2);
-                        append_float(&cp, (float) luaL_checknumber(L, -1));
-                        lua_pop(L, 1);
-                        break;
-                    default:
-                        normal_error("vf command","invalid packet code");
-                        /* fprintf(stdout, "Unknown char packet code %s\n", s); */
-                }
-            }
-            lua_pop(L, 1);      /* command code */
-        } else {
-            normal_error("vf command","commands has to be a tbale");
-            /* fprintf(stdout, "Found a `commands' item that is not a table\n"); */
-        }
-        lua_pop(L, 1);          /* command table */
-    }
-    append_packet(packet_end_code);
-    set_charinfo_packets(co, cpackets);
-    return;
-}
-
-@ @c
-static void read_lua_cidinfo(lua_State * L, int f)
-{
-    int i;
-    char *s;
-    /*lua_getfield(L, -1, "cidinfo");*/
-    lua_key_rawgeti(cidinfo);
-    if (lua_istable(L, -1)) {
-        i = lua_numeric_field_by_index(L,lua_key_index(version), 0);
-        set_font_cidversion(f, i);
-        i = lua_numeric_field_by_index(L,lua_key_index(supplement), 0);
-        set_font_cidsupplement(f, i);
-        s = n_string_field_copy(L, lua_key_index(registry), "Adobe");       /* Adobe-Identity-0 */
-        set_font_cidregistry(f, s);
-        s = n_string_field_copy(L, lua_key_index(ordering), "Identity");
-        set_font_cidordering(f, s);
-    }
-    lua_pop(L, 1);
-}
-
-
-@ @c
-static void read_lua_parameters(lua_State * L, int f)
-{
-    int i, n, t;
-    const char *s;
-    /*lua_getfield(L, -1, "parameters");*/
-    lua_key_rawgeti(parameters);
-    if (lua_istable(L, -1)) {
-        /* the number of parameters is the max(IntegerKeys(L)),7) */
-        n = 7;
-        lua_pushnil(L);         /* first key */
-        while (lua_next(L, -2) != 0) {
-            if (lua_type(L, -2) == LUA_TNUMBER) {
-                i = (int) lua_tointeger(L, -2);
-                if (i > n)
-                    n = i;
-            }
-            lua_pop(L, 1);      /* pop value */
-        }
-        if (n > 7)
-            set_font_params(f, n);
-        /* sometimes it is handy to have all integer keys */
-        for (i = 1; i <= 7; i++) {
-            lua_rawgeti(L, -1, i);
-            if (lua_type(L, -1) == LUA_TNUMBER) {
-                n = lua_roundnumber(L, -1); /* round ? */
-                set_font_param(f, i, n);
-            }
-            lua_pop(L, 1);
-        }
-        lua_pushnil(L);         /* first key */
-        while (lua_next(L, -2) != 0) {
-            t = lua_type(L,-2);
-            if (t == LUA_TNUMBER) {
-                i = (int) lua_tointeger(L, -2);
-                if (i >= 8) {
-                    if (lua_type(L,-1) == LUA_TNUMBER) {
-                        n = lua_roundnumber(L, -1);
-                    } else {
-                        n = 0;
-                    }
-                    set_font_param(f, i, n);
-                }
-            } else if (t == LUA_TSTRING) {
-                s = lua_tostring(L, -2);
-                if (lua_type(L,-1) == LUA_TNUMBER) {
-                    n = lua_roundnumber(L, -1);
-                } else {
-                    n = 0;
-                }
-                if (lua_key_eq(s, slant)) {
-                    set_font_param(f, slant_code, n);
-                } else if (lua_key_eq(s, space)) {
-                    set_font_param(f, space_code, n);
-                } else if (lua_key_eq(s, space_stretch)) {
-                    set_font_param(f, space_stretch_code, n);
-                } else if (lua_key_eq(s, space_shrink)) {
-                    set_font_param(f, space_shrink_code, n);
-                } else if (lua_key_eq(s, x_height)) {
-                    set_font_param(f, x_height_code, n);
-                } else if (lua_key_eq(s, quad)) {
-                    set_font_param(f, quad_code, n);
-                } else if (lua_key_eq(s, extra_space)) {
-                    set_font_param(f, extra_space_code, n);
-                }
-            }
-            lua_pop(L, 1);
-        }
-    }
-    lua_pop(L, 1);
-
-}
-
-@ @c
-static void read_lua_math_parameters(lua_State * L, int f)
-{
-    int i = 0, n = 0, t;
-    /*lua_getfield(L, -1, "MathConstants");*/
-    lua_key_rawgeti(MathConstants);
-    if (lua_istable(L, -1)) {
-        lua_pushnil(L);
-        while (lua_next(L, -2) != 0) {
-            t = lua_type(L,-2);
-            if (t == LUA_TNUMBER) {
-                i = (int) lua_tointeger(L, -2);
-            } else if (t == LUA_TSTRING) {
-                i = ff_checkoption(L, -2, NULL, MATH_param_names);
-            }
-            n = (int) lua_roundnumber(L, -1);
-            if (i > 0) {
-                set_font_math_param(f, i, n);
-            }
-            lua_pop(L, 1);      /* pop value */
-        }
-        set_font_oldmath(f,false);
-    } else {
-        set_font_oldmath(f,true);
-    }
-    lua_pop(L, 1);
-}
-
-@ @c
-#define MIN_INF -0x7FFFFFFF
-
-static void store_math_kerns(lua_State * L, int index, charinfo * co, int id)
-{
-    int l, k;
-    scaled ht, krn;
-    lua_key_direct_rawgeti(index);
-    if (lua_istable(L, -1) && ((k = (int) lua_rawlen(L, -1)) > 0)) {
-        for (l = 0; l < k; l++) {
-            lua_rawgeti(L, -1, (l + 1));
-            if (lua_istable(L, -1)) {
-                ht = (scaled) lua_numeric_field_by_index(L, lua_key_index(height), MIN_INF);
-                krn = (scaled) lua_numeric_field_by_index(L, lua_key_index(kern), MIN_INF);
-                if (krn > MIN_INF && ht > MIN_INF)
-                    add_charinfo_math_kern(co, id, ht, krn);
-            }
-            lua_pop(L, 1);
-        }
-    }
-    lua_pop(L, 1);
-}
-
-@ @c
-static void font_char_from_lua(lua_State * L, internal_font_number f, int i, int *l_fonts, boolean has_math)
-{
-    int k, r, t, lt, u, n;
-    charinfo *co;
-    kerninfo *ckerns;
-    liginfo *cligs;
-    scaled j;
-    const char *s;
-    int nl = 0;                 /* number of ligature table items */
-    int nk = 0;                 /* number of kern table items */
-    int ctr = 0;
-    int atsize = font_size(f);
-    if (lua_istable(L, -1)) {
-        co = get_charinfo(f, i);
-        set_charinfo_tag(co, 0);
-        j = lua_numeric_field_by_index(L, lua_key_index(width), 0);
-        set_charinfo_width(co, j);
-        j = lua_numeric_field_by_index(L, lua_key_index(height), 0);
-        set_charinfo_height(co, j);
-        j = lua_numeric_field_by_index(L, lua_key_index(depth), 0);
-        set_charinfo_depth(co, j);
-        j = lua_numeric_field_by_index(L, lua_key_index(italic), 0);
-        set_charinfo_italic(co, j);
-        j = lua_numeric_field_by_index(L, lua_key_index(vert_italic), 0);
-        set_charinfo_vert_italic(co, j);
-        j = lua_numeric_field_by_index(L, lua_key_index(index), 0);
-        set_charinfo_index(co, j);
-        j = lua_numeric_field_by_index(L, lua_key_index(expansion_factor), 1000);
-        set_charinfo_ef(co, j);
-        j = lua_numeric_field_by_index(L, lua_key_index(left_protruding), 0);
-        set_charinfo_lp(co, j);
-        j = lua_numeric_field_by_index(L, lua_key_index(right_protruding), 0);
-        set_charinfo_rp(co, j);
-        k = n_boolean_field(L, lua_key_index(used), 0);
-        set_charinfo_used(co, k);
-        s = n_string_field(L, lua_key_index(name));
-        if (s != NULL)
-            set_charinfo_name(co, xstrdup(s));
-        else
-            set_charinfo_name(co, NULL);
-        /* n_string_field leaves a value on stack*/
-        lua_pop(L,1);
-        u = n_some_field(L,lua_key_index(tounicode));
-        if (u == LUA_TNUMBER) {
-            u = lua_tointeger(L,-1);
-            if (u < 0) {
-                set_charinfo_tounicode(co, NULL);
-            } else if (u < 0xD7FF || (u > 0xDFFF && u <= 0xFFFF)) {
-                char *s = malloc(5);
-                sprintf(s,"%04X",(unsigned int) u);
-                set_charinfo_tounicode(co,s);
-            } else {
-                char *s = malloc(9);
-                u = u - 0x10000;
-                sprintf(s,"%04X%04X",(unsigned int) (floor(u/1024)+0xD800),(unsigned int) (u%1024+0xDC00));
-                set_charinfo_tounicode(co,s);
-            }
-        } else if (u == LUA_TTABLE) {
-            n = lua_rawlen(L,-1);
-            u = 0;
-            for (k = 1; k <= n; k++) {
-                lua_rawgeti(L, -1, k);
-                if (lua_type(L,-1) == LUA_TNUMBER) {
-                    u = lua_tointeger(L,-1);
-                } else {
-                    lua_pop(L, 1);
-                    break;
-                }
-                if (u < 0) {
-                    u = -1;
-                    lua_pop(L, 1);
-                    break;
-                } else if (u < 0xD7FF || (u > 0xDFFF && u <= 0xFFFF)) {
-                    u = u + 4;
-                } else {
-                    u = u + 8;
-                }
-                lua_pop(L, 1);
-            }
-            if (u>0) {
-                char *s = malloc(u+1);
-                char *t = s ;
-                for (k = 1; k <= n; k++) {
-                    lua_rawgeti(L, -1, k);
-                    u = lua_tointeger(L,-1);
-                    if (u < 0xD7FF || (u > 0xDFFF && u <= 0xFFFF)) {
-                        sprintf(t,"%04X",(unsigned int) u);
-                        t += 4;
-                    } else {
-                        u = u - 0x10000;
-                        sprintf(t,"%04X%04X",(unsigned int) (floor(u/1024)+0xD800),(unsigned int) (u%1024+0xDC00));
-                        t += 8;
-                    }
-                    lua_pop(L, 1);
-                }
-                set_charinfo_tounicode(co,s);
-            } else {
-                set_charinfo_tounicode(co, NULL);
-            }
-        } else if (u == LUA_TSTRING) {
-            s = lua_tostring(L,-1);
-            set_charinfo_tounicode(co, xstrdup(s));
-        } else {
-            set_charinfo_tounicode(co, NULL);
-        }
-        /* ... leaves a value on stack*/
-        lua_pop(L,1);
-
-        if (has_math) {
-            j = lua_numeric_field_by_index(L, lua_key_index(top_accent), INT_MIN);
-            set_charinfo_top_accent(co, j);
-            j = lua_numeric_field_by_index(L, lua_key_index(bot_accent), INT_MIN);
-            set_charinfo_bot_accent(co, j);
-            k = lua_numeric_field_by_index(L, lua_key_index(next), -1);
-            if (k >= 0) {
-                set_charinfo_tag(co, list_tag);
-                set_charinfo_remainder(co, k);
-            }
-
-            lua_key_rawgeti(extensible);
-            if (lua_istable(L, -1)) {
-                int top, bot, mid, rep;
-                top = lua_numeric_field_by_index(L, lua_key_index(top), 0);
-                bot = lua_numeric_field_by_index(L, lua_key_index(bot), 0);
-                mid = lua_numeric_field_by_index(L, lua_key_index(mid), 0);
-                rep = lua_numeric_field_by_index(L, lua_key_index(rep), 0);
-                if (top != 0 || bot != 0 || mid != 0 || rep != 0) {
-                    set_charinfo_tag(co, ext_tag);
-                    set_charinfo_extensible(co, top, bot, mid, rep);
-                } else {
-                    formatted_warning("font", "lua-loaded font %s char U+%X has an invalid extensible field",
-                        font_name(f), (int) i);
-                }
-            }
-            lua_pop(L, 1);
-
-            lua_key_rawgeti(horiz_variants);
-            if (lua_istable(L, -1)) {
-                int glyph, startconnect, endconnect, advance, extender;
-                extinfo *h;
-                set_charinfo_tag(co, ext_tag);
-                set_charinfo_hor_variants(co, NULL);
-                for (k = 1;; k++) {
-                    lua_rawgeti(L, -1, k);
-                    if (lua_istable(L, -1)) {
-                        glyph = lua_numeric_field_by_index(L, lua_key_index(glyph), 0);
-                        extender = lua_numeric_field_by_index(L, lua_key_index(extender), 0);
-                        startconnect = lua_numeric_field_by_index(L, lua_key_index(start), 0);
-                        endconnect = lua_numeric_field_by_index(L, lua_key_index(end), 0);
-                        advance = lua_numeric_field_by_index(L, lua_key_index(advance), 0);
-                        h = new_variant(glyph, startconnect, endconnect, advance, extender);
-                        add_charinfo_hor_variant(co, h);
-                        lua_pop(L, 1);
-                    } else {
-                        lua_pop(L, 1);
-                        break;
-                    }
-                }
-            }
-            lua_pop(L, 1);
-
-            lua_key_rawgeti(vert_variants);
-            if (lua_istable(L, -1)) {
-                int glyph, startconnect, endconnect, advance, extender;
-                extinfo *h;
-                set_charinfo_tag(co, ext_tag);
-                set_charinfo_vert_variants(co, NULL);
-                for (k = 1;; k++) {
-                    lua_rawgeti(L, -1, k);
-                    if (lua_istable(L, -1)) {
-                        glyph = lua_numeric_field_by_index(L, lua_key_index(glyph), 0);
-                        extender = lua_numeric_field_by_index(L, lua_key_index(extender), 0);
-                        startconnect = lua_numeric_field_by_index(L, lua_key_index(start), 0);
-                        endconnect = lua_numeric_field_by_index(L, lua_key_index(end), 0);
-                        advance = lua_numeric_field_by_index(L, lua_key_index(advance), 0);
-                        h = new_variant(glyph, startconnect, endconnect, advance, extender);
-                        add_charinfo_vert_variant(co, h);
-                        lua_pop(L, 1);
-                    } else {
-                        lua_pop(L, 1);
-                        break;
-                    }
-                }
-            }
-            lua_pop(L, 1);
-
-            /*
-                Here is a complete example:
-
-                |mathkern = {|
-                    | bottom_left  = { { height = 420, kern = 80  }, { height = 520, kern = 4   } },|
-                    | bottom_right = { { height = 0,   kern = 48  } },|
-                    | top_left     = { { height = 620, kern = 0   }, { height = 720, kern = -80 } },|
-                    | top_right    = { { height = 676, kern = 115 }, { height = 776, kern = 45  } },|
-                |}|
-
-            */
-
-            lua_key_rawgeti(mathkern);
-            if (lua_istable(L, -1)) {
-                store_math_kerns(L,lua_key_index(top_left), co, top_left_kern);
-                store_math_kerns(L,lua_key_index(top_right), co, top_right_kern);
-                store_math_kerns(L,lua_key_index(bottom_right), co, bottom_right_kern);
-                store_math_kerns(L,lua_key_index(bottom_left), co, bottom_left_kern);
-            }
-            lua_pop(L, 1);
-        }
-        /* end of |has_math| */
-        count_hash_items(L, kerns, nk);
-        if (nk > 0) {
-            /* kerns table still on stack */
-            ckerns = xcalloc((unsigned) (nk + 1), sizeof(kerninfo));
-            ctr = 0;
-            lua_pushnil(L); /* traverse hash */
-            while (lua_next(L, -2) != 0) {
-                k = non_boundarychar;
-                lt = lua_type(L,-2);
-                if (lt == LUA_TNUMBER) {
-                    k = (int) lua_tointeger(L, -2); /* adjacent char */
-                    if (k < 0)
-                        k = non_boundarychar;
-                } else if (lt == LUA_TSTRING) {
-                    s = lua_tostring(L, -2);
-                    if (lua_key_eq(s, right_boundary)) {
-                        k = right_boundarychar;
-                        if (!has_right_boundary(f))
-                            set_right_boundary(f, get_charinfo(f, right_boundarychar));
-                    }
-                }
-                j = lua_roundnumber(L, -1); /* movement */
-                if (k != non_boundarychar) {
-                    set_kern_item(ckerns[ctr], k, j);
-                    ctr++;
-                } else {
-                    formatted_warning("font", "lua-loaded font %s char U+%X has an invalid kern field",
-                        font_name(f), (int) i);
-                }
-                lua_pop(L, 1);
-            }
-            /* guard against empty tables */
-            if (ctr > 0) {
-                set_kern_item(ckerns[ctr], end_kern, 0);
-                set_charinfo_kerns(co, ckerns);
-            } else {
-                formatted_warning("font", "lua-loaded font %s char U+%X has an invalid kerns field",
-                    font_name(f), (int) i);
-            }
-            lua_pop(L, 1);
-        }
-
-        /* packet commands */
-        lua_key_rawgeti(commands);
-        if (lua_istable(L, -1)) {
-            lua_pushnil(L);     /* first key */
-            if (lua_next(L, -2) != 0) {
-                lua_pop(L, 2);
-                read_char_packets(L, (int *) l_fonts, co, f, atsize);
-            }
-        }
-        lua_pop(L, 1);
-
-        /* ligatures */
-        count_hash_items(L, ligatures, nl);
-        if (nl > 0) {
-            /* ligatures table still on stack */
-            cligs = xcalloc((unsigned) (nl + 1), sizeof(liginfo));
-            ctr = 0;
-            lua_pushnil(L); /* traverse hash */
-            while (lua_next(L, -2) != 0) {
-                k = non_boundarychar;
-                lt = lua_type(L,-2);
-                if (lt == LUA_TNUMBER) {
-                    k = (int) lua_tointeger(L, -2); /* adjacent char */
-                    if (k < 0) {
-                        k = non_boundarychar;
-                    }
-                } else if (lt == LUA_TSTRING) {
-                    s = lua_tostring(L, -2);
-                    if (lua_key_eq(s, right_boundary)) {
-                        k = right_boundarychar;
-                        if (!has_right_boundary(f))
-                            set_right_boundary(f, get_charinfo(f, right_boundarychar));
-                    }
-                }
-                r = -1;
-                if (lua_istable(L, -1)) {
-                    r = lua_numeric_field_by_index(L, lua_key_index(char), -1);    /* ligature */
-                }
-                if (r != -1 && k != non_boundarychar) {
-                    t = n_enum_field(L, lua_key_index(type), 0, ligature_type_strings);
-                    set_ligature_item(cligs[ctr], (char) ((t * 2) + 1), k, r);
-                    ctr++;
-                } else {
-                    formatted_warning("font", "lua-loaded font %s char U+%X has an invalid ligature field",
-                        font_name(f), (int) i);
-                }
-                lua_pop(L, 1);      /* iterator value */
-            }
-            /* guard against empty tables */
-            if (ctr > 0) {
-                set_ligature_item(cligs[ctr], 0, end_ligature, 0);
-                set_charinfo_ligatures(co, cligs);
-            } else {
-                formatted_warning("font", "lua-loaded font %s char U+%X has an invalid ligatures field",
-                    font_name(f), (int) i);
-            }
-            lua_pop(L, 1);      /* ligatures table */
-        }
-    }
-}
-
-@ The caller has to fix the state of the lua stack when there is an error!
-
- at c
-int font_from_lua(lua_State * L, int f)
-{
-    int i, n, r, t, lt;
-    int s_top;                  /* lua stack top */
-    int bc;                     /* first char index */
-    int ec;                     /* last char index */
-    char *s;
-    const char *ss;
-    int *l_fonts = NULL;
-    int save_ref ;
-    boolean no_math = false;
-
-    /* will we save a cache of the luat table? */
-
-    save_ref = 1; /* we start with  ss = "yes" */
-    ss = NULL;
-    ss = n_string_field(L, lua_key_index(cache));
-    if (lua_key_eq(ss, no))
-        save_ref = -1;
-    else if (lua_key_eq(ss, renew))
-        save_ref = 0;
-    /* n_string_field leaves a value on stack*/
-    lua_pop(L,1);
-
-    /* the table is at stack index -1 */
-
-    s = n_string_field_copy(L,lua_key_index(area), "");
-    set_font_area(f, s);
-    s = n_string_field_copy(L, lua_key_index(filename), NULL);
-    set_font_filename(f, s);
-    s = n_string_field_copy(L, lua_key_index(encodingname), NULL);
-    set_font_encodingname(f, s);
-
-    s = n_string_field_copy(L, lua_key_index(name), NULL);
-    set_font_name(f, s);
-    s = n_string_field_copy(L, lua_key_index(fullname), font_name(f));
-    set_font_fullname(f, s);
-
-    if (s == NULL) {
-        formatted_error("font","lua-loaded font '%d' has no name!", f);
-        return false;
-    }
-    s = n_string_field_copy(L, lua_key_index(psname), NULL);
-    set_font_psname(f, s);
-
-    i = lua_numeric_field_by_index(L,lua_key_index(units_per_em), 0);
-    set_font_units_per_em(f, i);
-    i = lua_numeric_field_by_index(L,lua_key_index(designsize), 655360);
-    set_font_dsize(f, i);
-    i = lua_numeric_field_by_index(L,lua_key_index(size), font_dsize(f));
-    set_font_size(f, i);
-    set_font_checksum(f, (unsigned)(lua_unsigned_numeric_field_by_index(L,lua_key_index(checksum), 0))) ;
-    i = lua_numeric_field_by_index(L,lua_key_index(direction), 0);
-    set_font_natural_dir(f, i);
-    i = lua_numeric_field_by_index(L,lua_key_index(encodingbytes), 0);
-    set_font_encodingbytes(f, (char) i);
-    i = n_boolean_field(L,lua_key_index(oldmath), 0);
-    set_font_oldmath(f, i);
-    i = lua_numeric_field_by_index(L,lua_key_index(tounicode), 0);
-    set_font_tounicode(f, (char) i);
-
-    i = lua_numeric_field_by_index(L,lua_key_index(extend), 1000);
-    if (i < FONT_EXTEND_MIN)
-        i = FONT_EXTEND_MIN;
-    if (i > FONT_EXTEND_MAX)
-        i = FONT_EXTEND_MAX;
-    set_font_extend(f, i);
-    i = lua_numeric_field_by_index(L,lua_key_index(slant), 0);
-    if (i < FONT_SLANT_MIN)
-        i = FONT_SLANT_MIN;
-    if (i > FONT_SLANT_MAX)
-        i = FONT_SLANT_MAX;
-    set_font_slant(f, i);
-
-    i = lua_numeric_field_by_index(L,lua_key_index(hyphenchar), default_hyphen_char_par);
-    set_hyphen_char(f, i);
-    i = lua_numeric_field_by_index(L,lua_key_index(skewchar), default_skew_char_par);
-    set_skew_char(f, i);
-    i = n_boolean_field(L, lua_key_index(used), 0);
-    set_font_used(f, (char) i);
-
-    s = n_string_field_copy(L, lua_key_index(attributes), NULL);
-    if (s != NULL && strlen(s) > 0) {
-        i = maketexstring(s);
-        set_pdf_font_attr(f, i);
-    }
-    free(s);
-
-    i = n_enum_field(L, lua_key_index(type), unknown_font_type, font_type_strings);
-    set_font_type(f, i);
-    i = n_enum_field(L, lua_key_index(format), unknown_format, font_format_strings);
-    set_font_format(f, i);
-    i = n_enum_field(L, lua_key_index(writingmode), unknown_writingmode, font_writingmode_strings);
-    set_font_writingmode(f, i);
-    i = n_enum_field(L, lua_key_index(identity), unknown_identity, font_identity_strings);
-    set_font_identity(f, i);
-    i = n_enum_field(L, lua_key_index(embedding), unknown_embedding, font_embedding_strings);
-    set_font_embedding(f, i);
-    if (font_encodingbytes(f) == 0 && (font_format(f) == opentype_format || font_format(f) == truetype_format)) {
-        set_font_encodingbytes(f, 2);
-    }
-
-    /* now fetch the base fonts, if needed */
-    count_hash_items(L, fonts, n);
-    if (n > 0) {
-        /* font table still on stack */
-        l_fonts = xmalloc((unsigned) ((unsigned) (n + 2) * sizeof(int)));
-        memset(l_fonts, 0, (size_t) ((unsigned) (n + 2) * sizeof(int)));
-        for (i = 1; i <= n; i++) {
-            lua_rawgeti(L, -1, i);
-            if (lua_istable(L, -1)) {
-                lua_key_rawgeti(id);
-                if (lua_isnumber(L, -1)) {
-                    l_fonts[i] = (int) lua_tointeger(L, -1);
-                    if (l_fonts[i] == 0) {
-                        l_fonts[i] = (int) f;
-                    }
-                    lua_pop(L, 2); /* pop id and entry */
-                    continue;
-                }
-                lua_pop(L, 1); /* pop id */
-            };
-            ss = NULL;
-            if (lua_istable(L, -1)) {
-                ss = n_string_field(L, lua_key_index(name));
-                /* string is anchored */
-                lua_pop(L,1);
-            }
-            if (ss != NULL) {
-                t = lua_numeric_field_by_index(L, lua_key_index(size), -1000);
-                /* TODO: the stack is messed up, otherwise this explicit resizing would not be needed */
-                s_top = lua_gettop(L);
-                if (strcmp(font_name(f), ss) == 0)
-                    l_fonts[i] = f;
-                else
-                    l_fonts[i] = find_font_id(ss, t);
-                lua_settop(L, s_top);
-            } else {
-                formatted_error("font","invalid local font in lua-loaded font '%s'", font_name(f));
-            }
-            lua_pop(L, 1); /* pop list entry */
-        }
-        lua_pop(L, 1); /* pop font table */
-    } else if (font_type(f) == virtual_font_type) {
-        formatted_error("font","invalid local fonts in lua-loaded font '%s'", font_name(f));
-    } else {
-        l_fonts = xmalloc(3 * sizeof(int));
-        l_fonts[0] = 0;
-        l_fonts[1] = f;
-        l_fonts[2] = 0;
-    }
-
-    /* parameters */
-    no_math = n_boolean_field(L, lua_key_index(nomath), 0);
-    read_lua_parameters(L, f);
-    if (!no_math) {
-        read_lua_math_parameters(L, f);
-        if (n_boolean_field(L, lua_key_index(oldmath), 0)) {
-            set_font_oldmath(f,true);
-        }
-
-    } else {
-        set_font_oldmath(f,true);
-    }
-    read_lua_cidinfo(L, f);
-
-    /* characters */
-    lua_key_rawgeti(characters);
-    if (lua_istable(L, -1)) {
-        /* find the array size values */
-        int num = 0;            /* number of charinfo's to add */
-        ec = 0;
-        bc = -1;
-        lua_pushnil(L);         /* first key */
-        while (lua_next(L, -2) != 0) {
-            if (lua_isnumber(L, -2)) {
-                i = (int) lua_tointeger(L, -2);
-                if (i >= 0) {
-                    if (lua_istable(L, -1)) {
-                        num++;
-                        if (i > ec)
-                            ec = i;
-                        if (bc < 0)
-                            bc = i;
-                        if (bc >= 0 && i < bc)
-                            bc = i;
-                    }
-                }
-            }
-            lua_pop(L, 1);
-        }
-        if (bc != -1) {
-            font_malloc_charinfo(f, num);
-            set_font_bc(f, bc);
-            set_font_ec(f, ec);
-            lua_pushnil(L);     /* first key */
-            while (lua_next(L, -2) != 0) {
-                lt = lua_type(L,-2);
-                if (lt == LUA_TNUMBER) {
-                    i = (int) lua_tointeger(L, -2);
-                    if (i >= 0) {
-                        font_char_from_lua(L, f, i, l_fonts, !no_math);
-                    }
-                } else if (lt == LUA_TSTRING) {
-                    const char *ss1 = lua_tostring(L, -2);
-                    if (lua_key_eq(ss1, left_boundary)) {
-                        font_char_from_lua(L, f, left_boundarychar, l_fonts,
-                                           !no_math);
-                    } else if (lua_key_eq(ss1, right_boundary)) {
-                        font_char_from_lua(L, f, right_boundarychar, l_fonts,
-                                           !no_math);
-                    }
-                }
-                lua_pop(L, 1);
-            }
-            lua_pop(L, 1);
-
-            /*
-                Handle font expansion last: the |copy_font| routine is called eventually,
-                and that needs to know |bc| and |ec|.
-            */
-
-            if (font_type(f) != virtual_font_type) {
-                int fstep = lua_numeric_field_by_index(L, lua_key_index(step), 0);
-                if (fstep < 0)
-                    fstep = 0;
-                if (fstep > 100)
-                    fstep = 100;
-                if (fstep != 0) {
-                    int fshrink = lua_numeric_field_by_index(L, lua_key_index(shrink), 0);
-                    int fstretch =lua_numeric_field_by_index(L, lua_key_index(stretch), 0);
-                    int fexpand = n_boolean_field(L, lua_key_index(auto_expand), 0);
-                    if (fshrink < 0)
-                        fshrink = 0;
-                    if (fshrink > 500)
-                        fshrink = 500;
-                    fshrink -= (fshrink % fstep);
-                    if (fshrink < 0)
-                        fshrink = 0;
-                    if (fstretch < 0)
-                        fstretch = 0;
-                    if (fstretch > 1000)
-                        fstretch = 1000;
-                    fstretch -= (fstretch % fstep);
-                    if (fstretch < 0)
-                        fstretch = 0;
-                    set_expand_params(f, fexpand, fstretch, fshrink, fstep);
-                }
-            }
-
-        } else {
-            /* jikes, no characters */
-            formatted_warning("font","lua-loaded font '%d' with name '%s' has no characters", f, font_name(f));
-        }
-
-        if (save_ref > 0) {
-            r = luaL_ref(L, LUA_REGISTRYINDEX); /* pops the table */
-            set_font_cache_id(f, r);
-        } else {
-            lua_pop(L, 1);
-            set_font_cache_id(f, save_ref);
-        }
-    } else {
-        /* jikes, no characters */
-        formatted_warning("font","lua-loaded font '%d' with name '%s' has no character table", f, font_name(f));
-    }
-    if (l_fonts != NULL)
-        free(l_fonts);
-    return true;
-}
-
-@* Ligaturing.
-
- at c
-static void nesting_append(halfword nest1, halfword newn)
-{
-    halfword tail = tlink(nest1);
-    if (tail == null) {
-        couple_nodes(nest1, newn);
-    } else {
-        couple_nodes(tail, newn);
-    }
-    tlink(nest1) = newn;
-}
-
-static void nesting_prepend(halfword nest1, halfword newn)
-{
-    halfword head = vlink(nest1);
-    couple_nodes(nest1, newn);
-    if (head == null) {
-        tlink(nest1) = newn;
-    } else {
-        couple_nodes(newn, head);
-    }
-}
-
-static void nesting_prepend_list(halfword nest1, halfword newn)
-{
-    halfword head = vlink(nest1);
-    couple_nodes(nest1, newn);
-    if (head == null) {
-        tlink(nest1) = tail_of_list(newn);
-    } else {
-        halfword tail = tail_of_list(newn);
-        couple_nodes(tail, head);
-    }
-}
-
-static int test_ligature(liginfo * lig, halfword left, halfword right)
-{
-    if (type(left) != glyph_node)
-        return 0;
-    if (font(left) != font(right))
-        return 0;
-    if (is_ghost(left) || is_ghost(right))
-        return 0;
-    *lig = get_ligature(font(left), character(left), character(right));
-    if (is_valid_ligature(*lig)) {
-        return 1;
-    }
-    return 0;
-}
-
-static int try_ligature(halfword * frst, halfword fwd)
-{
-    halfword cur = *frst;
-    liginfo lig;
-    if (test_ligature(&lig, cur, fwd)) {
-        int move_after = (lig_type(lig) & 0x0C) >> 2;
-        int keep_right = ((lig_type(lig) & 0x01) != 0);
-        int keep_left = ((lig_type(lig) & 0x02) != 0);
-        halfword newgl = raw_glyph_node();
-        font(newgl) = font(cur);
-        character(newgl) = lig_replacement(lig);
-        set_is_ligature(newgl);
-        /*
-            below might not be correct in contrived border case.
-            but we use it only for debugging, so ...
-        */
-        if (character(cur) < 0) {
-            set_is_leftboundary(newgl);
-        }
-        if (character(fwd) < 0) {
-            set_is_rightboundary(newgl);
-        }
-        if (character(cur) < 0) {
-            if (character(fwd) < 0) {
-                build_attribute_list(newgl);
-            } else {
-                add_node_attr_ref(node_attr(fwd));
-                node_attr(newgl) = node_attr(fwd);
-            }
-        } else {
-            add_node_attr_ref(node_attr(cur));
-            node_attr(newgl) = node_attr(cur);
-        }
-
-        /*
-            TODO/FIXME if this ligature is consists of another ligature
-            we should add it's |lig_ptr| to the new glyphs |lig_ptr| (and
-            cleanup the no longer needed node) LOW PRIORITY
-        */
-        /* left side */
-        if (keep_left) {
-            halfword new_first = copy_node(cur);
-            lig_ptr(newgl) = new_first;
-            couple_nodes(cur, newgl);
-            if (move_after) {
-                move_after--;
-                cur = newgl;
-            }
-        } else {
-            halfword prev = alink(cur);
-            uncouple_node(cur);
-            lig_ptr(newgl) = cur;
-            couple_nodes(prev, newgl);
-            cur = newgl;        /* as cur has disappeared */
-        }
-        /* right side */
-        if (keep_right) {
-            halfword new_second = copy_node(fwd);
-            /* correct, because we {\it know\/} |lig_ptr| points to {\it one\/} node */
-            couple_nodes(lig_ptr(newgl), new_second);
-            couple_nodes(newgl, fwd);
-            if (move_after) {
-                move_after--;
-                cur = fwd;
-            }
-        } else {
-            halfword next = vlink(fwd);
-            uncouple_node(fwd);
-            /* correct, because we {\it know\/} |lig_ptr| points to {\it one\/} node */
-            couple_nodes(lig_ptr(newgl), fwd);
-            if (next != null) {
-                couple_nodes(newgl, next);
-            }
-        }
-        /* check and return */
-        *frst = cur;
-        return 1;
-    }
-    return 0;
-}
-
-@ there shouldn't be any ligatures here - we only add them at the end of
- |xxx_break| in a \.{DISC-1 - DISC-2} situation and we stop processing \.{DISC-1}
- (we continue with \.{DISC-1}'s |post_| and |no_break|.
-
- at c
-static halfword handle_lig_nest(halfword root, halfword cur)
-{
-    if (cur == null)
-        return root;
-    while (vlink(cur) != null) {
-        halfword fwd = vlink(cur);
-        if (type(cur) == glyph_node && type(fwd) == glyph_node &&
-                font(cur) == font(fwd) && try_ligature(&cur, fwd)) {
-            continue;
-        }
-        cur = vlink(cur);
-    }
-    tlink(root) = cur;
-    return root;
-}
-
-static halfword handle_lig_word(halfword cur)
-{
-    halfword right = null;
-    if (type(cur) == boundary_node) {
-        halfword prev = alink(cur);
-        halfword fwd = vlink(cur);
-        /* no need to uncouple |cur|, it is freed */
-        flush_node(cur);
-        if (fwd == null) {
-            vlink(prev) = fwd;
-            return prev;
-        }
-        couple_nodes(prev, fwd);
-        if (type(fwd) != glyph_node)
-            return prev;
-        cur = fwd;
-    } else if (has_left_boundary(font(cur))) {
-        halfword prev = alink(cur);
-        halfword p = new_glyph(font(cur), left_boundarychar);
-        couple_nodes(prev, p);
-        couple_nodes(p, cur);
-        cur = p;
-    }
-    if (has_right_boundary(font(cur))) {
-        right = new_glyph(font(cur), right_boundarychar);
-    }
-    while (1) {
-        /* A glyph followed by ... */
-        if (type(cur) == glyph_node) {
-            halfword fwd = vlink(cur);
-            if (fwd == null) {  /* last character of paragraph */
-                if (right == null)
-                    break;
-                /* \.{--\\par} prohibits use of |couple_nodes| here */
-                try_couple_nodes(cur, right);
-                right = null;
-                continue;
-            }
-            if (type(fwd) == glyph_node) {      /* |GLYPH - GLYPH| */
-                if (font(cur) != font(fwd))
-                    break;
-                if (try_ligature(&cur, fwd))
-                    continue;
-            } else if (type(fwd) == disc_node) {        /* |GLYPH - DISC| */
-
-                /* if  \.{a{bx}{}{y}} and \.{a+b=>B} convert to \.{{Bx}{}{ay}} */
-                halfword pre = vlink_pre_break(fwd);
-                halfword nob = vlink_no_break(fwd);
-                halfword next, tail;
-                liginfo lig;
-                /* Check on: a{b?}{?}{?} and a+b=>B : {B?}{?}{a?} */
-                /* Check on: a{?}{?}{b?} and a+b=>B : {a?}{?}{B?} */
-                if ((pre != null && type(pre) == glyph_node
-                     && test_ligature(&lig, cur, pre))
-                    || (nob != null && type(nob) == glyph_node
-                        && test_ligature(&lig, cur, nob))) {
-                    /* move cur from before disc, to skipped part */
-                    halfword prev = alink(cur);
-                    uncouple_node(cur);
-                    couple_nodes(prev, fwd);
-                    nesting_prepend(no_break(fwd), cur);
-                    /* now ligature the |pre_break| */
-                    nesting_prepend(pre_break(fwd), copy_node(cur));
-                    /* As we have removed cur, we need to start again ... */
-                    cur = prev;
-                }
-                /* Check on: a{?}{?}{}b and a+b=>B : {a?}{?b}{B} */
-                next = vlink(fwd);
-                if (nob == null && next != null && type(next) == glyph_node
-                    && test_ligature(&lig, cur, next)) {
-                    /* move |cur| from before |disc| to |no_break| part */
-                    halfword prev = alink(cur);
-                    uncouple_node(cur);
-                    couple_nodes(prev, fwd);
-                    couple_nodes(no_break(fwd), cur);   /* we {\it know\/} it's empty */
-                    /* now copy cur the |pre_break| */
-                    nesting_prepend(pre_break(fwd), copy_node(cur));
-                    /* move next from after disc to |no_break| part */
-                    tail = vlink(next);
-                    uncouple_node(next);
-                    try_couple_nodes(fwd, tail);
-                    couple_nodes(cur, next);    /* we {\it know\/} this works */
-                    tlink(no_break(fwd)) = next;        /* and make sure the list is correct */
-                    /* now copy next to the |post_break| */
-                    nesting_append(post_break(fwd), copy_node(next));
-                    /* As we have removed cur, we need to start again ... */
-                    cur = prev;
-                }
-                /* we are finished with the |pre_break| */
-                handle_lig_nest(pre_break(fwd), vlink_pre_break(fwd));
-            } else if (type(fwd) == boundary_node) {
-                halfword next = vlink(fwd);
-                try_couple_nodes(cur, next);
-                flush_node(fwd);
-                if (right != null) {
-                    flush_node(right);  /* Shame, didn't need it */
-                    /* no need to reset |right|, we're going to leave the loop anyway */
-                }
-                break;
-            } else {
-                /* fwd is something unknown */
-                if (right == null)
-                    break;
-                couple_nodes(cur, right);
-                couple_nodes(right, fwd);
-                right = null;
-                continue;
-            }
-            /* A discretionary followed by ... */
-        } else if (type(cur) == disc_node) {
-            /* If \.{{?}{x}{?}} or \.{{?}{?}{y}} then ... */
-            if (vlink_no_break(cur) != null || vlink_post_break(cur) != null) {
-                halfword prev = 0;
-                halfword fwd;
-                liginfo lig;
-                if (subtype(cur) == select_disc) {
-                    prev = alink(cur);
-                    if (vlink_post_break(cur) != null)
-                        handle_lig_nest(post_break(prev), vlink_post_break(prev));
-                    if (vlink_no_break(cur) != null)
-                        handle_lig_nest(no_break(prev), vlink_no_break(prev));
-                }
-                if (vlink_post_break(cur) != null)
-                    handle_lig_nest(post_break(cur), vlink_post_break(cur));
-                if (vlink_no_break(cur) != null)
-                    handle_lig_nest(no_break(cur), vlink_no_break(cur));
-                while ((fwd = vlink(cur)) != null) {
-                    halfword nob, pst, next;
-                    if (type(fwd) != glyph_node)
-                        break;
-                    if (subtype(cur) != select_disc) {
-                        nob = tlink_no_break(cur);
-                        pst = tlink_post_break(cur);
-                        if ((nob == null || !test_ligature(&lig, nob, fwd)) &&
-                            (pst == null || !test_ligature(&lig, pst, fwd)))
-                            break;
-                        nesting_append(no_break(cur), copy_node(fwd));
-                        handle_lig_nest(no_break(cur), nob);
-                    } else {
-                        int dobreak = 0;
-                        nob = tlink_no_break(prev);
-                        pst = tlink_post_break(prev);
-                        if ((nob == null || !test_ligature(&lig, nob, fwd)) &&
-                            (pst == null || !test_ligature(&lig, pst, fwd)))
-                            dobreak = 1;
-                        if (!dobreak) {
-                            nesting_append(no_break(prev), copy_node(fwd));
-                            handle_lig_nest(no_break(prev), nob);
-                            nesting_append(post_break(prev), copy_node(fwd));
-                            handle_lig_nest(post_break(prev), pst);
-                        }
-                        dobreak = 0;
-                        nob = tlink_no_break(cur);
-                        pst = tlink_post_break(cur);
-                        if ((nob == null || !test_ligature(&lig, nob, fwd)) &&
-                            (pst == null || !test_ligature(&lig, pst, fwd)))
-                            dobreak = 1;
-                        if (!dobreak) {
-                            nesting_append(no_break(cur), copy_node(fwd));
-                            handle_lig_nest(no_break(cur), nob);
-                        }
-                        if (dobreak)
-                            break;
-                    }
-                    next = vlink(fwd);
-                    uncouple_node(fwd);
-                    try_couple_nodes(cur, next);
-                    nesting_append(post_break(cur), fwd);
-                    handle_lig_nest(post_break(cur), pst);
-                }
-                if (fwd != null && type(fwd) == disc_node) {
-                        halfword next = vlink(fwd);
-                        if (vlink_no_break(fwd) == null
-                        && vlink_post_break(fwd) == null
-                        && next != null
-                        && type(next) == glyph_node
-                        && ((tlink_post_break(cur) != null && test_ligature(&lig, tlink_post_break(cur), next)) ||
-                            (tlink_no_break  (cur) != null && test_ligature(&lig, tlink_no_break  (cur), next)))) {
-                        /* Building an |init_disc| followed by a |select_disc|
-                          \.{{a-}{b}{AB} {-}{}{}} 'c'
-                         */
-                        /* is it tail necessary ? */
-                        halfword last1 = vlink(next), tail ;
-                        uncouple_node(next);
-                        try_couple_nodes(fwd, last1);
-                        /* \.{{a-}{b}{AB} {-}{c}{}} */
-                        nesting_append(post_break(fwd), copy_node(next));
-                        /* \.{{a-}{b}{AB} {-}{c}{-}} */
-                        if (vlink_no_break(cur) != null) {
-                            nesting_prepend(no_break(fwd), copy_node(vlink_pre_break(fwd)));
-                        }
-                        /* \.{{a-}{b}{AB} {b-}{c}{-}} */
-                        if (vlink_post_break(cur) != null)
-                            nesting_prepend_list(pre_break(fwd), copy_node_list(vlink_post_break(cur)));
-                        /* \.{{a-}{b}{AB} {b-}{c}{AB-}} */
-                        if (vlink_no_break(cur) != null) {
-                            nesting_prepend_list(no_break(fwd), copy_node_list(vlink_no_break(cur)));
-                        }
-                        /* \.{{a-}{b}{ABC} {b-}{c}{AB-}} */
-                        tail = tlink_no_break(cur);
-                        nesting_append(no_break(cur), copy_node(next));
-                        handle_lig_nest(no_break(cur), tail);
-                        /* \.{{a-}{BC}{ABC} {b-}{c}{AB-}} */
-                        tail = tlink_post_break(cur);
-                        nesting_append(post_break(cur), next);
-                        handle_lig_nest(post_break(cur), tail);
-                        /* and set the subtypes */
-                        subtype(cur) = init_disc;
-                        subtype(fwd) = select_disc;
-                    }
-                }
-            }
-
-        } else {
-            /* NO GLYPH NOR DISC */
-            return cur;
-        }
-        /* step-to-next-node */
-        /* \.{--\\par} allows |vlink(cur)| to be null */
-        cur = vlink(cur);
-    }
-
-    return cur;
-}
-
-@ Return value is the new tail, head should be a dummy
-
- at c
-halfword handle_ligaturing(halfword head, halfword tail)
-{
-    halfword save_tail1 = null; /* trick to allow explicit |node==null| tests */
-    halfword cur, prev;
-
-    if (vlink(head) == null)
-        return tail;
-    if (tail != null) {
-        save_tail1 = vlink(tail);
-        vlink(tail) = null;
-    }
-
-    /* |if (fix_node_lists)| */
-    fix_node_list(head);
-
-    prev = head;
-    cur = vlink(prev);
-
-    while (cur != null) {
-        if (type(cur) == glyph_node || (type(cur) == boundary_node)) {
-            cur = handle_lig_word(cur);
-        }
-        prev = cur;
-        cur = vlink(cur);
-    }
-    if (prev == null) {
-        /* hh: looks bad to me */
-        prev = tail;
-    }
-
-    if (tail != null) {
-        try_couple_nodes(prev, save_tail1);
-    }
-    return prev;
-}
-
-
-@* Kerning.
-
- at c
-static void add_kern_before(halfword left, halfword right)
-{
-    if ((!is_rightghost(right)) &&
-        font(left) == font(right) && has_kern(font(left), character(left))) {
-        int k = raw_get_kern(font(left), character(left), character(right));
-        if (k != 0) {
-            halfword kern = new_kern(k);
-            halfword prev = alink(right);
-            couple_nodes(prev, kern);
-            couple_nodes(kern, right);
-            /* update the attribute list (inherit from left) */
-            delete_attribute_ref(node_attr(kern));
-            add_node_attr_ref(node_attr(left));
-            node_attr(kern) = node_attr(left);
-        }
-    }
-}
-
-static void add_kern_after(halfword left, halfword right, halfword aft)
-{
-    if ((!is_rightghost(right)) &&
-        font(left) == font(right) && has_kern(font(left), character(left))) {
-        int k = raw_get_kern(font(left), character(left), character(right));
-        if (k != 0) {
-            halfword kern = new_kern(k);
-            halfword next = vlink(aft);
-            couple_nodes(aft, kern);
-            try_couple_nodes(kern, next);
-            /* update the attribute list (inherit from left == aft) */
-            delete_attribute_ref(node_attr(kern));
-            add_node_attr_ref(node_attr(aft));
-            node_attr(kern) = node_attr(aft);
-        }
-    }
-}
-
-static void do_handle_kerning(halfword root, halfword init_left, halfword init_right)
-{
-    halfword cur = vlink(root);
-    halfword left = null;
-    if (cur == null) {
-        if (init_left != null && init_right != null) {
-            add_kern_after(init_left, init_right, root);
-            tlink(root) = vlink(root);
-        }
-        return;
-    }
-    if (type(cur) == glyph_node) {
-        set_is_glyph(cur);
-        if (init_left != null)
-            add_kern_before(init_left, cur);
-        left = cur;
-    }
-    while ((cur = vlink(cur)) != null) {
-        if (type(cur) == glyph_node) {
-            set_is_glyph(cur);
-            if (left != null) {
-                add_kern_before(left, cur);
-                if (character(left) < 0 || is_ghost(left)) {
-                    halfword prev = alink(left);
-                    couple_nodes(prev, cur);
-                    flush_node(left);
-                }
-            }
-            left = cur;
-        } else {
-            if (type(cur) == disc_node) {
-                halfword right = type(vlink(cur)) == glyph_node ? vlink(cur) : null;
-                do_handle_kerning(pre_break(cur), left, null);
-                if (vlink_pre_break(cur) != null)
-                    tlink_pre_break(cur) = tail_of_list(vlink_pre_break(cur));
-                do_handle_kerning(post_break(cur), null, right);
-                if (vlink_post_break(cur) != null)
-                    tlink_post_break(cur) = tail_of_list(vlink_post_break(cur));
-                do_handle_kerning(no_break(cur), left, right);
-                if (vlink_no_break(cur) != null)
-                    tlink_no_break(cur) = tail_of_list(vlink_no_break(cur));    /* needed? */
-            }
-            if (left != null) {
-                if (character(left) < 0 || is_ghost(left)) {
-                    halfword prev = alink(left);
-                    couple_nodes(prev, cur);
-                    flush_node(left);
-                }
-                left = null;
-            }
-        }
-    }
-    if (left != null) {
-        if (init_right != null)
-            add_kern_after(left, init_right, left);
-        if (character(left) < 0 || is_ghost(left)) {
-            halfword prev = alink(left);
-            halfword next = vlink(left);
-            if (next != null) {
-                couple_nodes(prev, next);
-                tlink(root) = next;
-            } else if (prev != root) {
-                vlink(prev) = null;
-                tlink(root) = prev;
-            } else {
-                vlink(root) = null;
-                tlink(root) = null;
-            }
-            flush_node(left);
-        }
-    }
-}
-
-/*
-halfword handle_kerning(halfword head, halfword tail)
-{
-    halfword save_link;
-    save_link = vlink(tail);
-    vlink(tail) = null;
-    tlink(head) = tail;
-    do_handle_kerning(head, null, null);
-    tail = tlink(head);
-    if (valid_node(save_link)) {
-        try_couple_nodes(tail, save_link);
-    }
-    return tail;
-}
-*/
-
-halfword handle_kerning(halfword head, halfword tail)
-{
-    halfword save_link = null;
-    if (tail == null) {
-        tlink(head) = null;
-        do_handle_kerning(head, null, null);
-    } else {
-        save_link = vlink(tail);
-        vlink(tail) = null;
-        tlink(head) = tail;
-        do_handle_kerning(head, null, null);
-        tail = tlink(head);
-        if (valid_node(save_link)) {
-            try_couple_nodes(tail, save_link);
-        }
-    }
-    return tail;
-}
-
-@* ligaturing and kerning : lua-interface.
-
- at c
-static halfword run_lua_ligkern_callback(halfword head, halfword tail, int callback_id)
-{
-    int i;
-    int top = lua_gettop(Luas);
-    if (!get_callback(Luas, callback_id)) {
-        lua_pop(Luas, 2);
-        return tail;
-    }
-    nodelist_to_lua(Luas, head);
-    nodelist_to_lua(Luas, tail);
-    if ((i=lua_pcall(Luas, 2, 0, 0)) != 0) {
-        luatex_error(Luas, (i == LUA_ERRRUN ? 0 : 1));
-        return tail;
-    }
-    fix_node_list(head);
-    lua_settop(Luas, top);
-    return tail;
-}
-
-halfword new_ligkern(halfword head, halfword tail)
-{
-    int callback_id = 0;
-    if (vlink(head) == null)
-        return tail;
-    callback_id = callback_defined(ligaturing_callback);
-    if (callback_id > 0) {
-        tail = run_lua_ligkern_callback(head, tail, callback_id);
-        if (tail == null)
-            tail = tail_of_list(head);
-    } else if (callback_id == 0) {
-        tail = handle_ligaturing(head, tail);
-    }
-    callback_id = callback_defined(kerning_callback);
-    if (callback_id > 0) {
-        tail = run_lua_ligkern_callback(head, tail, callback_id);
-        if (tail == null) {
-            tail = tail_of_list(head);
-        }
-    } else if (callback_id == 0) {
-        halfword nest1 = new_node(nesting_node, 1);
-        halfword cur = vlink(head);
-        halfword aft = vlink(tail);
-        couple_nodes(nest1, cur);
-        tlink(nest1) = tail;
-        vlink(tail) = null;
-        do_handle_kerning(nest1, null, null);
-        couple_nodes(head, vlink(nest1));
-        tail = tlink(nest1);
-        try_couple_nodes(tail, aft);
-        flush_node(nest1);
-    }
-    return tail;
-}
+% luafont.w
+%
+% Copyright 2006-2011 Taco Hoekwater <taco@@luatex.org>
+%
+% 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 <http://www.gnu.org/licenses/>.
+
+@ @c
+#include "ptexlib.h"
+#include "lua/luatex-api.h"
+
+#define noVERBOSE
+
+/* todo: also keys */
+
+const char *font_type_strings[] = {
+    "unknown", "virtual", "real", NULL
+};
+
+const char *font_writingmode_strings[] = {
+    "unknown", "horizontal", "vertical", NULL
+};
+
+const char *font_identity_strings[] = {
+    "unknown", "horizontal", "vertical", NULL
+};
+
+const char *font_format_strings[] = {
+    "unknown", "type1", "type3", "truetype", "opentype", NULL
+};
+
+const char *font_embedding_strings[] = {
+    "unknown", "no", "subset", "full", NULL
+};
+
+const char *ligature_type_strings[] = {
+    "=:", "=:|", "|=:", "|=:|", "", "=:|>", "|=:>", "|=:|>", "", "", "", "|=:|>>", NULL
+};
+
+const char *MATH_param_names[] = {
+    "nil",
+    "ScriptPercentScaleDown",
+    "ScriptScriptPercentScaleDown",
+    "DelimitedSubFormulaMinHeight",
+    "DisplayOperatorMinHeight",
+    "MathLeading",
+    "AxisHeight",
+    "AccentBaseHeight",
+    "FlattenedAccentBaseHeight",
+    "SubscriptShiftDown",
+    "SubscriptTopMax",
+    "SubscriptBaselineDropMin",
+    "SuperscriptShiftUp",
+    "SuperscriptShiftUpCramped",
+    "SuperscriptBottomMin",
+    "SuperscriptBaselineDropMax",
+    "SubSuperscriptGapMin",
+    "SuperscriptBottomMaxWithSubscript",
+    "SpaceAfterScript",
+    "UpperLimitGapMin",
+    "UpperLimitBaselineRiseMin",
+    "LowerLimitGapMin",
+    "LowerLimitBaselineDropMin",
+    "StackTopShiftUp",
+    "StackTopDisplayStyleShiftUp",
+    "StackBottomShiftDown",
+    "StackBottomDisplayStyleShiftDown",
+    "StackGapMin",
+    "StackDisplayStyleGapMin",
+    "StretchStackTopShiftUp",
+    "StretchStackBottomShiftDown",
+    "StretchStackGapAboveMin",
+    "StretchStackGapBelowMin",
+    "FractionNumeratorShiftUp",
+    "FractionNumeratorDisplayStyleShiftUp",
+    "FractionDenominatorShiftDown",
+    "FractionDenominatorDisplayStyleShiftDown",
+    "FractionNumeratorGapMin",
+    "FractionNumeratorDisplayStyleGapMin",
+    "FractionRuleThickness",
+    "FractionDenominatorGapMin",
+    "FractionDenominatorDisplayStyleGapMin",
+    "SkewedFractionHorizontalGap",
+    "SkewedFractionVerticalGap",
+    "OverbarVerticalGap",
+    "OverbarRuleThickness",
+    "OverbarExtraAscender",
+    "UnderbarVerticalGap",
+    "UnderbarRuleThickness",
+    "UnderbarExtraDescender",
+    "RadicalVerticalGap",
+    "RadicalDisplayStyleVerticalGap",
+    "RadicalRuleThickness",
+    "RadicalExtraAscender",
+    "RadicalKernBeforeDegree",
+    "RadicalKernAfterDegree",
+    "RadicalDegreeBottomRaisePercent",
+    "MinConnectorOverlap",
+    "SubscriptShiftDownWithSuperscript",
+    "FractionDelimiterSize",
+    "FractionDelimiterDisplayStyleSize",
+    "NoLimitSubFactor",
+    "NoLimitSupFactor",
+    NULL,
+};
+
+/* here for now, may be useful elsewhere */
+
+int ff_checkoption (lua_State *L, int narg, const char *def, const char *const lst[]);
+
+int ff_checkoption (lua_State *L, int narg, const char *def, const char *const lst[]) {
+    const char *name = (def) ? luaL_optstring(L, narg, def) : luaL_checkstring(L, narg);
+    int i;
+    for (i=0; lst[i]; i++)
+    if (strcmp(lst[i], name) == 0)
+        return i;
+    return -1;
+}
+
+#define dump_intfield(L,n,c) \
+    lua_push_string_by_name(L,n); \
+    lua_pushinteger(L, c); \
+    lua_rawset(L, -3); \
+
+#define dump_stringfield(L,n,c) \
+    lua_push_string_by_name(L,n); \
+    lua_pushstring(L, c); \
+    lua_rawset(L, -3);
+
+#define dump_booleanfield(L,n,c) \
+    lua_push_string_by_name(L,n); \
+    lua_pushboolean(L, c); \
+    lua_rawset(L, -3);
+
+static void dump_math_kerns(lua_State * L, charinfo * co, int l, int id)
+{
+    int i;
+    for (i = 0; i < l; i++) {
+        lua_newtable(L);
+        if (id==top_left_kern) {
+            dump_intfield(L, height, co->top_left_math_kern_array[(2*i)]);
+            dump_intfield(L, kern,   co->top_left_math_kern_array[(2*i)+1]);
+        } else if (id==top_right_kern) {
+            dump_intfield(L, height, co->top_right_math_kern_array[(2*i)]);
+            dump_intfield(L, kern,   co->top_right_math_kern_array[(2*i)+1]);
+        } else if (id==bottom_right_kern) {
+            dump_intfield(L, height, co->bottom_right_math_kern_array[(2*i)]);
+            dump_intfield(L, kern,   co->bottom_right_math_kern_array[(2*i)+1]);
+        } else if (id==bottom_left_kern) {
+            dump_intfield(L, height, co->bottom_left_math_kern_array[(2*i)]);
+            dump_intfield(L, kern,   co->bottom_left_math_kern_array[(2*i)+1]);
+        }
+        lua_rawseti(L, -2, (i + 1));
+    }
+}
+
+static void font_char_to_lua(lua_State * L, internal_font_number f, charinfo * co)
+{
+    liginfo *l;
+    kerninfo *ki;
+
+    lua_createtable(L, 0, 10);
+
+    dump_intfield(L,width,get_charinfo_width(co));
+    dump_intfield(L,height,get_charinfo_height(co));
+    dump_intfield(L,depth,get_charinfo_depth(co));
+
+    if (get_charinfo_italic(co) != 0) {
+       dump_intfield(L,italic,get_charinfo_italic(co));
+    }
+    if (get_charinfo_vert_italic(co) != 0) {
+       dump_intfield(L,vert_italic,get_charinfo_vert_italic(co));
+    }
+    if (get_charinfo_top_accent(co) !=0 && get_charinfo_top_accent(co) != INT_MIN) {
+       dump_intfield(L,top_accent,get_charinfo_top_accent(co));
+    }
+    if (get_charinfo_bot_accent(co) != 0 && get_charinfo_bot_accent(co) != INT_MIN) {
+       dump_intfield(L,bot_accent,get_charinfo_bot_accent(co));
+    }
+    if (get_charinfo_ef(co) != 1000) {
+        dump_intfield(L,expansion_factor,get_charinfo_ef(co));
+    }
+    if (get_charinfo_lp(co) != 0) {
+        dump_intfield(L,left_protruding,get_charinfo_lp(co));
+    }
+    if (get_charinfo_rp(co) != 0) {
+        dump_intfield(L,right_protruding,get_charinfo_rp(co));
+    }
+    if (font_encodingbytes(f) == 2) {
+        dump_intfield(L,index,get_charinfo_index(co));
+    }
+    if (get_charinfo_name(co) != NULL) {
+        dump_stringfield(L,name,get_charinfo_name(co));
+    }
+    if (get_charinfo_tounicode(co) != NULL) {
+        dump_stringfield(L,tounicode,get_charinfo_tounicode(co));
+    }
+    if (get_charinfo_tag(co) == list_tag) {
+        dump_intfield(L,next,get_charinfo_remainder(co));
+    }
+    if (get_charinfo_used(co)) {
+        dump_booleanfield(L,used,(get_charinfo_used(co) ? true : false));
+    }
+    if (get_charinfo_tag(co) == ext_tag) {
+        extinfo *h;
+        h = get_charinfo_hor_variants(co);
+        if (h != NULL) {
+            int i = 1;
+            lua_push_string_by_name(L,horiz_variants);
+            lua_newtable(L);
+            while (h != NULL) {
+                lua_createtable(L, 0, 5);
+                dump_intfield(L, glyph, h->glyph);
+                dump_intfield(L, extender, h->extender);
+                dump_intfield(L, start, h->start_overlap);
+                dump_intfield(L, end, h->end_overlap);
+                dump_intfield(L, advance, h->advance);
+                lua_rawseti(L, -2, i);
+                i++;
+                h = h->next;
+            }
+            lua_rawset(L, -3);
+        }
+        h = get_charinfo_vert_variants(co);
+        if (h != NULL) {
+            int i = 1;
+            lua_push_string_by_name(L,vert_variants);
+            lua_newtable(L);
+            while (h != NULL) {
+                lua_createtable(L, 0, 5);
+                dump_intfield(L, glyph, h->glyph);
+                dump_intfield(L, extender, h->extender);
+                dump_intfield(L, start, h->start_overlap);
+                dump_intfield(L, end, h->end_overlap);
+                dump_intfield(L, advance, h->advance);
+                lua_rawseti(L, -2, i);
+                i++;
+                h = h->next;
+            }
+            lua_rawset(L, -3);
+        }
+    }
+    ki = get_charinfo_kerns(co);
+    if (ki != NULL) {
+        int i;
+        lua_push_string_by_name(L,kerns);
+        lua_createtable(L, 10, 1);
+        for (i = 0; !kern_end(ki[i]); i++) {
+            if (kern_disabled(ki[i])) {
+                /* skip like in lookup */
+            } else {
+                lua_rawgeti(L, -1, kern_char(ki[i]));
+                if (lua_type(L,-1) == LUA_TNIL) {
+                    lua_pop(L,1);
+                    if (kern_char(ki[i]) == right_boundarychar) {
+                        lua_push_string_by_name(L,right_boundary);
+                    } else {
+                        lua_pushinteger(L, kern_char(ki[i]));
+                    }
+                    lua_pushinteger(L, kern_kern(ki[i]));
+                    lua_rawset(L, -3);
+                } else {
+                    /* first one wins */
+                    lua_pop(L,1);
+                }
+            }
+        }
+        lua_rawset(L, -3);
+    }
+    l = get_charinfo_ligatures(co);
+    if (l != NULL) {
+        int i;
+        lua_push_string_by_name(L,ligatures);
+        lua_createtable(L, 10, 1);
+        for (i = 0; !lig_end(l[i]); i++) {
+            if (lig_char(l[i]) == right_boundarychar) {
+                lua_push_string_by_name(L,right_boundary);
+            } else {
+                lua_pushinteger(L, lig_char(l[i]));
+            }
+            lua_createtable(L, 0, 2);
+            lua_push_string_by_name(L,type);
+            lua_pushinteger(L, lig_type(l[i]));
+            lua_rawset(L, -3);
+            lua_push_string_by_name(L,char);
+            lua_pushinteger(L, lig_replacement(l[i]));
+            lua_rawset(L, -3);
+            lua_rawset(L, -3);
+        }
+        lua_rawset(L, -3);
+    }
+
+    lua_push_string_by_name(L,mathkern);
+    lua_newtable(L);
+    {
+    int i, j;
+    i = get_charinfo_math_kerns(co, top_right_kern);
+    j = 0;
+    if (i > 0) {
+        j++;
+        lua_push_string_by_name(L,top_right);
+        lua_newtable(L);
+        dump_math_kerns(L, co, i, top_right_kern);
+        lua_rawset(L, -3);
+    }
+    i = get_charinfo_math_kerns(co, top_left_kern);
+    if (i > 0) {
+        j++;
+        lua_push_string_by_name(L,top_left);
+        lua_newtable(L);
+        dump_math_kerns(L, co, i, top_left_kern);
+        lua_rawset(L, -3);
+    }
+    i = get_charinfo_math_kerns(co, bottom_right_kern);
+    if (i > 0) {
+        j++;
+        lua_push_string_by_name(L,bottom_right);
+        lua_newtable(L);
+        dump_math_kerns(L, co, i, bottom_right_kern);
+        lua_rawset(L, -3);
+    }
+    i = get_charinfo_math_kerns(co, bottom_left_kern);
+    if (i > 0) {
+        j++;
+        lua_push_string_by_name(L,bottom_left);
+        lua_newtable(L);
+        dump_math_kerns(L, co, i, bottom_left_kern);
+        lua_rawset(L, -3);
+    }
+    if (j > 0)
+        lua_rawset(L, -3);
+    else
+        lua_pop(L, 2);
+    }
+}
+
+static void write_lua_parameters(lua_State * L, int f)
+{
+    int k;
+    lua_push_string_by_name(L,parameters);
+    lua_newtable(L);
+    for (k = 1; k <= font_params(f); k++) {
+        switch (k) {
+            case slant_code:
+                dump_intfield(L,slant,font_param(f, k));
+                break;
+            case space_code:
+                dump_intfield(L,space,font_param(f, k));
+                break;
+            case space_stretch_code:
+                dump_intfield(L,space_stretch,font_param(f, k));
+                break;
+            case space_shrink_code:
+                dump_intfield(L,space_shrink,font_param(f, k));
+                break;
+            case x_height_code:
+                dump_intfield(L,x_height,font_param(f, k));
+                break;
+            case quad_code:
+                dump_intfield(L,quad,font_param(f, k));
+                break;
+            case extra_space_code:
+                dump_intfield(L,extra_space,font_param(f, k));
+                break;
+            default:
+                lua_pushinteger(L, font_param(f, k));
+                lua_rawseti(L, -2, k);
+        }
+    }
+    lua_rawset(L, -3);
+}
+
+@ @c
+static void write_lua_math_parameters(lua_State * L, int f)
+{
+    int k;
+    lua_push_string_by_name(L,MathConstants);
+    lua_newtable(L);
+    for (k = 1; k <= font_math_params(f); k++) {
+        lua_pushinteger(L, font_math_param(f, k));
+        if (k <= MATH_param_max) {
+            lua_setfield(L, -2, MATH_param_names[k]);
+        } else {
+            lua_rawseti(L, -2, k);
+        }
+    }
+    lua_rawset(L, -3);
+}
+
+int font_to_lua(lua_State * L, int f)
+{
+    int k;
+    charinfo *co;
+    if (font_cache_id(f) > 0) {
+        /* fetch the table from the registry if it was
+           saved there by |font_from_lua()| */
+        lua_rawgeti(L, LUA_REGISTRYINDEX, font_cache_id(f));
+        /* fontdimens can be changed from tex code */
+        write_lua_parameters(L, f);
+        return 1;
+    }
+
+    lua_newtable(L);
+    lua_push_string_by_name(L,name);
+    lua_pushstring(L, font_name(f));
+    lua_rawset(L, -3);
+    if (font_area(f) != NULL) {
+        dump_stringfield(L,area,font_area(f));
+    }
+    if (font_filename(f) != NULL) {
+        dump_stringfield(L,filename,font_filename(f));
+    }
+    if (font_fullname(f) != NULL) {
+        dump_stringfield(L,fullname,font_fullname(f));
+    }
+    if (font_psname(f) != NULL) {
+        dump_stringfield(L,psname,font_psname(f));
+    }
+    if (font_encodingname(f) != NULL) {
+        dump_stringfield(L,encodingname,font_encodingname(f));
+    }
+
+    dump_booleanfield(L,used,(font_used(f) ? true : false));
+    dump_stringfield(L,type,font_type_strings[font_type(f)]);
+    dump_stringfield(L,format,font_format_strings[font_format(f)]);
+    dump_stringfield(L,writingmode,font_writingmode_strings[font_writingmode(f)]);
+    dump_stringfield(L,identity,font_identity_strings[font_identity(f)]);
+    dump_stringfield(L,embedding,font_embedding_strings[font_embedding(f)]);
+
+    dump_intfield(L,units_per_em,font_units_per_em(f));
+    dump_intfield(L,size,font_size(f));
+    dump_intfield(L,designsize,font_dsize(f));
+    dump_intfield(L,checksum,font_checksum(f));
+    dump_intfield(L,slant,font_slant(f));
+    dump_intfield(L,extend,font_extend(f));
+    dump_intfield(L,direction,font_natural_dir(f));
+    dump_intfield(L,encodingbytes,font_encodingbytes(f));
+    dump_booleanfield(L,oldmath,font_oldmath(f));
+    dump_intfield(L,tounicode,font_tounicode(f));
+
+    /* the next one is read only */
+    if (font_max_shrink(f) != 0) {
+        dump_intfield(L,shrink,font_max_shrink(f));
+    }
+    if (font_max_stretch(f) != 0) {
+        dump_intfield(L,stretch,font_max_stretch(f));
+    }
+    if (font_step(f) != 0) {
+        dump_intfield(L,step,font_step(f));
+    }
+    if (font_auto_expand(f) != 0) {
+        dump_booleanfield(L,auto_expand,font_auto_expand(f));
+    }
+    if (pdf_font_attr(f) != 0) {
+        char *s = makecstring(pdf_font_attr(f));
+        dump_stringfield(L,attributes,s);
+        free(s);
+    }
+
+    /* params */
+    write_lua_parameters(L, f);
+    write_lua_math_parameters(L, f);
+
+    /* chars */
+    lua_push_string_by_name(L,characters);
+    lua_createtable(L, font_tables[f]->charinfo_size, 0);       /* all characters */
+    if (has_left_boundary(f)) {
+        co = get_charinfo(f, left_boundarychar);
+        lua_push_string_by_name(L,left_boundary);
+        font_char_to_lua(L, f, co);
+        lua_rawset(L, -3);
+    }
+    if (has_right_boundary(f)) {
+        co = get_charinfo(f, right_boundarychar);
+        lua_push_string_by_name(L,right_boundary);
+        font_char_to_lua(L, f, co);
+        lua_rawset(L, -3);
+    }
+    for (k = font_bc(f); k <= font_ec(f); k++) {
+        if (quick_char_exists(f, k)) {
+            lua_pushinteger(L, k);
+            co = get_charinfo(f, k);
+            font_char_to_lua(L, f, co);
+            lua_rawset(L, -3);
+        }
+    }
+    lua_rawset(L, -3);
+
+    if (font_cache_id(f) == 0) {        /* renew */
+        int r;
+        lua_pushvalue(L, -1);
+        r = luaL_ref(L, LUA_REGISTRYINDEX);     /* pops the table */
+        set_font_cache_id(f, r);
+    }
+    return 1;
+}
+
+#define count_hash_items(L,name,n) \
+    n = 0; \
+    lua_key_rawgeti(name); \
+    if (lua_type(L, -1) == LUA_TTABLE) { \
+        lua_pushnil(L); \
+        while (lua_next(L, -2) != 0) { \
+            n++; \
+            lua_pop(L, 1); \
+        } \
+    } \
+    if (n) { \
+        /* keep table on stack */ \
+    } else{ \
+        lua_pop(L, 1); \
+    }
+
+@ @c
+#define streq(a,b) (strcmp(a,b)==0)
+
+#define append_packet(k) { *(cp++) = (eight_bits) (k); }
+
+#define do_store_four(l) {                 \
+    append_packet((l & 0xFF000000) >> 24); \
+    append_packet((l & 0x00FF0000) >> 16); \
+    append_packet((l & 0x0000FF00) >> 8);  \
+    append_packet((l & 0x000000FF));       \
+}
+
+@ @c
+static void append_float(eight_bits ** cpp, float a)
+{
+    unsigned int i;
+    eight_bits *cp = *cpp;
+    union U {
+        float a;
+        eight_bits b[sizeof(float)];
+    } u;
+    u.a = a;
+    for (i = 0; i < sizeof(float); i++)
+        append_packet(u.b[i]);
+    *cpp = cp;
+}
+
+static int n_enum_field(lua_State * L, int name_index, int dflt, const char **values)
+{
+    int k, t;
+    const char *s;
+    int i = dflt;
+    lua_rawgeti(L, LUA_REGISTRYINDEX, name_index);      /* fetch the stringptr */
+    lua_rawget(L, -2);
+    t = lua_type(L,-1);
+    if (t == LUA_TNUMBER) {
+        i = (int) lua_tointeger(L, -1);
+    } else if (t == LUA_TSTRING) {
+        s = lua_tostring(L, -1);
+        k = 0;
+        while (values[k] != NULL) {
+            if (strcmp(values[k], s) == 0) {
+                i = k;
+                break;
+            }
+            k++;
+        }
+    }
+    lua_pop(L, 1);
+    return i;
+}
+
+static int n_boolean_field(lua_State * L, int name_index, int dflt)
+{
+    int i = dflt;
+    lua_rawgeti(L, LUA_REGISTRYINDEX, name_index);      /* fetch the stringptr */
+    lua_rawget(L, -2);
+    if (lua_isboolean(L, -1)) {
+        i = lua_toboolean(L, -1);
+    }
+    lua_pop(L, 1);
+    return i;
+}
+
+static char *n_string_field_copy(lua_State * L, int name_index, const char *dflt)
+{
+    char *i;
+    lua_rawgeti(L, LUA_REGISTRYINDEX, name_index);      /* fetch the stringptr */
+    lua_rawget(L, -2);
+    if (lua_type(L,-1) == LUA_TSTRING) {
+        i = xstrdup(lua_tostring(L, -1));
+    } else if (dflt == NULL) {
+        i = NULL;
+    } else {
+        i = xstrdup(dflt);
+    }
+    lua_pop(L, 1);
+    return i;
+}
+
+static const char *n_string_field(lua_State * L, int name_index)
+{
+    lua_rawgeti(L, LUA_REGISTRYINDEX, name_index);      /* fetch the stringptr */
+    lua_rawget(L, -2);
+    return lua_tostring(L,-1);
+}
+
+static int n_some_field(lua_State * L, int name_index)
+{
+    lua_rawgeti(L, LUA_REGISTRYINDEX, name_index);      /* fetch the stringptr */
+    lua_rawget(L, -2);
+    return lua_type(L,-1);
+}
+
+/*static void init_font_string_pointers(lua_State * L){}*/
+
+static int count_char_packet_bytes(lua_State * L)
+{
+    register int i;
+    register int l = 0;
+    int ff = 0;
+    for (i = 1; i <= (int) lua_rawlen(L, -1); i++) {
+        lua_rawgeti(L, -1, i);
+        if (lua_istable(L, -1)) {
+            lua_rawgeti(L, -1, 1);
+            if (lua_type(L,-1) == LUA_TSTRING) {
+                const char *s = lua_tostring(L, -1);
+                if (lua_key_eq(s, font)) {
+                    l += 5;
+                    ff = 1;
+                } else if (lua_key_eq(s, char)) {
+                    if (ff == 0) {
+                        l += 5;
+                    }
+                    l += 5;
+                    ff = 1;
+                } else if (lua_key_eq(s, slot)) {
+                    l += 10;
+                    ff = 1;
+                } else if (lua_key_eq(s, comment) || lua_key_eq(s, nop)) {
+                    ;
+                } else if (lua_key_eq(s, push) || lua_key_eq(s, pop)) {
+                    l++;
+                } else if (lua_key_eq(s, rule)) {
+                    l += 9;
+                } else if (lua_key_eq(s, right) || lua_key_eq(s, node)
+                           || lua_key_eq(s, down) || lua_key_eq(s, image)) {
+                    l += 5;
+                } else if (lua_key_eq(s, scale)) {
+                    l += sizeof(float) + 1;
+                } else if (lua_key_eq(s, special) || lua_key_eq(s, lua)) {
+                    size_t len;
+                    lua_rawgeti(L, -2, 2);
+                    if (lua_type(L,-1) == LUA_TSTRING) {
+                        (void) lua_tolstring(L, -1, &len);
+                        lua_pop(L, 1);
+                        if (len > 0) {
+                            l = (int) (l + 5 + (int) len);
+                        }
+                    } else {
+                        lua_pop(L, 1);
+                        normal_error("vf command","invalid packet special");
+                        /* fprintf(stdout, "invalid packet special!\n"); */
+                    }
+                } else {
+                    normal_error("vf command","unknown packet command");
+                    /* fprintf(stdout, "unknown packet command %s!\n", s); */
+                }
+            } else {
+                normal_error("vf command","no packet command");
+             /* fprintf(stdout, "no packet command!\n"); */
+            }
+            lua_pop(L, 1);      /* command name */
+        }
+        lua_pop(L, 1);          /* item */
+    }
+    return l;
+}
+
+static scaled sp_to_dvi(halfword sp, halfword atsize)
+{
+    double result, mult;
+    mult = (double) (atsize / 65536.0);
+    result = (double) (sp * 16.0);
+    return floor(result / mult);
+}
+
+@ @c
+static void read_char_packets(lua_State * L, int *l_fonts, charinfo * co, internal_font_number f, int atsize)
+{
+    int i, n, m;
+    size_t l;
+    int cmd;
+    const char *s;
+    eight_bits *cpackets, *cp;
+    int ff = 0;
+    int max_f = 0;
+    int pc = count_char_packet_bytes(L);
+    if (pc <= 0)
+        return;
+    while (l_fonts[(max_f + 1)] != 0)
+        max_f++;
+    cp = cpackets = xmalloc((unsigned) (pc + 1));
+    for (i = 1; i <= (int) lua_rawlen(L, -1); i++) {
+        lua_rawgeti(L, -1, i);
+        if (lua_istable(L, -1)) {
+            /* fetch the command code */
+            lua_rawgeti(L, -1, 1);
+            if (lua_type(L,-1) == LUA_TSTRING) {
+                s = lua_tostring(L, -1);
+                cmd = 0;
+                if (lua_key_eq(s, font)) {
+                    cmd = packet_font_code;
+                } else if (lua_key_eq(s, char)) {
+                    cmd = packet_char_code;
+                    if (ff == 0) {
+                        append_packet(packet_font_code);
+                        ff = l_fonts[1];
+                        do_store_four(ff);
+                    }
+                } else if (lua_key_eq(s, slot)) {
+                    cmd = packet_nop_code;
+                    lua_rawgeti(L, -2, 2);
+                    n = (int) luaL_checkinteger(L, -1);
+                    if (n ==0) {
+                        ff = f;
+                    } else {
+                        ff = (n > max_f ? l_fonts[1] : l_fonts[n]);
+                    }
+                    lua_rawgeti(L, -3, 3);
+                    n = (int) luaL_checkinteger(L, -1);
+                    lua_pop(L, 2);
+                    append_packet(packet_font_code);
+                    do_store_four(ff);
+                    append_packet(packet_char_code);
+                    do_store_four(n);
+                } else if (lua_key_eq(s, comment) || lua_key_eq(s, nop)) {
+                    cmd = packet_nop_code;
+                } else if (lua_key_eq(s, node)) {
+                    cmd = packet_node_code;
+                } else if (lua_key_eq(s, push)) {
+                    cmd = packet_push_code;
+                } else if (lua_key_eq(s, pop)) {
+                    cmd = packet_pop_code;
+                } else if (lua_key_eq(s, rule)) {
+                    cmd = packet_rule_code;
+                } else if (lua_key_eq(s, right)) {
+                    cmd = packet_right_code;
+                } else if (lua_key_eq(s, down)) {
+                    cmd = packet_down_code;
+                } else if (lua_key_eq(s, special)) {
+                    cmd = packet_special_code;
+                } else if (lua_key_eq(s, image)) {
+                    cmd = packet_image_code;
+                } else if (lua_key_eq(s, scale)) {
+                    cmd = packet_scale_code;
+                } else if (lua_key_eq(s, lua)) {
+                    cmd = packet_lua_code;
+                }
+                switch (cmd) {
+                    case packet_push_code:
+                    case packet_pop_code:
+                        append_packet(cmd);
+                        break;
+                    case packet_font_code:
+                        append_packet(cmd);
+                        lua_rawgeti(L, -2, 2);
+                        n = (int) luaL_checkinteger(L, -1);
+                        if (n == 0) {
+                            ff = n;
+                        } else {
+                            ff = (n > max_f ? l_fonts[1] : l_fonts[n]);
+                        }
+                        do_store_four(ff);
+                        lua_pop(L, 1);
+                        break;
+                    case packet_node_code:
+                        append_packet(cmd);
+                        lua_rawgeti(L, -2, 2);
+                        n = copy_node_list(nodelist_from_lua(L));
+                        do_store_four(n);
+                        lua_pop(L, 1);
+                        break;
+                    case packet_char_code:
+                        append_packet(cmd);
+                        lua_rawgeti(L, -2, 2);
+                        n = (int) luaL_checkinteger(L, -1);
+                        do_store_four(n);
+                        lua_pop(L, 1);
+                        break;
+                    case packet_right_code:
+                    case packet_down_code:
+                        append_packet(cmd);
+                        lua_rawgeti(L, -2, 2);
+                        n = (int) luaL_checkinteger(L, -1);
+                        do_store_four(sp_to_dvi(n, atsize));
+                        lua_pop(L, 1);
+                        break;
+                    case packet_rule_code:
+                        append_packet(cmd);
+                        lua_rawgeti(L, -2, 2);
+                        n = (int) luaL_checkinteger(L, -1);
+                        do_store_four(sp_to_dvi(n, atsize));
+                        lua_rawgeti(L, -3, 3);
+                        n = (int) luaL_checkinteger(L, -1);
+                        do_store_four(sp_to_dvi(n, atsize));
+                        lua_pop(L, 2);
+                        break;
+                    case packet_special_code:
+                    case packet_lua_code:
+                        append_packet(cmd);
+                        lua_rawgeti(L, -2, 2);
+                        s = luaL_checklstring(L, -1, &l);
+                        if (l > 0) {
+                            do_store_four(l);
+                            m = (int) l;
+                            while (m > 0) {
+                                n = *s++;
+                                m--;
+                                append_packet(n);
+                            }
+                        }
+                        lua_pop(L, 1);
+                        break;
+                    case packet_image_code:
+                        append_packet(cmd);
+                        lua_rawgeti(L, -2, 2);      /* img/imgtable? ... */
+                        if (lua_istable(L, -1)) {   /* imgtable ... */
+                            lua_getglobal(L, "img");        /* imglib imgtable ... */
+                            lua_pushstring(L, "new");       /* `new' imglib imgtable ... */
+                            lua_gettable(L, -2);    /* f imglib imgtable ... */
+                            lua_insert(L, -3);      /* imglib imgtable f ... */
+                            lua_pop(L, 1);  /* imgtable f ... */
+                            lua_call(L, 1, 1);
+                        }           /* img ... */
+                        luaL_checkudata(L, -1, TYPE_IMG);   /* img ... --- just typecheck */
+                        n = luaL_ref(L, LUA_REGISTRYINDEX);  /* ... */
+                        do_store_four(n);
+                        break;
+                    case packet_nop_code:
+                        break;
+                    case packet_scale_code:
+                        append_packet(cmd);
+                        lua_rawgeti(L, -2, 2);
+                        append_float(&cp, (float) luaL_checknumber(L, -1));
+                        lua_pop(L, 1);
+                        break;
+                    default:
+                        normal_error("vf command","invalid packet code");
+                        /* fprintf(stdout, "Unknown char packet code %s\n", s); */
+                }
+            }
+            lua_pop(L, 1);      /* command code */
+        } else {
+            normal_error("vf command","commands has to be a tbale");
+            /* fprintf(stdout, "Found a `commands' item that is not a table\n"); */
+        }
+        lua_pop(L, 1);          /* command table */
+    }
+    append_packet(packet_end_code);
+    set_charinfo_packets(co, cpackets);
+    return;
+}
+
+@ @c
+static void read_lua_cidinfo(lua_State * L, int f)
+{
+    int i;
+    char *s;
+    /*lua_getfield(L, -1, "cidinfo");*/
+    lua_key_rawgeti(cidinfo);
+    if (lua_istable(L, -1)) {
+        i = lua_numeric_field_by_index(L,lua_key_index(version), 0);
+        set_font_cidversion(f, i);
+        i = lua_numeric_field_by_index(L,lua_key_index(supplement), 0);
+        set_font_cidsupplement(f, i);
+        s = n_string_field_copy(L, lua_key_index(registry), "Adobe");       /* Adobe-Identity-0 */
+        set_font_cidregistry(f, s);
+        s = n_string_field_copy(L, lua_key_index(ordering), "Identity");
+        set_font_cidordering(f, s);
+    }
+    lua_pop(L, 1);
+}
+
+
+@ @c
+static void read_lua_parameters(lua_State * L, int f)
+{
+    int i, n, t;
+    const char *s;
+    /*lua_getfield(L, -1, "parameters");*/
+    lua_key_rawgeti(parameters);
+    if (lua_istable(L, -1)) {
+        /* the number of parameters is the max(IntegerKeys(L)),7) */
+        n = 7;
+        lua_pushnil(L);         /* first key */
+        while (lua_next(L, -2) != 0) {
+            if (lua_type(L, -2) == LUA_TNUMBER) {
+                i = (int) lua_tointeger(L, -2);
+                if (i > n)
+                    n = i;
+            }
+            lua_pop(L, 1);      /* pop value */
+        }
+        if (n > 7)
+            set_font_params(f, n);
+        /* sometimes it is handy to have all integer keys */
+        for (i = 1; i <= 7; i++) {
+            lua_rawgeti(L, -1, i);
+            if (lua_type(L, -1) == LUA_TNUMBER) {
+                n = lua_roundnumber(L, -1); /* round ? */
+                set_font_param(f, i, n);
+            }
+            lua_pop(L, 1);
+        }
+        lua_pushnil(L);         /* first key */
+        while (lua_next(L, -2) != 0) {
+            t = lua_type(L,-2);
+            if (t == LUA_TNUMBER) {
+                i = (int) lua_tointeger(L, -2);
+                if (i >= 8) {
+                    if (lua_type(L,-1) == LUA_TNUMBER) {
+                        n = lua_roundnumber(L, -1);
+                    } else {
+                        n = 0;
+                    }
+                    set_font_param(f, i, n);
+                }
+            } else if (t == LUA_TSTRING) {
+                s = lua_tostring(L, -2);
+                if (lua_type(L,-1) == LUA_TNUMBER) {
+                    n = lua_roundnumber(L, -1);
+                } else {
+                    n = 0;
+                }
+                if (lua_key_eq(s, slant)) {
+                    set_font_param(f, slant_code, n);
+                } else if (lua_key_eq(s, space)) {
+                    set_font_param(f, space_code, n);
+                } else if (lua_key_eq(s, space_stretch)) {
+                    set_font_param(f, space_stretch_code, n);
+                } else if (lua_key_eq(s, space_shrink)) {
+                    set_font_param(f, space_shrink_code, n);
+                } else if (lua_key_eq(s, x_height)) {
+                    set_font_param(f, x_height_code, n);
+                } else if (lua_key_eq(s, quad)) {
+                    set_font_param(f, quad_code, n);
+                } else if (lua_key_eq(s, extra_space)) {
+                    set_font_param(f, extra_space_code, n);
+                }
+            }
+            lua_pop(L, 1);
+        }
+    }
+    lua_pop(L, 1);
+
+}
+
+@ @c
+static void read_lua_math_parameters(lua_State * L, int f)
+{
+    int i = 0, n = 0, t;
+    /*lua_getfield(L, -1, "MathConstants");*/
+    lua_key_rawgeti(MathConstants);
+    if (lua_istable(L, -1)) {
+        lua_pushnil(L);
+        while (lua_next(L, -2) != 0) {
+            t = lua_type(L,-2);
+            if (t == LUA_TNUMBER) {
+                i = (int) lua_tointeger(L, -2);
+            } else if (t == LUA_TSTRING) {
+                i = ff_checkoption(L, -2, NULL, MATH_param_names);
+            }
+            n = (int) lua_roundnumber(L, -1);
+            if (i > 0) {
+                set_font_math_param(f, i, n);
+            }
+            lua_pop(L, 1);      /* pop value */
+        }
+        set_font_oldmath(f,false);
+    } else {
+        set_font_oldmath(f,true);
+    }
+    lua_pop(L, 1);
+}
+
+@ @c
+#define MIN_INF -0x7FFFFFFF
+
+static void store_math_kerns(lua_State * L, int index, charinfo * co, int id)
+{
+    int l, k;
+    scaled ht, krn;
+    lua_key_direct_rawgeti(index);
+    if (lua_istable(L, -1) && ((k = (int) lua_rawlen(L, -1)) > 0)) {
+        for (l = 0; l < k; l++) {
+            lua_rawgeti(L, -1, (l + 1));
+            if (lua_istable(L, -1)) {
+                ht = (scaled) lua_numeric_field_by_index(L, lua_key_index(height), MIN_INF);
+                krn = (scaled) lua_numeric_field_by_index(L, lua_key_index(kern), MIN_INF);
+                if (krn > MIN_INF && ht > MIN_INF)
+                    add_charinfo_math_kern(co, id, ht, krn);
+            }
+            lua_pop(L, 1);
+        }
+    }
+    lua_pop(L, 1);
+}
+
+@ @c
+static void font_char_from_lua(lua_State * L, internal_font_number f, int i, int *l_fonts, boolean has_math)
+{
+    int k, r, t, lt, u, n;
+    charinfo *co;
+    kerninfo *ckerns;
+    liginfo *cligs;
+    scaled j;
+    const char *s;
+    int nl = 0;                 /* number of ligature table items */
+    int nk = 0;                 /* number of kern table items */
+    int ctr = 0;
+    int atsize = font_size(f);
+    if (lua_istable(L, -1)) {
+        co = get_charinfo(f, i);
+        set_charinfo_tag(co, 0);
+        j = lua_numeric_field_by_index(L, lua_key_index(width), 0);
+        set_charinfo_width(co, j);
+        j = lua_numeric_field_by_index(L, lua_key_index(height), 0);
+        set_charinfo_height(co, j);
+        j = lua_numeric_field_by_index(L, lua_key_index(depth), 0);
+        set_charinfo_depth(co, j);
+        j = lua_numeric_field_by_index(L, lua_key_index(italic), 0);
+        set_charinfo_italic(co, j);
+        j = lua_numeric_field_by_index(L, lua_key_index(vert_italic), 0);
+        set_charinfo_vert_italic(co, j);
+        j = lua_numeric_field_by_index(L, lua_key_index(index), 0);
+        set_charinfo_index(co, j);
+        j = lua_numeric_field_by_index(L, lua_key_index(expansion_factor), 1000);
+        set_charinfo_ef(co, j);
+        j = lua_numeric_field_by_index(L, lua_key_index(left_protruding), 0);
+        set_charinfo_lp(co, j);
+        j = lua_numeric_field_by_index(L, lua_key_index(right_protruding), 0);
+        set_charinfo_rp(co, j);
+        k = n_boolean_field(L, lua_key_index(used), 0);
+        set_charinfo_used(co, k);
+        s = n_string_field(L, lua_key_index(name));
+        if (s != NULL)
+            set_charinfo_name(co, xstrdup(s));
+        else
+            set_charinfo_name(co, NULL);
+        /* n_string_field leaves a value on stack*/
+        lua_pop(L,1);
+        u = n_some_field(L,lua_key_index(tounicode));
+        if (u == LUA_TNUMBER) {
+            u = lua_tointeger(L,-1);
+            if (u < 0) {
+                set_charinfo_tounicode(co, NULL);
+            } else if (u < 0xD7FF || (u > 0xDFFF && u <= 0xFFFF)) {
+                char *s = malloc(5);
+                sprintf(s,"%04X",(unsigned int) u);
+                set_charinfo_tounicode(co,s);
+            } else {
+                char *s = malloc(9);
+                u = u - 0x10000;
+                sprintf(s,"%04X%04X",(unsigned int) (floor(u/1024)+0xD800),(unsigned int) (u%1024+0xDC00));
+                set_charinfo_tounicode(co,s);
+            }
+        } else if (u == LUA_TTABLE) {
+            n = lua_rawlen(L,-1);
+            u = 0;
+            for (k = 1; k <= n; k++) {
+                lua_rawgeti(L, -1, k);
+                if (lua_type(L,-1) == LUA_TNUMBER) {
+                    u = lua_tointeger(L,-1);
+                } else {
+                    lua_pop(L, 1);
+                    break;
+                }
+                if (u < 0) {
+                    u = -1;
+                    lua_pop(L, 1);
+                    break;
+                } else if (u < 0xD7FF || (u > 0xDFFF && u <= 0xFFFF)) {
+                    u = u + 4;
+                } else {
+                    u = u + 8;
+                }
+                lua_pop(L, 1);
+            }
+            if (u>0) {
+                char *s = malloc(u+1);
+                char *t = s ;
+                for (k = 1; k <= n; k++) {
+                    lua_rawgeti(L, -1, k);
+                    u = lua_tointeger(L,-1);
+                    if (u < 0xD7FF || (u > 0xDFFF && u <= 0xFFFF)) {
+                        sprintf(t,"%04X",(unsigned int) u);
+                        t += 4;
+                    } else {
+                        u = u - 0x10000;
+                        sprintf(t,"%04X%04X",(unsigned int) (floor(u/1024)+0xD800),(unsigned int) (u%1024+0xDC00));
+                        t += 8;
+                    }
+                    lua_pop(L, 1);
+                }
+                set_charinfo_tounicode(co,s);
+            } else {
+                set_charinfo_tounicode(co, NULL);
+            }
+        } else if (u == LUA_TSTRING) {
+            s = lua_tostring(L,-1);
+            set_charinfo_tounicode(co, xstrdup(s));
+        } else {
+            set_charinfo_tounicode(co, NULL);
+        }
+        /* ... leaves a value on stack*/
+        lua_pop(L,1);
+
+        if (has_math) {
+            j = lua_numeric_field_by_index(L, lua_key_index(top_accent), INT_MIN);
+            set_charinfo_top_accent(co, j);
+            j = lua_numeric_field_by_index(L, lua_key_index(bot_accent), INT_MIN);
+            set_charinfo_bot_accent(co, j);
+            k = lua_numeric_field_by_index(L, lua_key_index(next), -1);
+            if (k >= 0) {
+                set_charinfo_tag(co, list_tag);
+                set_charinfo_remainder(co, k);
+            }
+
+            lua_key_rawgeti(extensible);
+            if (lua_istable(L, -1)) {
+                int top, bot, mid, rep;
+                top = lua_numeric_field_by_index(L, lua_key_index(top), 0);
+                bot = lua_numeric_field_by_index(L, lua_key_index(bot), 0);
+                mid = lua_numeric_field_by_index(L, lua_key_index(mid), 0);
+                rep = lua_numeric_field_by_index(L, lua_key_index(rep), 0);
+                if (top != 0 || bot != 0 || mid != 0 || rep != 0) {
+                    set_charinfo_tag(co, ext_tag);
+                    set_charinfo_extensible(co, top, bot, mid, rep);
+                } else {
+                    formatted_warning("font", "lua-loaded font %s char U+%X has an invalid extensible field",
+                        font_name(f), (int) i);
+                }
+            }
+            lua_pop(L, 1);
+
+            lua_key_rawgeti(horiz_variants);
+            if (lua_istable(L, -1)) {
+                int glyph, startconnect, endconnect, advance, extender;
+                extinfo *h;
+                set_charinfo_tag(co, ext_tag);
+                set_charinfo_hor_variants(co, NULL);
+                for (k = 1;; k++) {
+                    lua_rawgeti(L, -1, k);
+                    if (lua_istable(L, -1)) {
+                        glyph = lua_numeric_field_by_index(L, lua_key_index(glyph), 0);
+                        extender = lua_numeric_field_by_index(L, lua_key_index(extender), 0);
+                        startconnect = lua_numeric_field_by_index(L, lua_key_index(start), 0);
+                        endconnect = lua_numeric_field_by_index(L, lua_key_index(end), 0);
+                        advance = lua_numeric_field_by_index(L, lua_key_index(advance), 0);
+                        h = new_variant(glyph, startconnect, endconnect, advance, extender);
+                        add_charinfo_hor_variant(co, h);
+                        lua_pop(L, 1);
+                    } else {
+                        lua_pop(L, 1);
+                        break;
+                    }
+                }
+            }
+            lua_pop(L, 1);
+
+            lua_key_rawgeti(vert_variants);
+            if (lua_istable(L, -1)) {
+                int glyph, startconnect, endconnect, advance, extender;
+                extinfo *h;
+                set_charinfo_tag(co, ext_tag);
+                set_charinfo_vert_variants(co, NULL);
+                for (k = 1;; k++) {
+                    lua_rawgeti(L, -1, k);
+                    if (lua_istable(L, -1)) {
+                        glyph = lua_numeric_field_by_index(L, lua_key_index(glyph), 0);
+                        extender = lua_numeric_field_by_index(L, lua_key_index(extender), 0);
+                        startconnect = lua_numeric_field_by_index(L, lua_key_index(start), 0);
+                        endconnect = lua_numeric_field_by_index(L, lua_key_index(end), 0);
+                        advance = lua_numeric_field_by_index(L, lua_key_index(advance), 0);
+                        h = new_variant(glyph, startconnect, endconnect, advance, extender);
+                        add_charinfo_vert_variant(co, h);
+                        lua_pop(L, 1);
+                    } else {
+                        lua_pop(L, 1);
+                        break;
+                    }
+                }
+            }
+            lua_pop(L, 1);
+
+            /*
+                Here is a complete example:
+
+                |mathkern = {|
+                    | bottom_left  = { { height = 420, kern = 80  }, { height = 520, kern = 4   } },|
+                    | bottom_right = { { height = 0,   kern = 48  } },|
+                    | top_left     = { { height = 620, kern = 0   }, { height = 720, kern = -80 } },|
+                    | top_right    = { { height = 676, kern = 115 }, { height = 776, kern = 45  } },|
+                |}|
+
+            */
+
+            lua_key_rawgeti(mathkern);
+            if (lua_istable(L, -1)) {
+                store_math_kerns(L,lua_key_index(top_left), co, top_left_kern);
+                store_math_kerns(L,lua_key_index(top_right), co, top_right_kern);
+                store_math_kerns(L,lua_key_index(bottom_right), co, bottom_right_kern);
+                store_math_kerns(L,lua_key_index(bottom_left), co, bottom_left_kern);
+            }
+            lua_pop(L, 1);
+        }
+        /* end of |has_math| */
+        count_hash_items(L, kerns, nk);
+        if (nk > 0) {
+            /* kerns table still on stack */
+            ckerns = xcalloc((unsigned) (nk + 1), sizeof(kerninfo));
+            ctr = 0;
+            lua_pushnil(L); /* traverse hash */
+            while (lua_next(L, -2) != 0) {
+                k = non_boundarychar;
+                lt = lua_type(L,-2);
+                if (lt == LUA_TNUMBER) {
+                    k = (int) lua_tointeger(L, -2); /* adjacent char */
+                    if (k < 0)
+                        k = non_boundarychar;
+                } else if (lt == LUA_TSTRING) {
+                    s = lua_tostring(L, -2);
+                    if (lua_key_eq(s, right_boundary)) {
+                        k = right_boundarychar;
+                        if (!has_right_boundary(f))
+                            set_right_boundary(f, get_charinfo(f, right_boundarychar));
+                    }
+                }
+                j = lua_roundnumber(L, -1); /* movement */
+                if (k != non_boundarychar) {
+                    set_kern_item(ckerns[ctr], k, j);
+                    ctr++;
+                } else {
+                    formatted_warning("font", "lua-loaded font %s char U+%X has an invalid kern field",
+                        font_name(f), (int) i);
+                }
+                lua_pop(L, 1);
+            }
+            /* guard against empty tables */
+            if (ctr > 0) {
+                set_kern_item(ckerns[ctr], end_kern, 0);
+                set_charinfo_kerns(co, ckerns);
+            } else {
+                formatted_warning("font", "lua-loaded font %s char U+%X has an invalid kerns field",
+                    font_name(f), (int) i);
+            }
+            lua_pop(L, 1);
+        }
+
+        /* packet commands */
+        lua_key_rawgeti(commands);
+        if (lua_istable(L, -1)) {
+            lua_pushnil(L);     /* first key */
+            if (lua_next(L, -2) != 0) {
+                lua_pop(L, 2);
+                read_char_packets(L, (int *) l_fonts, co, f, atsize);
+            }
+        }
+        lua_pop(L, 1);
+
+        /* ligatures */
+        count_hash_items(L, ligatures, nl);
+        if (nl > 0) {
+            /* ligatures table still on stack */
+            cligs = xcalloc((unsigned) (nl + 1), sizeof(liginfo));
+            ctr = 0;
+            lua_pushnil(L); /* traverse hash */
+            while (lua_next(L, -2) != 0) {
+                k = non_boundarychar;
+                lt = lua_type(L,-2);
+                if (lt == LUA_TNUMBER) {
+                    k = (int) lua_tointeger(L, -2); /* adjacent char */
+                    if (k < 0) {
+                        k = non_boundarychar;
+                    }
+                } else if (lt == LUA_TSTRING) {
+                    s = lua_tostring(L, -2);
+                    if (lua_key_eq(s, right_boundary)) {
+                        k = right_boundarychar;
+                        if (!has_right_boundary(f))
+                            set_right_boundary(f, get_charinfo(f, right_boundarychar));
+                    }
+                }
+                r = -1;
+                if (lua_istable(L, -1)) {
+                    r = lua_numeric_field_by_index(L, lua_key_index(char), -1);    /* ligature */
+                }
+                if (r != -1 && k != non_boundarychar) {
+                    t = n_enum_field(L, lua_key_index(type), 0, ligature_type_strings);
+                    set_ligature_item(cligs[ctr], (char) ((t * 2) + 1), k, r);
+                    ctr++;
+                } else {
+                    formatted_warning("font", "lua-loaded font %s char U+%X has an invalid ligature field",
+                        font_name(f), (int) i);
+                }
+                lua_pop(L, 1);      /* iterator value */
+            }
+            /* guard against empty tables */
+            if (ctr > 0) {
+                set_ligature_item(cligs[ctr], 0, end_ligature, 0);
+                set_charinfo_ligatures(co, cligs);
+            } else {
+                formatted_warning("font", "lua-loaded font %s char U+%X has an invalid ligatures field",
+                    font_name(f), (int) i);
+            }
+            lua_pop(L, 1);      /* ligatures table */
+        }
+    }
+}
+
+@ The caller has to fix the state of the lua stack when there is an error!
+
+ at c
+int font_from_lua(lua_State * L, int f)
+{
+    int i, n, r, t, lt;
+    int s_top;                  /* lua stack top */
+    int bc;                     /* first char index */
+    int ec;                     /* last char index */
+    char *s;
+    const char *ss;
+    int *l_fonts = NULL;
+    int save_ref ;
+    boolean no_math = false;
+
+    /* will we save a cache of the luat table? */
+
+    save_ref = 1; /* we start with  ss = "yes" */
+    ss = NULL;
+    ss = n_string_field(L, lua_key_index(cache));
+    if (lua_key_eq(ss, no))
+        save_ref = -1;
+    else if (lua_key_eq(ss, renew))
+        save_ref = 0;
+    /* n_string_field leaves a value on stack*/
+    lua_pop(L,1);
+
+    /* the table is at stack index -1 */
+
+    s = n_string_field_copy(L,lua_key_index(area), "");
+    set_font_area(f, s);
+    s = n_string_field_copy(L, lua_key_index(filename), NULL);
+    set_font_filename(f, s);
+    s = n_string_field_copy(L, lua_key_index(encodingname), NULL);
+    set_font_encodingname(f, s);
+
+    s = n_string_field_copy(L, lua_key_index(name), NULL);
+    set_font_name(f, s);
+    s = n_string_field_copy(L, lua_key_index(fullname), font_name(f));
+    set_font_fullname(f, s);
+
+    if (s == NULL) {
+        formatted_error("font","lua-loaded font '%d' has no name!", f);
+        return false;
+    }
+    s = n_string_field_copy(L, lua_key_index(psname), NULL);
+    set_font_psname(f, s);
+
+    i = lua_numeric_field_by_index(L,lua_key_index(units_per_em), 0);
+    set_font_units_per_em(f, i);
+    i = lua_numeric_field_by_index(L,lua_key_index(designsize), 655360);
+    set_font_dsize(f, i);
+    i = lua_numeric_field_by_index(L,lua_key_index(size), font_dsize(f));
+    set_font_size(f, i);
+    set_font_checksum(f, (unsigned)(lua_unsigned_numeric_field_by_index(L,lua_key_index(checksum), 0))) ;
+    i = lua_numeric_field_by_index(L,lua_key_index(direction), 0);
+    set_font_natural_dir(f, i);
+    i = lua_numeric_field_by_index(L,lua_key_index(encodingbytes), 0);
+    set_font_encodingbytes(f, (char) i);
+    i = n_boolean_field(L,lua_key_index(oldmath), 0);
+    set_font_oldmath(f, i);
+    i = lua_numeric_field_by_index(L,lua_key_index(tounicode), 0);
+    set_font_tounicode(f, (char) i);
+
+    i = lua_numeric_field_by_index(L,lua_key_index(extend), 1000);
+    if (i < FONT_EXTEND_MIN)
+        i = FONT_EXTEND_MIN;
+    if (i > FONT_EXTEND_MAX)
+        i = FONT_EXTEND_MAX;
+    set_font_extend(f, i);
+    i = lua_numeric_field_by_index(L,lua_key_index(slant), 0);
+    if (i < FONT_SLANT_MIN)
+        i = FONT_SLANT_MIN;
+    if (i > FONT_SLANT_MAX)
+        i = FONT_SLANT_MAX;
+    set_font_slant(f, i);
+
+    i = lua_numeric_field_by_index(L,lua_key_index(hyphenchar), default_hyphen_char_par);
+    set_hyphen_char(f, i);
+    i = lua_numeric_field_by_index(L,lua_key_index(skewchar), default_skew_char_par);
+    set_skew_char(f, i);
+    i = n_boolean_field(L, lua_key_index(used), 0);
+    set_font_used(f, (char) i);
+
+    s = n_string_field_copy(L, lua_key_index(attributes), NULL);
+    if (s != NULL && strlen(s) > 0) {
+        i = maketexstring(s);
+        set_pdf_font_attr(f, i);
+    }
+    free(s);
+
+    i = n_enum_field(L, lua_key_index(type), unknown_font_type, font_type_strings);
+    set_font_type(f, i);
+    i = n_enum_field(L, lua_key_index(format), unknown_format, font_format_strings);
+    set_font_format(f, i);
+    i = n_enum_field(L, lua_key_index(writingmode), unknown_writingmode, font_writingmode_strings);
+    set_font_writingmode(f, i);
+    i = n_enum_field(L, lua_key_index(identity), unknown_identity, font_identity_strings);
+    set_font_identity(f, i);
+    i = n_enum_field(L, lua_key_index(embedding), unknown_embedding, font_embedding_strings);
+    set_font_embedding(f, i);
+    if (font_encodingbytes(f) == 0 && (font_format(f) == opentype_format || font_format(f) == truetype_format)) {
+        set_font_encodingbytes(f, 2);
+    }
+
+    /* now fetch the base fonts, if needed */
+    count_hash_items(L, fonts, n);
+    if (n > 0) {
+        /* font table still on stack */
+        l_fonts = xmalloc((unsigned) ((unsigned) (n + 2) * sizeof(int)));
+        memset(l_fonts, 0, (size_t) ((unsigned) (n + 2) * sizeof(int)));
+        for (i = 1; i <= n; i++) {
+            lua_rawgeti(L, -1, i);
+            if (lua_istable(L, -1)) {
+                lua_key_rawgeti(id);
+                if (lua_isnumber(L, -1)) {
+                    l_fonts[i] = (int) lua_tointeger(L, -1);
+                    if (l_fonts[i] == 0) {
+                        l_fonts[i] = (int) f;
+                    }
+                    lua_pop(L, 2); /* pop id and entry */
+                    continue;
+                }
+                lua_pop(L, 1); /* pop id */
+            };
+            ss = NULL;
+            if (lua_istable(L, -1)) {
+                ss = n_string_field(L, lua_key_index(name));
+                /* string is anchored */
+                lua_pop(L,1);
+            }
+            if (ss != NULL) {
+                t = lua_numeric_field_by_index(L, lua_key_index(size), -1000);
+                /* TODO: the stack is messed up, otherwise this explicit resizing would not be needed */
+                s_top = lua_gettop(L);
+                if (strcmp(font_name(f), ss) == 0)
+                    l_fonts[i] = f;
+                else
+                    l_fonts[i] = find_font_id(ss, t);
+                lua_settop(L, s_top);
+            } else {
+                formatted_error("font","invalid local font in lua-loaded font '%s'", font_name(f));
+            }
+            lua_pop(L, 1); /* pop list entry */
+        }
+        lua_pop(L, 1); /* pop font table */
+    } else if (font_type(f) == virtual_font_type) {
+        formatted_error("font","invalid local fonts in lua-loaded font '%s'", font_name(f));
+    } else {
+        l_fonts = xmalloc(3 * sizeof(int));
+        l_fonts[0] = 0;
+        l_fonts[1] = f;
+        l_fonts[2] = 0;
+    }
+
+    /* parameters */
+    no_math = n_boolean_field(L, lua_key_index(nomath), 0);
+    read_lua_parameters(L, f);
+    if (!no_math) {
+        read_lua_math_parameters(L, f);
+        if (n_boolean_field(L, lua_key_index(oldmath), 0)) {
+            set_font_oldmath(f,true);
+        }
+
+    } else {
+        set_font_oldmath(f,true);
+    }
+    read_lua_cidinfo(L, f);
+
+    /* characters */
+    lua_key_rawgeti(characters);
+    if (lua_istable(L, -1)) {
+        /* find the array size values */
+        int num = 0;            /* number of charinfo's to add */
+        ec = 0;
+        bc = -1;
+        lua_pushnil(L);         /* first key */
+        while (lua_next(L, -2) != 0) {
+            if (lua_isnumber(L, -2)) {
+                i = (int) lua_tointeger(L, -2);
+                if (i >= 0) {
+                    if (lua_istable(L, -1)) {
+                        num++;
+                        if (i > ec)
+                            ec = i;
+                        if (bc < 0)
+                            bc = i;
+                        if (bc >= 0 && i < bc)
+                            bc = i;
+                    }
+                }
+            }
+            lua_pop(L, 1);
+        }
+        if (bc != -1) {
+            font_malloc_charinfo(f, num);
+            set_font_bc(f, bc);
+            set_font_ec(f, ec);
+            lua_pushnil(L);     /* first key */
+            while (lua_next(L, -2) != 0) {
+                lt = lua_type(L,-2);
+                if (lt == LUA_TNUMBER) {
+                    i = (int) lua_tointeger(L, -2);
+                    if (i >= 0) {
+                        font_char_from_lua(L, f, i, l_fonts, !no_math);
+                    }
+                } else if (lt == LUA_TSTRING) {
+                    const char *ss1 = lua_tostring(L, -2);
+                    if (lua_key_eq(ss1, left_boundary)) {
+                        font_char_from_lua(L, f, left_boundarychar, l_fonts,
+                                           !no_math);
+                    } else if (lua_key_eq(ss1, right_boundary)) {
+                        font_char_from_lua(L, f, right_boundarychar, l_fonts,
+                                           !no_math);
+                    }
+                }
+                lua_pop(L, 1);
+            }
+            lua_pop(L, 1);
+
+            /*
+                Handle font expansion last: the |copy_font| routine is called eventually,
+                and that needs to know |bc| and |ec|.
+            */
+
+            if (font_type(f) != virtual_font_type) {
+                int fstep = lua_numeric_field_by_index(L, lua_key_index(step), 0);
+                if (fstep < 0)
+                    fstep = 0;
+                if (fstep > 100)
+                    fstep = 100;
+                if (fstep != 0) {
+                    int fshrink = lua_numeric_field_by_index(L, lua_key_index(shrink), 0);
+                    int fstretch =lua_numeric_field_by_index(L, lua_key_index(stretch), 0);
+                    int fexpand = n_boolean_field(L, lua_key_index(auto_expand), 0);
+                    if (fshrink < 0)
+                        fshrink = 0;
+                    if (fshrink > 500)
+                        fshrink = 500;
+                    fshrink -= (fshrink % fstep);
+                    if (fshrink < 0)
+                        fshrink = 0;
+                    if (fstretch < 0)
+                        fstretch = 0;
+                    if (fstretch > 1000)
+                        fstretch = 1000;
+                    fstretch -= (fstretch % fstep);
+                    if (fstretch < 0)
+                        fstretch = 0;
+                    set_expand_params(f, fexpand, fstretch, fshrink, fstep);
+                }
+            }
+
+        } else {
+            /* jikes, no characters */
+            formatted_warning("font","lua-loaded font '%d' with name '%s' has no characters", f, font_name(f));
+        }
+
+        if (save_ref > 0) {
+            r = luaL_ref(L, LUA_REGISTRYINDEX); /* pops the table */
+            set_font_cache_id(f, r);
+        } else {
+            lua_pop(L, 1);
+            set_font_cache_id(f, save_ref);
+        }
+    } else {
+        /* jikes, no characters */
+        formatted_warning("font","lua-loaded font '%d' with name '%s' has no character table", f, font_name(f));
+    }
+    if (l_fonts != NULL)
+        free(l_fonts);
+    return true;
+}
+
+@* Ligaturing.
+
+ at c
+static void nesting_append(halfword nest1, halfword newn)
+{
+    halfword tail = tlink(nest1);
+    if (tail == null) {
+        couple_nodes(nest1, newn);
+    } else {
+        couple_nodes(tail, newn);
+    }
+    tlink(nest1) = newn;
+}
+
+static void nesting_prepend(halfword nest1, halfword newn)
+{
+    halfword head = vlink(nest1);
+    couple_nodes(nest1, newn);
+    if (head == null) {
+        tlink(nest1) = newn;
+    } else {
+        couple_nodes(newn, head);
+    }
+}
+
+static void nesting_prepend_list(halfword nest1, halfword newn)
+{
+    halfword head = vlink(nest1);
+    couple_nodes(nest1, newn);
+    if (head == null) {
+        tlink(nest1) = tail_of_list(newn);
+    } else {
+        halfword tail = tail_of_list(newn);
+        couple_nodes(tail, head);
+    }
+}
+
+static int test_ligature(liginfo * lig, halfword left, halfword right)
+{
+    if (type(left) != glyph_node)
+        return 0;
+    if (font(left) != font(right))
+        return 0;
+    if (is_ghost(left) || is_ghost(right))
+        return 0;
+    *lig = get_ligature(font(left), character(left), character(right));
+    if (is_valid_ligature(*lig)) {
+        return 1;
+    }
+    return 0;
+}
+
+static int try_ligature(halfword * frst, halfword fwd)
+{
+    halfword cur = *frst;
+    liginfo lig;
+    if (test_ligature(&lig, cur, fwd)) {
+        int move_after = (lig_type(lig) & 0x0C) >> 2;
+        int keep_right = ((lig_type(lig) & 0x01) != 0);
+        int keep_left = ((lig_type(lig) & 0x02) != 0);
+        halfword newgl = raw_glyph_node();
+        font(newgl) = font(cur);
+        character(newgl) = lig_replacement(lig);
+        set_is_ligature(newgl);
+        /*
+            below might not be correct in contrived border case.
+            but we use it only for debugging, so ...
+        */
+        if (character(cur) < 0) {
+            set_is_leftboundary(newgl);
+        }
+        if (character(fwd) < 0) {
+            set_is_rightboundary(newgl);
+        }
+        if (character(cur) < 0) {
+            if (character(fwd) < 0) {
+                build_attribute_list(newgl);
+            } else {
+                add_node_attr_ref(node_attr(fwd));
+                node_attr(newgl) = node_attr(fwd);
+            }
+        } else {
+            add_node_attr_ref(node_attr(cur));
+            node_attr(newgl) = node_attr(cur);
+        }
+
+        /*
+            TODO/FIXME if this ligature is consists of another ligature
+            we should add it's |lig_ptr| to the new glyphs |lig_ptr| (and
+            cleanup the no longer needed node) LOW PRIORITY
+        */
+        /* left side */
+        if (keep_left) {
+            halfword new_first = copy_node(cur);
+            lig_ptr(newgl) = new_first;
+            couple_nodes(cur, newgl);
+            if (move_after) {
+                move_after--;
+                cur = newgl;
+            }
+        } else {
+            halfword prev = alink(cur);
+            uncouple_node(cur);
+            lig_ptr(newgl) = cur;
+            couple_nodes(prev, newgl);
+            cur = newgl;        /* as cur has disappeared */
+        }
+        /* right side */
+        if (keep_right) {
+            halfword new_second = copy_node(fwd);
+            /* correct, because we {\it know\/} |lig_ptr| points to {\it one\/} node */
+            couple_nodes(lig_ptr(newgl), new_second);
+            couple_nodes(newgl, fwd);
+            if (move_after) {
+                move_after--;
+                cur = fwd;
+            }
+        } else {
+            halfword next = vlink(fwd);
+            uncouple_node(fwd);
+            /* correct, because we {\it know\/} |lig_ptr| points to {\it one\/} node */
+            couple_nodes(lig_ptr(newgl), fwd);
+            if (next != null) {
+                couple_nodes(newgl, next);
+            }
+        }
+        /* check and return */
+        *frst = cur;
+        return 1;
+    }
+    return 0;
+}
+
+@ there shouldn't be any ligatures here - we only add them at the end of
+ |xxx_break| in a \.{DISC-1 - DISC-2} situation and we stop processing \.{DISC-1}
+ (we continue with \.{DISC-1}'s |post_| and |no_break|.
+
+ at c
+static halfword handle_lig_nest(halfword root, halfword cur)
+{
+    if (cur == null)
+        return root;
+    while (vlink(cur) != null) {
+        halfword fwd = vlink(cur);
+        if (type(cur) == glyph_node && type(fwd) == glyph_node &&
+                font(cur) == font(fwd) && try_ligature(&cur, fwd)) {
+            continue;
+        }
+        cur = vlink(cur);
+    }
+    tlink(root) = cur;
+    return root;
+}
+
+static halfword handle_lig_word(halfword cur)
+{
+    halfword right = null;
+    if (type(cur) == boundary_node) {
+        halfword prev = alink(cur);
+        halfword fwd = vlink(cur);
+        /* no need to uncouple |cur|, it is freed */
+        flush_node(cur);
+        if (fwd == null) {
+            vlink(prev) = fwd;
+            return prev;
+        }
+        couple_nodes(prev, fwd);
+        if (type(fwd) != glyph_node)
+            return prev;
+        cur = fwd;
+    } else if (has_left_boundary(font(cur))) {
+        halfword prev = alink(cur);
+        halfword p = new_glyph(font(cur), left_boundarychar);
+        couple_nodes(prev, p);
+        couple_nodes(p, cur);
+        cur = p;
+    }
+    if (has_right_boundary(font(cur))) {
+        right = new_glyph(font(cur), right_boundarychar);
+    }
+    while (1) {
+        /* A glyph followed by ... */
+        if (type(cur) == glyph_node) {
+            halfword fwd = vlink(cur);
+            if (fwd == null) {  /* last character of paragraph */
+                if (right == null)
+                    break;
+                /* \.{--\\par} prohibits use of |couple_nodes| here */
+                try_couple_nodes(cur, right);
+                right = null;
+                continue;
+            }
+            if (type(fwd) == glyph_node) {      /* |GLYPH - GLYPH| */
+                if (font(cur) != font(fwd))
+                    break;
+                if (try_ligature(&cur, fwd))
+                    continue;
+            } else if (type(fwd) == disc_node) {        /* |GLYPH - DISC| */
+
+                /* if  \.{a{bx}{}{y}} and \.{a+b=>B} convert to \.{{Bx}{}{ay}} */
+                halfword pre = vlink_pre_break(fwd);
+                halfword nob = vlink_no_break(fwd);
+                halfword next, tail;
+                liginfo lig;
+                /* Check on: a{b?}{?}{?} and a+b=>B : {B?}{?}{a?} */
+                /* Check on: a{?}{?}{b?} and a+b=>B : {a?}{?}{B?} */
+                if ((pre != null && type(pre) == glyph_node
+                     && test_ligature(&lig, cur, pre))
+                    || (nob != null && type(nob) == glyph_node
+                        && test_ligature(&lig, cur, nob))) {
+                    /* move cur from before disc, to skipped part */
+                    halfword prev = alink(cur);
+                    uncouple_node(cur);
+                    couple_nodes(prev, fwd);
+                    nesting_prepend(no_break(fwd), cur);
+                    /* now ligature the |pre_break| */
+                    nesting_prepend(pre_break(fwd), copy_node(cur));
+                    /* As we have removed cur, we need to start again ... */
+                    cur = prev;
+                }
+                /* Check on: a{?}{?}{}b and a+b=>B : {a?}{?b}{B} */
+                next = vlink(fwd);
+                if (nob == null && next != null && type(next) == glyph_node
+                    && test_ligature(&lig, cur, next)) {
+                    /* move |cur| from before |disc| to |no_break| part */
+                    halfword prev = alink(cur);
+                    uncouple_node(cur);
+                    couple_nodes(prev, fwd);
+                    couple_nodes(no_break(fwd), cur);   /* we {\it know\/} it's empty */
+                    /* now copy cur the |pre_break| */
+                    nesting_prepend(pre_break(fwd), copy_node(cur));
+                    /* move next from after disc to |no_break| part */
+                    tail = vlink(next);
+                    uncouple_node(next);
+                    try_couple_nodes(fwd, tail);
+                    couple_nodes(cur, next);    /* we {\it know\/} this works */
+                    tlink(no_break(fwd)) = next;        /* and make sure the list is correct */
+                    /* now copy next to the |post_break| */
+                    nesting_append(post_break(fwd), copy_node(next));
+                    /* As we have removed cur, we need to start again ... */
+                    cur = prev;
+                }
+                /* we are finished with the |pre_break| */
+                handle_lig_nest(pre_break(fwd), vlink_pre_break(fwd));
+            } else if (type(fwd) == boundary_node) {
+                halfword next = vlink(fwd);
+                try_couple_nodes(cur, next);
+                flush_node(fwd);
+                if (right != null) {
+                    flush_node(right);  /* Shame, didn't need it */
+                    /* no need to reset |right|, we're going to leave the loop anyway */
+                }
+                break;
+            } else {
+                /* fwd is something unknown */
+                if (right == null)
+                    break;
+                couple_nodes(cur, right);
+                couple_nodes(right, fwd);
+                right = null;
+                continue;
+            }
+            /* A discretionary followed by ... */
+        } else if (type(cur) == disc_node) {
+            /* If \.{{?}{x}{?}} or \.{{?}{?}{y}} then ... */
+            if (vlink_no_break(cur) != null || vlink_post_break(cur) != null) {
+                halfword prev = 0;
+                halfword fwd;
+                liginfo lig;
+                if (subtype(cur) == select_disc) {
+                    prev = alink(cur);
+                    if (vlink_post_break(cur) != null)
+                        handle_lig_nest(post_break(prev), vlink_post_break(prev));
+                    if (vlink_no_break(cur) != null)
+                        handle_lig_nest(no_break(prev), vlink_no_break(prev));
+                }
+                if (vlink_post_break(cur) != null)
+                    handle_lig_nest(post_break(cur), vlink_post_break(cur));
+                if (vlink_no_break(cur) != null)
+                    handle_lig_nest(no_break(cur), vlink_no_break(cur));
+                while ((fwd = vlink(cur)) != null) {
+                    halfword nob, pst, next;
+                    if (type(fwd) != glyph_node)
+                        break;
+                    if (subtype(cur) != select_disc) {
+                        nob = tlink_no_break(cur);
+                        pst = tlink_post_break(cur);
+                        if ((nob == null || !test_ligature(&lig, nob, fwd)) &&
+                            (pst == null || !test_ligature(&lig, pst, fwd)))
+                            break;
+                        nesting_append(no_break(cur), copy_node(fwd));
+                        handle_lig_nest(no_break(cur), nob);
+                    } else {
+                        int dobreak = 0;
+                        nob = tlink_no_break(prev);
+                        pst = tlink_post_break(prev);
+                        if ((nob == null || !test_ligature(&lig, nob, fwd)) &&
+                            (pst == null || !test_ligature(&lig, pst, fwd)))
+                            dobreak = 1;
+                        if (!dobreak) {
+                            nesting_append(no_break(prev), copy_node(fwd));
+                            handle_lig_nest(no_break(prev), nob);
+                            nesting_append(post_break(prev), copy_node(fwd));
+                            handle_lig_nest(post_break(prev), pst);
+                        }
+                        dobreak = 0;
+                        nob = tlink_no_break(cur);
+                        pst = tlink_post_break(cur);
+                        if ((nob == null || !test_ligature(&lig, nob, fwd)) &&
+                            (pst == null || !test_ligature(&lig, pst, fwd)))
+                            dobreak = 1;
+                        if (!dobreak) {
+                            nesting_append(no_break(cur), copy_node(fwd));
+                            handle_lig_nest(no_break(cur), nob);
+                        }
+                        if (dobreak)
+                            break;
+                    }
+                    next = vlink(fwd);
+                    uncouple_node(fwd);
+                    try_couple_nodes(cur, next);
+                    nesting_append(post_break(cur), fwd);
+                    handle_lig_nest(post_break(cur), pst);
+                }
+                if (fwd != null && type(fwd) == disc_node) {
+                        halfword next = vlink(fwd);
+                        if (vlink_no_break(fwd) == null
+                        && vlink_post_break(fwd) == null
+                        && next != null
+                        && type(next) == glyph_node
+                        && ((tlink_post_break(cur) != null && test_ligature(&lig, tlink_post_break(cur), next)) ||
+                            (tlink_no_break  (cur) != null && test_ligature(&lig, tlink_no_break  (cur), next)))) {
+                        /* Building an |init_disc| followed by a |select_disc|
+                          \.{{a-}{b}{AB} {-}{}{}} 'c'
+                         */
+                        /* is it tail necessary ? */
+                        halfword last1 = vlink(next), tail ;
+                        uncouple_node(next);
+                        try_couple_nodes(fwd, last1);
+                        /* \.{{a-}{b}{AB} {-}{c}{}} */
+                        nesting_append(post_break(fwd), copy_node(next));
+                        /* \.{{a-}{b}{AB} {-}{c}{-}} */
+                        if (vlink_no_break(cur) != null) {
+                            nesting_prepend(no_break(fwd), copy_node(vlink_pre_break(fwd)));
+                        }
+                        /* \.{{a-}{b}{AB} {b-}{c}{-}} */
+                        if (vlink_post_break(cur) != null)
+                            nesting_prepend_list(pre_break(fwd), copy_node_list(vlink_post_break(cur)));
+                        /* \.{{a-}{b}{AB} {b-}{c}{AB-}} */
+                        if (vlink_no_break(cur) != null) {
+                            nesting_prepend_list(no_break(fwd), copy_node_list(vlink_no_break(cur)));
+                        }
+                        /* \.{{a-}{b}{ABC} {b-}{c}{AB-}} */
+                        tail = tlink_no_break(cur);
+                        nesting_append(no_break(cur), copy_node(next));
+                        handle_lig_nest(no_break(cur), tail);
+                        /* \.{{a-}{BC}{ABC} {b-}{c}{AB-}} */
+                        tail = tlink_post_break(cur);
+                        nesting_append(post_break(cur), next);
+                        handle_lig_nest(post_break(cur), tail);
+                        /* and set the subtypes */
+                        subtype(cur) = init_disc;
+                        subtype(fwd) = select_disc;
+                    }
+                }
+            }
+
+        } else {
+            /* NO GLYPH NOR DISC */
+            return cur;
+        }
+        /* step-to-next-node */
+        /* \.{--\\par} allows |vlink(cur)| to be null */
+        cur = vlink(cur);
+    }
+
+    return cur;
+}
+
+@ Return value is the new tail, head should be a dummy
+
+ at c
+halfword handle_ligaturing(halfword head, halfword tail)
+{
+    halfword save_tail1 = null; /* trick to allow explicit |node==null| tests */
+    halfword cur, prev;
+
+    if (vlink(head) == null)
+        return tail;
+    if (tail != null) {
+        save_tail1 = vlink(tail);
+        vlink(tail) = null;
+    }
+
+    /* |if (fix_node_lists)| */
+    fix_node_list(head);
+
+    prev = head;
+    cur = vlink(prev);
+
+    while (cur != null) {
+        if (type(cur) == glyph_node || (type(cur) == boundary_node)) {
+            cur = handle_lig_word(cur);
+        }
+        prev = cur;
+        cur = vlink(cur);
+    }
+    if (prev == null) {
+        /* hh: looks bad to me */
+        prev = tail;
+    }
+
+    if (tail != null) {
+        try_couple_nodes(prev, save_tail1);
+    }
+    return prev;
+}
+
+
+@* Kerning.
+
+ at c
+static void add_kern_before(halfword left, halfword right)
+{
+    if ((!is_rightghost(right)) &&
+        font(left) == font(right) && has_kern(font(left), character(left))) {
+        int k = raw_get_kern(font(left), character(left), character(right));
+        if (k != 0) {
+            halfword kern = new_kern(k);
+            halfword prev = alink(right);
+            couple_nodes(prev, kern);
+            couple_nodes(kern, right);
+            /* update the attribute list (inherit from left) */
+            delete_attribute_ref(node_attr(kern));
+            add_node_attr_ref(node_attr(left));
+            node_attr(kern) = node_attr(left);
+        }
+    }
+}
+
+static void add_kern_after(halfword left, halfword right, halfword aft)
+{
+    if ((!is_rightghost(right)) &&
+        font(left) == font(right) && has_kern(font(left), character(left))) {
+        int k = raw_get_kern(font(left), character(left), character(right));
+        if (k != 0) {
+            halfword kern = new_kern(k);
+            halfword next = vlink(aft);
+            couple_nodes(aft, kern);
+            try_couple_nodes(kern, next);
+            /* update the attribute list (inherit from left == aft) */
+            delete_attribute_ref(node_attr(kern));
+            add_node_attr_ref(node_attr(aft));
+            node_attr(kern) = node_attr(aft);
+        }
+    }
+}
+
+static void do_handle_kerning(halfword root, halfword init_left, halfword init_right)
+{
+    halfword cur = vlink(root);
+    halfword left = null;
+    if (cur == null) {
+        if (init_left != null && init_right != null) {
+            add_kern_after(init_left, init_right, root);
+            tlink(root) = vlink(root);
+        }
+        return;
+    }
+    if (type(cur) == glyph_node) {
+        set_is_glyph(cur);
+        if (init_left != null)
+            add_kern_before(init_left, cur);
+        left = cur;
+    }
+    while ((cur = vlink(cur)) != null) {
+        if (type(cur) == glyph_node) {
+            set_is_glyph(cur);
+            if (left != null) {
+                add_kern_before(left, cur);
+                if (character(left) < 0 || is_ghost(left)) {
+                    halfword prev = alink(left);
+                    couple_nodes(prev, cur);
+                    flush_node(left);
+                }
+            }
+            left = cur;
+        } else {
+            if (type(cur) == disc_node) {
+                halfword right = type(vlink(cur)) == glyph_node ? vlink(cur) : null;
+                do_handle_kerning(pre_break(cur), left, null);
+                if (vlink_pre_break(cur) != null)
+                    tlink_pre_break(cur) = tail_of_list(vlink_pre_break(cur));
+                do_handle_kerning(post_break(cur), null, right);
+                if (vlink_post_break(cur) != null)
+                    tlink_post_break(cur) = tail_of_list(vlink_post_break(cur));
+                do_handle_kerning(no_break(cur), left, right);
+                if (vlink_no_break(cur) != null)
+                    tlink_no_break(cur) = tail_of_list(vlink_no_break(cur));    /* needed? */
+            }
+            if (left != null) {
+                if (character(left) < 0 || is_ghost(left)) {
+                    halfword prev = alink(left);
+                    couple_nodes(prev, cur);
+                    flush_node(left);
+                }
+                left = null;
+            }
+        }
+    }
+    if (left != null) {
+        if (init_right != null)
+            add_kern_after(left, init_right, left);
+        if (character(left) < 0 || is_ghost(left)) {
+            halfword prev = alink(left);
+            halfword next = vlink(left);
+            if (next != null) {
+                couple_nodes(prev, next);
+                tlink(root) = next;
+            } else if (prev != root) {
+                vlink(prev) = null;
+                tlink(root) = prev;
+            } else {
+                vlink(root) = null;
+                tlink(root) = null;
+            }
+            flush_node(left);
+        }
+    }
+}
+
+/*
+halfword handle_kerning(halfword head, halfword tail)
+{
+    halfword save_link;
+    save_link = vlink(tail);
+    vlink(tail) = null;
+    tlink(head) = tail;
+    do_handle_kerning(head, null, null);
+    tail = tlink(head);
+    if (valid_node(save_link)) {
+        try_couple_nodes(tail, save_link);
+    }
+    return tail;
+}
+*/
+
+halfword handle_kerning(halfword head, halfword tail)
+{
+    halfword save_link = null;
+    if (tail == null) {
+        tlink(head) = null;
+        do_handle_kerning(head, null, null);
+    } else {
+        save_link = vlink(tail);
+        vlink(tail) = null;
+        tlink(head) = tail;
+        do_handle_kerning(head, null, null);
+        tail = tlink(head);
+        if (valid_node(save_link)) {
+            try_couple_nodes(tail, save_link);
+        }
+    }
+    return tail;
+}
+
+@* ligaturing and kerning : lua-interface.
+
+ at c
+static halfword run_lua_ligkern_callback(halfword head, halfword tail, int callback_id)
+{
+    int i;
+    int top = lua_gettop(Luas);
+    if (!get_callback(Luas, callback_id)) {
+        lua_pop(Luas, 2);
+        return tail;
+    }
+    nodelist_to_lua(Luas, head);
+    nodelist_to_lua(Luas, tail);
+    if ((i=lua_pcall(Luas, 2, 0, 0)) != 0) {
+        luatex_error(Luas, (i == LUA_ERRRUN ? 0 : 1));
+        return tail;
+    }
+    fix_node_list(head);
+    lua_settop(Luas, top);
+    return tail;
+}
+
+halfword new_ligkern(halfword head, halfword tail)
+{
+    int callback_id = 0;
+    if (vlink(head) == null)
+        return tail;
+    callback_id = callback_defined(ligaturing_callback);
+    if (callback_id > 0) {
+        tail = run_lua_ligkern_callback(head, tail, callback_id);
+        if (tail == null)
+            tail = tail_of_list(head);
+    } else if (callback_id == 0) {
+        tail = handle_ligaturing(head, tail);
+    }
+    callback_id = callback_defined(kerning_callback);
+    if (callback_id > 0) {
+        tail = run_lua_ligkern_callback(head, tail, callback_id);
+        if (tail == null) {
+            tail = tail_of_list(head);
+        }
+    } else if (callback_id == 0) {
+        halfword nest1 = new_node(nesting_node, 1);
+        halfword cur = vlink(head);
+        halfword aft = vlink(tail);
+        couple_nodes(nest1, cur);
+        tlink(nest1) = tail;
+        vlink(tail) = null;
+        do_handle_kerning(nest1, null, null);
+        couple_nodes(head, vlink(nest1));
+        tail = tlink(nest1);
+        try_couple_nodes(tail, aft);
+        flush_node(nest1);
+    }
+    return tail;
+}

Modified: trunk/Build/source/texk/web2c/luatexdir/lang/texlang.w
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/lang/texlang.w	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/lang/texlang.w	2017-03-11 00:31:06 UTC (rev 43454)
@@ -321,13 +321,8 @@
     load_hyphenation(get_language(curlang), (unsigned char *) s);
 }
 
-@ TODO: clean this up. The |delete_attribute_ref()| statements are not very nice,
-but needed. Also, in the post-break, it would be nicer to get the attribute list
-from |vlink(n)|. No rush, as it is currently not used much.
-
- at c
-halfword insert_discretionary(halfword t, halfword pre, halfword post,
-                              halfword replace, int penalty)
+@ @c
+halfword insert_discretionary(halfword t, halfword pre, halfword post, halfword replace, int penalty)
 {
     halfword g, n;
     int f;
@@ -417,24 +412,21 @@
     return n;
 }
 
-halfword insert_word_discretionary(halfword t, lang_variables * lan)
-{
-    halfword pre = null, pos = null;
-    if (lan->pre_exhyphen_char > 0)
-        pre = insert_character(null, lan->pre_exhyphen_char);
-    if (lan->post_exhyphen_char > 0)
-        pos = insert_character(null, lan->post_exhyphen_char);
-    return insert_discretionary(t, pre, pos, null,ex_hyphen_penalty_par);
-}
-
 @ @c
 halfword compound_word_break(halfword t, int clang)
 {
-    int disc;
-    lang_variables langdata;
-    langdata.pre_exhyphen_char = get_pre_exhyphen_char(clang);
-    langdata.post_exhyphen_char = get_post_exhyphen_char(clang);
-    disc = insert_word_discretionary(t, &langdata);
+    halfword disc = null;
+    halfword pre = null;
+    halfword pos = null;
+    halfword pre_exhyphen_char = get_pre_exhyphen_char(clang);
+    halfword post_exhyphen_char = get_post_exhyphen_char(clang);
+    if (pre_exhyphen_char > 0)
+        pre = insert_character(null,pre_exhyphen_char);
+    if (post_exhyphen_char > 0)
+        pos = insert_character(null,post_exhyphen_char);
+    disc = insert_discretionary(t,pre,pos,null,ex_hyphen_penalty_par);
+    subtype(disc) = automatic_disc;
+    set_automatic_disc_penalty(disc);
     return disc;
 }
 
@@ -443,7 +435,7 @@
                                       halfword replace)
 {
     (void) lan;
-    return insert_discretionary(t, pre, pos, replace,hyphen_penalty_par);
+    return insert_discretionary(t,pre,pos,replace,hyphen_penalty_par);
 }
 
 halfword insert_character(halfword t, int c)
@@ -775,8 +767,7 @@
                     */
                     t = vlink(r) ;
                     if ((start_ok == 0) && (t!=null) && (type(t) == glyph_node) && (character(t) != ex_hyphen_char_par)) {
-                        t = compound_word_break(r, char_lang(r));
-                        subtype(t) = automatic_disc;
+                        compound_word_break(r, char_lang(r));
                         start_ok = 1 ;
                     } else {
                         start_ok = 0;
@@ -866,7 +857,7 @@
 
     assert(tail != null);
     save_tail1 = vlink(tail);
-    s = new_penalty(0);
+    s = new_penalty(0,word_penalty);
     couple_nodes(tail, s);
 
     while (r != null) {         /* could be while(1), but let's be paranoid */
@@ -947,7 +938,6 @@
                     set of explicit hyphens
                 */
                 halfword rr = r;
-                halfword t = null;
 #ifdef VERBOSE
                 formatted_warning("hyphenation","explicit hyphen(s) found in %s (c=%d)", utf8word, clang);
 #endif
@@ -954,8 +944,7 @@
                 while (rr != wordstart) {
                 if (is_simple_character(rr)) {
                         if (character(rr) == ex_hyphen_char_par) {
-                            t = compound_word_break(rr, clang);
-                            subtype(t) = automatic_disc;
+                            compound_word_break(rr, clang);
                             while (character(alink(rr)) == ex_hyphen_char_par)
                                 rr = alink(rr);
                             if (rr == wordstart)
@@ -990,13 +979,13 @@
                     /* what is right overruns left .. a bit messy */
                 }
                 /* maybe an extra check ... */
-                /* if (left && right) { */
+                /* |if (left && right) {| */
 #ifdef VERBOSE
                     formatted_warning("hyphenation","hyphenate %s (c=%d,l=%d,r=%d) from %c to %c",
                         utf8word, clang, lhmin, rhmin, character(left), character(right));
 #endif
                     (void) hnj_hyphen_hyphenate(lang->patterns, wordstart, end_word, wordlen, left, right, &langdata);
-                /* } */
+                /* |}| */
             }
         }
         explicit_hyphen = false;

Added: trunk/Build/source/texk/web2c/luatexdir/lua/helpers.w
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/lua/helpers.w	                        (rev 0)
+++ trunk/Build/source/texk/web2c/luatexdir/lua/helpers.w	2017-03-11 00:31:06 UTC (rev 43454)
@@ -0,0 +1,26 @@
+% helpers.w
+%
+% Copyright 2017 LuaTeX team <bugs@@luatex.org>
+%
+% 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 <http://www.gnu.org/licenses/>.
+
+@ We will move here some helpers common to code that are now in weird places,
+probably organized in texhelpers, luahelpers, pdfhelpers.
+
+
+
+ at c
+/* to fill... */

Modified: trunk/Build/source/texk/web2c/luatexdir/lua/lauxlib_bridge.h
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/lua/lauxlib_bridge.h	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/lua/lauxlib_bridge.h	2017-03-11 00:31:06 UTC (rev 43454)
@@ -23,10 +23,6 @@
 /* http://lua.2524044.n2.nabble.com/converting-modules-in-written-in-C-for-Lua-5-1-to-Lua-5-2-td7642941.html */
 #define luaL_setmetatable(L, tname) luaL_getmetatable(L, tname);lua_setmetatable(L, -2);
 
-/* see http://comments.gmane.org/gmane.comp.programming.swig/18673 */
-/*
-# define lua_rawlen lua_objlen 
-*/
 
 
 LUALIB_API void *(luaL_testudata) (lua_State *L, int ud, const char *tname);
@@ -39,23 +35,7 @@
 #define lua_unlock(L)   ((void) 0)
 #endif
 
-#define luaL_newlibtable(L,l)   \
-  lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1)
-
+#define luaL_newlibtable(L,l)   lua_createtable(L, 0, sizeof(l)/sizeof((l)[0]) - 1)
 #define luaL_newlib(L,l)        (luaL_newlibtable(L,l), luaL_setfuncs(L,l,0))
 
-/*  Is this ok as replacement for lua_copy of lua 5.2.4 ? */
-/*
-#if defined(LUAJIT)
-LUA_API void lua_copy (lua_State *L, int fromidx, int toidx) {
-  TValue *fr, *to;
-  lua_lock(L);
-  fr = index2adr(L, fromidx);
-  to = index2adr(L, toidx);
-  api_checkvalidindex(L, to);
-  copyTV(L,to,fr);
-  lua_unlock(L);
-}
 #endif
-*/
-#endif

Modified: trunk/Build/source/texk/web2c/luatexdir/lua/lepdflib.cc
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/lua/lepdflib.cc	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/lua/lepdflib.cc	2017-03-11 00:31:06 UTC (rev 43454)
@@ -3484,11 +3484,6 @@
     setfuncs_meta(TextSpan);
     setfuncs_meta(XRef);
     setfuncs_meta(XRefEntry);
-
-#ifdef LuajitTeX
     luaL_register(L, "epdf", epdflib_f);
-#else
-    luaL_newlib(L, epdflib_f);
-#endif
     return 1;
 }

Modified: trunk/Build/source/texk/web2c/luatexdir/lua/limglib.c
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/lua/limglib.c	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/lua/limglib.c	2017-03-11 00:31:06 UTC (rev 43454)
@@ -761,12 +761,11 @@
     luaL_register(L, NULL, img_m);
     luaL_newmetatable(L, TYPE_IMG_DICT);
     luaL_register(L, NULL, img_dict_m);
-    luaL_register(L, "img", imglib_f);
 #else
     luaL_setfuncs(L, img_m, 0);
     luaL_newmetatable(L, TYPE_IMG_DICT);
     luaL_setfuncs(L, img_dict_m, 0);
-    luaL_newlib(L, imglib_f);
 #endif
+    luaL_register(L, "img", imglib_f);
     return 1;
 }

Modified: trunk/Build/source/texk/web2c/luatexdir/lua/liolibext.c
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/lua/liolibext.c	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/lua/liolibext.c	2017-03-11 00:31:06 UTC (rev 43454)
@@ -15,18 +15,13 @@
    License for more details.
 
    You should have received a copy of the GNU General Public License along
-   with LuaTeX; if not, see <http://www.gnu.org/licenses/>. */
+   with LuaTeX; if not, see <http://www.gnu.org/licenses/>.
 
-/* This extension only replaces one function: io.popen() and two
-   metatable entries: f:read() and f:lines() but unfortunately
-   it has to copy quite a bunch of lines from the original liolib.c
-   to do so.
 */
 
 #include "ptexlib.h"
 #include "lua/luatex-api.h"
 
-
 #ifdef LuajitTeX
 #include "lua/lauxlib_bridge.h"
 #else
@@ -36,6 +31,10 @@
 
 
 
+#ifdef LuajitTeX
+/* luajit has its own way for io, which is a mix of    */
+/* lua 5.1 and lua 5.2 . We use the stock luajit.      */    
+#else
 /*
 ** {======================================================
 ** lua_popen spawns a new process connected to the current
@@ -46,17 +45,17 @@
 #if defined(_WIN32)
 
 #ifdef _MSC_VER
-#define lua_popen(L,c,m)		((void)L, win32_popen(c,m))
-#define lua_pclose(L,file)		((void)L, win32_pclose(file))
+#define lua_popen(L,c,m)                ((void)L, win32_popen(c,m))
+#define lua_pclose(L,file)              ((void)L, win32_pclose(file))
 #else
-#define lua_popen(L,c,m)		((void)L, _popen(c,m))
-#define lua_pclose(L,file)		((void)L, _pclose(file))
+#define lua_popen(L,c,m)                ((void)L, _popen(c,m))
+#define lua_pclose(L,file)              ((void)L, _pclose(file))
 #endif
 
 #else
 
-#define lua_popen(L,c,m)	((void)L, fflush(NULL), popen(c,m))
-#define lua_pclose(L,file)	((void)L, pclose(file))
+#define lua_popen(L,c,m)        ((void)L, fflush(NULL), popen(c,m))
+#define lua_pclose(L,file)      ((void)L, pclose(file))
 
 #endif
 
@@ -65,17 +64,17 @@
 
 #if defined(LUA_USE_POSIX)
 
-#define l_fseek(f,o,w)		fseeko(f,o,w)
-#define l_ftell(f)		ftello(f)
-#define l_seeknum		off_t
+#define l_fseek(f,o,w)          fseeko(f,o,w)
+#define l_ftell(f)              ftello(f)
+#define l_seeknum               off_t
 
 #elif defined(LUA_WIN) && !defined(_CRTIMP_TYPEINFO) \
    && defined(_MSC_VER) && (_MSC_VER >= 1400)
 /* Windows (but not DDK) and Visual C++ 2005 or higher */
 
-#define l_fseek(f,o,w)		_fseeki64(f,o,w)
-#define l_ftell(f)		_ftelli64(f)
-#define l_seeknum		__int64
+#define l_fseek(f,o,w)          _fseeki64(f,o,w)
+#define l_ftell(f)              _ftelli64(f)
+#define l_seeknum               __int64
 
 #elif defined(__MINGW32__)
 
@@ -85,637 +84,371 @@
 
 #else
 
-#define l_fseek(f,o,w)		fseek(f,o,w)
-#define l_ftell(f)		ftell(f)
-#define l_seeknum		long
+#define l_fseek(f,o,w)          fseek(f,o,w)
+#define l_ftell(f)              ftell(f)
+#define l_seeknum               long
 
 #endif
 
-#define IO_PREFIX	"_IO_"
-#define IO_INPUT	(IO_PREFIX "input")
-#define IO_OUTPUT	(IO_PREFIX "output")
+#endif/* #ifdef LuajitTeX */
 
-typedef luaL_Stream LStream;
 
-#define tolstream(L)	((LStream *)luaL_checkudata(L, 1, LUA_FILEHANDLE))
 
-#define isclosed(p)	((p)->closef == NULL)
 
-static int io_type (lua_State *L) {
-  LStream *p;
-  luaL_checkany(L, 1);
-  p = (LStream *)luaL_testudata(L, 1, LUA_FILEHANDLE);
-  if (p == NULL)
-    lua_pushnil(L);  /* not a file */
-  else if (isclosed(p))
-    lua_pushliteral(L, "closed file");
-  else
-    lua_pushliteral(L, "file");
-  return 1;
-}
-
-
-static int f_tostring (lua_State *L) {
-  LStream *p = tolstream(L);
-  if (isclosed(p))
-    lua_pushliteral(L, "file (closed)");
-  else
-    lua_pushfstring(L, "file (%p)", p->f);
-  return 1;
-}
-
 static FILE *tofile (lua_State *L) {
-  LStream *p = tolstream(L);
-  if (isclosed(p))
-    luaL_error(L, "attempt to use a closed file");
-  lua_assert(p->f);
-  return p->f;
+#ifdef LuajitTeX
+    FILE **f = luaL_checkudata(L,1,LUA_FILEHANDLE);
+    if (*f == NULL)
+        luaL_error(L,"attempt to use a closed file");
+    return *f;
+#else
+    luaL_Stream *p = ((luaL_Stream *)luaL_checkudata(L, 1, LUA_FILEHANDLE));
+    if ((p)->closef == NULL)
+        luaL_error(L, "attempt to use a closed file");
+    lua_assert(p->f);
+    return p->f;
+#endif
 }
 
-
 /*
-** When creating file handles, always creates a `closed' file handle
-** before opening the actual file; so, if there is a memory error, the
-** file is not left opened.
+    HH: A few helpers to avoid reading numbers as strings. For now we put them in their
+    own namespace. We also have a few helpers that can make io functions tex friendly.
 */
-static LStream *newprefile (lua_State *L) {
-  LStream *p = (LStream *)lua_newuserdata(L, sizeof(LStream));
-  p->closef = NULL;  /* mark file handle as 'closed' */
-  luaL_setmetatable(L, LUA_FILEHANDLE);
-  return p;
+
+static int readcardinal1(lua_State *L) {
+    FILE *f = tofile(L);
+    int a = getc(f);
+    if (a == EOF)
+        lua_pushnil(L);
+    else
+        lua_pushinteger(L, a);
+    return 1;
 }
 
-
-static int aux_close (lua_State *L) {
-  LStream *p = tolstream(L);
-  lua_CFunction cf = p->closef;
-  p->closef = NULL;  /* mark stream as closed */
-  return (*cf)(L);  /* close it */
+static int readcardinal2(lua_State *L) {
+    FILE *f = tofile(L);
+    int a = getc(f);
+    int b = getc(f);
+    if (b == EOF)
+        lua_pushnil(L);
+    else
+        /* (a<<8) | b */
+        lua_pushinteger(L, 0x100 * a + b);
+    return 1;
 }
 
-
-static int io_close (lua_State *L) {
-  if (lua_isnone(L, 1))  /* no argument? */
-    lua_getfield(L, LUA_REGISTRYINDEX, IO_OUTPUT);  /* use standard output */
-  tofile(L);  /* make sure argument is an open stream */
-  return aux_close(L);
+static int readcardinal3(lua_State *L) {
+    FILE *f = tofile(L);
+    int a = getc(f);
+    int b = getc(f);
+    int c = getc(f);
+    if (c == EOF)
+        lua_pushnil(L);
+    else
+        /* (a<<16) | (b<<8) | c */
+        lua_pushinteger(L, 0x10000 * a + 0x100 * b + c);
+    return 1;
 }
 
-static int f_gc (lua_State *L) {
-  LStream *p = tolstream(L);
-  if (!isclosed(p) && p->f != NULL)
-    aux_close(L);  /* ignore closed and incompletely open files */
-  return 0;
+static int readcardinal4(lua_State *L) {
+    FILE *f = tofile(L);
+    int a = getc(f);
+    int b = getc(f);
+    int c = getc(f);
+    int d = getc(f);
+    if (d == EOF)
+        lua_pushnil(L);
+    else
+        /* (a<<24) | (b<<16) | (c<<8) | d */
+        lua_pushinteger(L,0x1000000 * a + 0x10000 * b + 0x100 * c + d);
+    return 1;
 }
 
-
-/*
-** function to close regular files
-*/
-static int io_fclose (lua_State *L) {
-  LStream *p = tolstream(L);
-  int res = fclose(p->f);
-  return luaL_fileresult(L, (res == 0), NULL);
+static int readinteger1(lua_State *L) {
+    FILE *f = tofile(L);
+    int a = getc(f);
+    if (a == EOF)
+        lua_pushnil(L);
+    else if (a >= 0x80)
+        lua_pushinteger(L, a - 0x100);
+    else
+        lua_pushinteger(L, a);
+    return 1;
 }
 
-
-static LStream *newfile (lua_State *L) {
-  LStream *p = newprefile(L);
-  p->f = NULL;
-  p->closef = &io_fclose;
-  return p;
+static int readinteger2(lua_State *L) {
+    FILE *f = tofile(L);
+    int a = getc(f);
+    int b = getc(f);
+    if (b == EOF)
+        lua_pushnil(L);
+    else if (a >= 0x80)
+        lua_pushinteger(L, 0x100 * a + b - 0x10000);
+    else
+        lua_pushinteger(L, 0x100 * a + b);
+    return 1;
 }
 
-
-static void opencheck (lua_State *L, const char *fname, const char *mode) {
-  LStream *p = newfile(L);
-  p->f = fopen(fname, mode);
-  if (p->f == NULL) {
-    luaL_error(L, "cannot open file " LUA_QS " (%s)", fname, strerror(errno));
-  } else {
-#ifdef WIN32
-    _setmode (fileno (p->f), _O_BINARY);
-#endif
-    if (mode[0]=='r')
-       recorder_record_input(fname);
+static int readinteger3(lua_State *L) {
+    FILE *f = tofile(L);
+    int a = getc(f);
+    int b = getc(f);
+    int c = getc(f);
+    if (c == EOF)
+        lua_pushnil(L);
+    else if (a >= 0x80)
+        lua_pushinteger(L, 0x10000 * a + 0x100 * b + c - 0x1000000);
     else
-       recorder_record_output(fname);
-  }
+        lua_pushinteger(L, 0x10000 * a + 0x100 * b + c);
+    return 1;
 }
 
-
-static int io_open (lua_State *L) {
-  const char *filename = luaL_checkstring(L, 1);
-  const char *mode = luaL_optstring(L, 2, "r");
-  LStream *p = newfile(L);
-  int i = 0;
-  /* check whether 'mode' matches '[rwa]%+?b?' */
-  if (!(mode[i] != '\0' && strchr("rwa", mode[i++]) != NULL &&
-       (mode[i] != '+' || ++i) &&  /* skip if char is '+' */
-       (mode[i] != 'b' || ++i) &&  /* skip if char is 'b' */
-       (mode[i] == '\0')))
-    return luaL_error(L, "invalid mode " LUA_QS
-                         " (should match " LUA_QL("[rwa]%%+?b?") ")", mode);
-  p->f = fopen(filename, mode);
-  if (p->f == NULL) {
-      return luaL_fileresult(L, 0, filename) ;
-  } else {
-#ifdef WIN32
-      _setmode (fileno (p->f), _O_BINARY);
-#endif
-      if (mode[0]=='r')
-	  recorder_record_input(filename);
-      else
-	  recorder_record_output(filename);
-      return 1;
-  }
+static int readinteger4(lua_State *L) {
+    FILE *f = tofile(L);
+    int a = getc(f);
+    int b = getc(f);
+    int c = getc(f);
+    int d = getc(f);
+    if (d == EOF)
+        lua_pushnil(L);
+    else if (a >= 0x80)
+        lua_pushinteger(L, 0x1000000 * a + 0x10000 * b + 0x100 * c + d - 0x100000000);
+    else
+        lua_pushinteger(L, 0x1000000 * a + 0x10000 * b + 0x100 * c + d);
+    return 1;
 }
 
-/*
-** function to close 'popen' files
-*/
-static int io_pclose (lua_State *L) {
-  LStream *p = tolstream(L);
-  return luaL_execresult(L, lua_pclose(L, p->f));
+static int readfixed2(lua_State *L) {
+    FILE *f = tofile(L);
+    int a = getc(f);
+    int b = getc(f);
+    if (b == EOF)
+        lua_pushnil(L);
+    else if (a >= 0x80)
+        lua_pushinteger(L, a + b/0xFFFF - 0x100);
+    else
+        lua_pushinteger(L, a + b/0xFFFF);
+    return 1;
 }
 
-static int io_popen(lua_State * L)
-{
-    int ret = 1;
-    char *safecmd = NULL;
-    char *cmdname = NULL;
-    int allow = 0;
-    const char *filename = luaL_checkstring(L, 1);
-    const char *mode = luaL_optstring(L, 2, "r");
-    LStream *p = newprefile(L);
-
-    if (shellenabledp <= 0) {
+static int readfixed4(lua_State *L) {
+    FILE *f = tofile(L);
+    int a = getc(f);
+    int b = getc(f);
+    int c = getc(f);
+    int d = getc(f);
+    if (d == EOF)
         lua_pushnil(L);
-        lua_pushliteral(L, "All command execution is disabled");
-        return 2;
-    }
-    /* If restrictedshell == 0, any command is allowed. */
-    if (restrictedshell == 0)
-        allow = 1;
+    else if (a >= 0x80)
+        lua_pushnumber(L, (0x1000000 * a + 0x10000 * b + 0x100 * c + d - 0x100000000)/65536.0);
     else
-        allow = shell_cmd_is_allowed(filename, &safecmd, &cmdname);
+        lua_pushnumber(L, (0x1000000 * a + 0x10000 * b + 0x100 * c + d)/65536.0);
+    /* from ff */
+    /* int n = 0x1000000 * a + 0x10000 * b + 0x100 * c + d; */
+    /* lua_pushnumber(L,(real) (n>>16) + ((n&0xffff)/65536.0)); */
+    return 1;
+}
 
-    if (allow == 1) {
-        p->f = lua_popen(L, filename, mode);
-	p->closef = &io_pclose;
-        ret = (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1;
-    } else if (allow == 2) {
-        p->f = lua_popen(L, safecmd, mode);
-	p->closef = &io_pclose;
-        ret = (p->f == NULL) ? luaL_fileresult(L, 0, filename) : 1;
-    } else if (allow == 0) {
+static int read2dot14(lua_State *L) {
+    FILE *f = tofile(L);
+    int a = getc(f);
+    int b = getc(f);
+    if (b == EOF) {
         lua_pushnil(L);
-        lua_pushliteral(L, "Command execution disabled via shell_escape='p'");
-        ret = 2;
     } else {
-        lua_pushnil(L);
-        lua_pushliteral(L, "Bad command line quoting");
-        ret = 2;
+        int n = 0x100 * a + b;
+        /* from ff */
+        lua_pushnumber(L,(real) ((n<<16)>>(16+14)) + ((n&0x3fff)/16384.0));
     }
-    return ret;
+    return 1;
 }
 
+static int getposition(lua_State *L) {
+    FILE *f = tofile(L);
+    long p = ftell(f);
+    if (p<0)
+        lua_pushnil(L);
+    else
+        lua_pushinteger(L, p);
+    return 1;
+}
 
-static int io_tmpfile (lua_State *L) {
-  LStream *p = newfile(L);
-  p->f = tmpfile();
-  return (p->f == NULL) ? luaL_fileresult(L, 0, NULL) : 1;
+static int setposition(lua_State *L) {
+    FILE *f = tofile(L);
+    long p = lua_tointeger(L,2);
+    p = fseek(f,p,SEEK_SET);
+    if (p<0)
+        lua_pushnil(L);
+    else
+        lua_pushinteger(L, p);
+    return 1;
 }
 
-
-static FILE *getiofile (lua_State *L, const char *findex) {
-  LStream *p;
-  lua_getfield(L, LUA_REGISTRYINDEX, findex);
-  p = (LStream *)lua_touserdata(L, -1);
-  if (isclosed(p))
-    luaL_error(L, "standard %s file is closed", findex + strlen(IO_PREFIX));
-  return p->f;
+static int skipposition(lua_State *L) {
+    FILE *f = tofile(L);
+    long p = lua_tointeger(L,2);
+    p = fseek(f,ftell(f)+p,SEEK_SET);
+    if (p<0)
+        lua_pushnil(L);
+    else
+        lua_pushinteger(L, p);
+    return 1;
 }
 
-
-static int g_iofile (lua_State *L, const char *f, const char *mode) {
-  if (!lua_isnoneornil(L, 1)) {
-    const char *filename = lua_tostring(L, 1);
-    if (filename)
-      opencheck(L, filename, mode);
-    else {
-      tofile(L);  /* check that it's a valid file handle */
-      lua_pushvalue(L, 1);
+static int readbytetable(lua_State *L) {
+    FILE *f = tofile(L);
+    int n = lua_tointeger(L,2);
+    int i ;
+    lua_createtable(L, n, 0);
+    for (i=1;i<=n;i++) {
+        int a = getc(f);
+        if (a == EOF) {
+            break;
+        } else {
+            /*
+                lua_pushinteger(L, i);
+                lua_pushinteger(L, a);
+                lua_rawset(L, -3);
+            */
+            lua_pushinteger(L, a);
+            lua_rawseti(L,-2,i);
+        }
     }
-    lua_setfield(L, LUA_REGISTRYINDEX, f);
-  }
-  /* return current value */
-  lua_getfield(L, LUA_REGISTRYINDEX, f);
-  return 1;
+    return 1;
 }
 
-
-static int io_input (lua_State *L) {
-  return g_iofile(L, IO_INPUT, "r");
+static int readbytes(lua_State *L) {
+    FILE *f = tofile(L);
+    int n = lua_tointeger(L,2);
+    int i = 0;
+    for (i=1;i<=n;i++) {
+        int a = getc(f);
+        if (a == EOF) {
+            return i-1;
+        } else {
+            lua_pushinteger(L, a);
+        }
+    }
+    return n;
 }
 
-
-static int io_output (lua_State *L) {
-  return g_iofile(L, IO_OUTPUT, "w");
+static int recordfilename(lua_State *L)
+{
+    const char *fname = luaL_checkstring(L, 1);
+    const char *ftype = lua_tostring(L, 2);
+    if (fname != NULL && ftype != NULL) {
+        switch (ftype[1]) {
+            case 'r':
+                recorder_record_input(fname);
+                break;
+            case 'w':
+                recorder_record_output(fname);
+                break;
+            default:
+                /* silently ignore */
+                break;
+        }
+    } else {
+        /* silently ignore */
+    }
+    return 0;
 }
 
-
-static int io_readline (lua_State *L);
-
-
-static void aux_lines (lua_State *L, int toclose) {
-  int i;
-  int n = lua_gettop(L) - 1;  /* number of arguments to read */
-  /* ensure that arguments will fit here and into 'io_readline' stack */
-  luaL_argcheck(L, n <= LUA_MINSTACK - 3, LUA_MINSTACK - 3, "too many options");
-  lua_pushvalue(L, 1);  /* file handle */
-  lua_pushinteger(L, n);  /* number of arguments to read */
-  lua_pushboolean(L, toclose);  /* close/not close file when finished */
-  for (i = 1; i <= n; i++) lua_pushvalue(L, i + 1);  /* copy arguments */
-  lua_pushcclosure(L, io_readline, 3 + n);
-}
-
-
-static int f_lines (lua_State *L) {
-  tofile(L);  /* check that it's a valid file handle */
-  aux_lines(L, 0);
-  return 1;
-}
-
-
-static int io_lines (lua_State *L) {
-  int toclose;
-  if (lua_isnone(L, 1)) lua_pushnil(L);  /* at least one argument */
-  if (lua_isnil(L, 1)) {  /* no file name? */
-    lua_getfield(L, LUA_REGISTRYINDEX, IO_INPUT);  /* get default input */
-    lua_replace(L, 1);  /* put it at index 1 */
-    tofile(L);  /* check that it's a valid file handle */
-    toclose = 0;  /* do not close it after iteration */
-  }
-  else {  /* open a new file */
+static int checkpermission(lua_State *L)
+{
     const char *filename = luaL_checkstring(L, 1);
-    opencheck(L, filename, "r");
-    lua_replace(L, 1);  /* put file at index 1 */
-    toclose = 1;  /* close it after iteration */
-  }
-  aux_lines(L, toclose);
-  return 1;
+    if (filename == NULL) {
+        lua_pushboolean(L,0);
+        lua_pushliteral(L,"no command name given");
+    } else if (shellenabledp <= 0) {
+        lua_pushboolean(L,0);
+        lua_pushliteral(L,"all command execution is disabled");
+    } else if (restrictedshell == 0) {
+        lua_pushboolean(L,1);
+        lua_pushliteral(L,"all commands are permitted");
+    } else {
+        char *safecmd = NULL;
+        char *cmdname = NULL;
+        switch (shell_cmd_is_allowed(filename, &safecmd, &cmdname)) {
+            case 0:
+                lua_pushboolean(L,0);
+                lua_pushliteral(L, "specific command execution disabled");
+                break;
+            case 1:
+                lua_pushboolean(L,1);
+                lua_pushstring(L,filename);
+                break;
+            case 2:
+                lua_pushboolean(L,1);
+                lua_pushstring(L,safecmd);
+                break;
+            default:
+                lua_pushboolean(L,0);
+                lua_pushliteral(L, "bad command line quoting");
+                break;
+        }
+    }
+    return 2;
 }
 
-/*
-** {======================================================
-** READ
-** =======================================================
-*/
-
-
-static int read_number (lua_State *L, FILE *f) {
-  lua_Number d;
-  if (fscanf(f, LUA_NUMBER_SCAN, &d) == 1) {
-    lua_pushnumber(L, d); /* integer or float */
-    return 1;
-  }
-  else {
-   lua_pushnil(L);  /* "result" to be removed */
-   return 0;  /* read fails */
-  }
-}
-
-
-static int test_eof (lua_State *L, FILE *f) {
-  int c = getc(f);
-  ungetc(c, f);
-  lua_pushlstring(L, NULL, 0);
-  return (c != EOF);
-}
-
-/* this version does not care wether the file has
-   line endings using an 'alien' convention */
-
-static int read_line(lua_State * L, FILE * f, int chop)
+static int readline(lua_State *L)
 {
     luaL_Buffer buf;
     int c, d;
+    FILE *f = tofile(L);
     luaL_buffinit(L, &buf);
     while (1) {
         c = fgetc(f);
         if (c == EOF) {
-            luaL_pushresult(&buf);      /* close buffer */
-            return (lua_rawlen(L, -1) > 0);     /* check whether read something */
-        };
-        if (c == '\n') {
-	    if (!chop)
-		luaL_addchar(&buf, c);
-            break;
+            luaL_pushresult(&buf);
+            if (lua_rawlen(L, -1) == 0) {
+                lua_pop(L, 1);
+                lua_pushnil(L);
+            }
+            return 1;
+        } else if (c == '\n') {
+            luaL_pushresult(&buf);
+            return 1;
         } else if (c == '\r') {
-	    if (!chop)
-		luaL_addchar(&buf, c);
             d = fgetc(f);
             if (d != EOF && d != '\n') {
                 ungetc(d, f);
-	    } else if (!chop) {
-		luaL_addchar(&buf, d);
-	    }
-            break;
+            }
+            luaL_pushresult(&buf);
+            return 1;
         } else {
             luaL_addchar(&buf, c);
         }
     }
-    luaL_pushresult(&buf);      /* close buffer */
-    return 1;
 }
 
-#define MAX_SIZE_T	(~(size_t)0)
+static const luaL_Reg fiolib[] = {
+    /* helpers */
+    { "readcardinal1",   readcardinal1 },
+    { "readcardinal2",   readcardinal2 },
+    { "readcardinal3",   readcardinal3 },
+    { "readcardinal4",   readcardinal4 },
+    { "readinteger1",    readinteger1 },
+    { "readinteger2",    readinteger2 },
+    { "readinteger3",    readinteger3 },
+    { "readinteger4",    readinteger4 },
+    { "readfixed2",      readfixed2 },
+    { "readfixed4",      readfixed4 },
+    { "read2dot14",      read2dot14 },
+    { "setposition",     setposition },
+    { "getposition",     getposition },
+    { "skipposition",    skipposition },
+    { "readbytes",       readbytes },
+    { "readbytetable",   readbytetable },
+    { "readline",        readline },
+    /* extras */
+    { "recordfilename",  recordfilename },
+    { "checkpermission", checkpermission },
+    /* done */
+    {NULL, NULL}
+};
 
-static void read_all (lua_State *L, FILE *f) {
-  size_t rlen = LUAL_BUFFERSIZE;  /* how much to read in each cycle */
-  l_seeknum old, nrlen = 0;  /* for testing file size */
-  luaL_Buffer b;
-  luaL_buffinit(L, &b);
-  /* speed up loading of not too large files: */
-  old = l_ftell(f);
-  if ((l_fseek(f, 0, SEEK_END) >= 0) &&
-      ((nrlen = l_ftell(f)) > 0) && nrlen < 1000 * 1000 * 100) {
-      rlen = nrlen;
-  }
-  l_fseek(f, old, SEEK_SET);
-  for (;;) {
-    char *p = luaL_prepbuffsize(&b, rlen);
-    size_t nr = fread(p, sizeof(char), rlen, f);
-    luaL_addsize(&b, nr);
-    if (nr < rlen) break;  /* eof? */
-    else if (rlen <= (MAX_SIZE_T / 4))  /* avoid buffers too large */
-      rlen *= 2;  /* double buffer size at each iteration */
-  }
-  luaL_pushresult(&b);  /* close buffer */
-}
-
-
-static int read_chars (lua_State *L, FILE *f, size_t n) {
-  size_t nr;  /* number of chars actually read */
-  char *p;
-  luaL_Buffer b;
-  luaL_buffinit(L, &b);
-  p = luaL_prepbuffsize(&b, n);  /* prepare buffer to read whole block */
-  nr = fread(p, sizeof(char), n, f);  /* try to read 'n' chars */
-  luaL_addsize(&b, nr);
-  luaL_pushresult(&b);  /* close buffer */
-  return (nr > 0);  /* true iff read something */
-}
-
-
-static int g_read (lua_State *L, FILE *f, int first) {
-  int nargs = lua_gettop(L) - 1;
-  int success;
-  int n;
-  clearerr(f);
-  if (nargs == 0) {  /* no arguments? */
-    success = read_line(L, f, 1);
-    n = first+1;  /* to return 1 result */
-  }
-  else {  /* ensure stack space for all results and for auxlib's buffer */
-    luaL_checkstack(L, nargs+LUA_MINSTACK, "too many arguments");
-    success = 1;
-    for (n = first; nargs-- && success; n++) {
-      if (lua_type(L, n) == LUA_TNUMBER) {
-        size_t l = (size_t)lua_tointeger(L, n);
-        success = (l == 0) ? test_eof(L, f) : read_chars(L, f, l);
-      }
-      else {
-        const char *p = lua_tostring(L, n);
-        luaL_argcheck(L, p && p[0] == '*', n, "invalid option");
-        switch (p[1]) {
-          case 'n':  /* number */
-            success = read_number(L, f);
-            break;
-          case 'l':  /* line */
-            success = read_line(L, f, 1);
-            break;
-          case 'L':  /* line with end-of-line */
-            success = read_line(L, f, 0);
-            break;
-          case 'a':  /* file */
-            read_all(L, f);  /* read entire file */
-            success = 1; /* always success */
-            break;
-          default:
-            return luaL_argerror(L, n, "invalid format");
-        }
-      }
-    }
-  }
-  if (ferror(f))
-    return luaL_fileresult(L, 0, NULL);
-  if (!success) {
-    lua_pop(L, 1);  /* remove last result */
-    lua_pushnil(L);  /* push nil instead */
-  }
-  return n - first;
-}
-
-
-static int io_read (lua_State *L) {
-  return g_read(L, getiofile(L, IO_INPUT), 1);
-}
-
-static int f_read (lua_State *L) {
-  return g_read(L, tofile(L), 2);
-}
-
-
-static int io_readline (lua_State *L) {
-  LStream *p = (LStream *)lua_touserdata(L, lua_upvalueindex(1));
-  int i;
-  int n = (int)lua_tointeger(L, lua_upvalueindex(2));
-  if (isclosed(p))  /* file is already closed? */
-    return luaL_error(L, "file is already closed");
-  lua_settop(L , 1);
-  for (i = 1; i <= n; i++)  /* push arguments to 'g_read' */
-    lua_pushvalue(L, lua_upvalueindex(3 + i));
-  n = g_read(L, p->f, 2);  /* 'n' is number of results */
-  lua_assert(n > 0);  /* should return at least a nil */
-  if (!lua_isnil(L, -n))  /* read at least one value? */
-    return n;  /* return them */
-  else {  /* first result is nil: EOF or error */
-    if (n > 1) {  /* is there error information? */
-      /* 2nd result is error message */
-      return luaL_error(L, "%s", lua_tostring(L, -n + 1));
-    }
-    if (lua_toboolean(L, lua_upvalueindex(3))) {  /* generator created file? */
-      lua_settop(L, 0);
-      lua_pushvalue(L, lua_upvalueindex(1));
-      aux_close(L);  /* close it */
-    }
-    return 0;
-  }
-}
-
-/* }====================================================== */
-
-
-static int g_write (lua_State *L, FILE *f, int arg) {
-  int nargs = lua_gettop(L) - arg;
-  int status = 1;
-  for (; nargs--; arg++) {
-    if (lua_type(L, arg) == LUA_TNUMBER) {
-      /* optimization: could be done exactly as for strings */
-      status = status &&
-          fprintf(f, LUA_NUMBER_FMT, lua_tonumber(L, arg)) > 0;
-    }
-    else {
-      size_t l;
-      const char *s = luaL_checklstring(L, arg, &l);
-      status = status && (fwrite(s, sizeof(char), l, f) == l);
-    }
-  }
-  if (status) return 1;  /* file handle already on stack top */
-  else return luaL_fileresult(L, status, NULL);
-}
-
-
-static int io_write (lua_State *L) {
-  return g_write(L, getiofile(L, IO_OUTPUT), 1);
-}
-
-
-static int f_write (lua_State *L) {
-  FILE *f = tofile(L);
-  lua_pushvalue(L, 1);  /* push file at the stack top (to be returned) */
-  return g_write(L, f, 2);
-}
-
-
-static int f_seek (lua_State *L) {
-  static const int mode[] = {SEEK_SET, SEEK_CUR, SEEK_END};
-  static const char *const modenames[] = {"set", "cur", "end", NULL};
-  FILE *f = tofile(L);
-  int op = luaL_checkoption(L, 2, "cur", modenames);
-  lua_Number p3 = luaL_optnumber(L, 3, 0);
-  l_seeknum offset = (l_seeknum)p3;
-  luaL_argcheck(L, (lua_Number)offset == p3, 3,
-                  "not an integer in proper range");
-  op = l_fseek(f, offset, mode[op]);
-  if (op)
-    return luaL_fileresult(L, 0, NULL);  /* error */
-  else {
-    lua_pushinteger(L, (lua_Number)l_ftell(f));
+int luaopen_fio(lua_State *L) {
+    luaL_register(L, "fio", fiolib);
     return 1;
-  }
 }
-
-
-static int f_setvbuf (lua_State *L) {
-  int res;
-  static const int mode[] = {_IONBF, _IOFBF, _IOLBF};
-  static const char *const modenames[] = {"no", "full", "line", NULL};
-  FILE *f = tofile(L);
-  int op = luaL_checkoption(L, 2, NULL, modenames);
-  lua_Integer sz = luaL_optinteger(L, 3, LUAL_BUFFERSIZE);
-  if(sz == 0)
-    res = setvbuf(f, NULL, _IONBF, sz);
-  else
-    res = setvbuf(f, NULL, mode[op], sz);
-  return luaL_fileresult(L, res == 0, NULL);
-}
-
-
-
-static int io_flush (lua_State *L) {
-  return luaL_fileresult(L, fflush(getiofile(L, IO_OUTPUT)) == 0, NULL);
-}
-
-
-static int f_flush (lua_State *L) {
-  return luaL_fileresult(L, fflush(tofile(L)) == 0, NULL);
-}
-
-
-/*
-** functions for 'io' library
-*/
-static const luaL_Reg iolib[] = {
-  {"close", io_close},
-  {"flush", io_flush},
-  {"input", io_input},
-  {"lines", io_lines},
-  {"open", io_open},
-  {"output", io_output},
-  {"popen", io_popen},
-  {"read", io_read},
-  {"tmpfile", io_tmpfile},
-  {"type", io_type},
-  {"write", io_write},
-  {NULL, NULL}
-};
-
-
-/*
-** methods for file handles
-*/
-static const luaL_Reg flib[] = {
-  {"close", io_close},
-  {"flush", f_flush},
-  {"lines", f_lines},
-  {"read", f_read},
-  {"seek", f_seek},
-  {"setvbuf", f_setvbuf},
-  {"write", f_write},
-  {"__gc", f_gc},
-  {"__tostring", f_tostring},
-  {NULL, NULL}
-};
-
-
-#ifndef LuajitTeX
-static void createmeta (lua_State *L) {
-  luaL_newmetatable(L, LUA_FILEHANDLE);  /* create metatable for file handles */
-  lua_pushvalue(L, -1);  /* push metatable */
-  lua_setfield(L, -2, "__index");  /* metatable.__index = metatable */
-  luaL_setfuncs(L, flib, 0);  /* add file methods to new metatable */
-  lua_pop(L, 1);  /* pop new metatable */
-}
-
-
-/*
-** function to (not) close the standard files stdin, stdout, and stderr
-*/
-static int io_noclose (lua_State *L) {
-  LStream *p = tolstream(L);
-  p->closef = &io_noclose;  /* keep file opened */
-  lua_pushnil(L);
-  lua_pushliteral(L, "cannot close standard file");
-  return 2;
-}
-
-
-static void createstdfile (lua_State *L, FILE *f, const char *k,
-                           const char *fname) {
-  LStream *p = newprefile(L);
-  p->f = f;
-  p->closef = &io_noclose;
-  if (k != NULL) {
-    lua_pushvalue(L, -1);
-    lua_setfield(L, LUA_REGISTRYINDEX, k);  /* add file to registry */
-  }
-  lua_setfield(L, -2, fname);  /* add file to module */
-}
-#endif
-
-int open_iolibext (lua_State *L) {
-#ifdef LuajitTeX
-  return luaopen_io(L);
-#else
-  luaL_newlib(L, iolib);  /* new module */
-  createmeta(L);
-  /* create (and set) default files */
-  createstdfile(L, stdin, IO_INPUT, "stdin");
-  createstdfile(L, stdout, IO_OUTPUT, "stdout");
-  createstdfile(L, stderr, NULL, "stderr");
-  return 1;
-#endif
-}

Modified: trunk/Build/source/texk/web2c/luatexdir/lua/lnewtokenlib.c
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/lua/lnewtokenlib.c	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/lua/lnewtokenlib.c	2017-03-11 00:31:06 UTC (rev 43454)
@@ -797,6 +797,53 @@
 
 /* TODO: check for a quick way to set a macro to empty (HH) */
 
+static int get_meaning(lua_State * L)
+{
+    const char *name = null;
+    size_t lname = 0;
+    int cs, cmd;
+    if (lua_type(L, 1) == LUA_TSTRING) {
+        name = lua_tolstring(L, 1, &lname);
+        cs = string_lookup(name, lname);
+        cmd = eq_type(cs);
+        if (cmd >= call_cmd) {
+            int chr = equiv(cs);
+            char *str = tokenlist_to_cstring(chr, true, NULL);
+            lua_pushstring(L, str);
+            free(str);
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static int get_macro(lua_State * L)
+{
+    const char *name = null;
+    size_t lname = 0;
+    int cs, cmd;
+    if (lua_type(L, 1) == LUA_TSTRING) {
+        name = lua_tolstring(L, 1, &lname);
+        cs = string_lookup(name, lname);
+        cmd = eq_type(cs);
+        if (cmd >= call_cmd) {
+            /*
+                Expanding would expand in-place, unless we make a copy which we don't want to
+                do. So, we just pass the meaning i.e. no: expand_macros_in_tokenlist(chr).
+
+                Actually it would be nice to adapt tokenlist_to_cstring with an extra argument
+                indicating that we are not interested in the before -> part.
+            */
+            int chr = equiv(cs);
+            char *str = tokenlist_to_xstring(chr, true, NULL);
+            lua_pushstring(L, str);
+            free(str);
+            return 1;
+        }
+    }
+    return 0;
+}
+
 static int set_macro(lua_State * L)
 {
     const char *name = null;
@@ -950,6 +997,8 @@
     { "get_protected", lua_tokenlib_get_protected },
     /* maybe more setters */
     { "set_macro", set_macro },
+    { "get_macro", get_macro },
+    { "get_meaning", get_meaning },
     /* probably never */
  /* {"expand", run_expand},               */ /* does not work yet! */
  /* {"csname_id", run_get_csname_id},     */ /* yes or no */

Modified: trunk/Build/source/texk/web2c/luatexdir/lua/loslibext.c
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/lua/loslibext.c	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/lua/loslibext.c	2017-03-11 00:31:06 UTC (rev 43454)
@@ -894,11 +894,11 @@
 }
 #endif
 
-static const char repl[] = "0123456789abcdefghijklmnopqrstuvwxyz";
 
 #define MAXTRIES 36*36*36
 
 #ifndef HAVE_MKDTEMP
+static const char repl[] = "0123456789abcdefghijklmnopqrstuvwxyz";
 static int dirs_made = 0;
 
 static char *do_mkdtemp(char *tmpl)
@@ -1014,7 +1014,7 @@
 }
 
 
-void open_oslibext(lua_State * L, int safer)
+void open_oslibext(lua_State * L)
 {
 
     find_env(L);
@@ -1037,17 +1037,16 @@
     lua_setfield(L, -2, "gettimeofday");
 #endif
 
-    if (!safer) {
-        lua_pushcfunction(L, os_setenv);
-        lua_setfield(L, -2, "setenv");
-        lua_pushcfunction(L, os_exec);
-        lua_setfield(L, -2, "exec");
-        lua_pushcfunction(L, os_spawn);
-        lua_setfield(L, -2, "spawn");
-        lua_pushcfunction(L, os_execute);
-        lua_setfield(L, -2, "execute");
-        lua_pushcfunction(L, os_tmpdir);
-        lua_setfield(L, -2, "tmpdir");
-    }
+    lua_pushcfunction(L, os_setenv);
+    lua_setfield(L, -2, "setenv");
+    lua_pushcfunction(L, os_exec);
+    lua_setfield(L, -2, "exec");
+    lua_pushcfunction(L, os_spawn);
+    lua_setfield(L, -2, "spawn");
+    lua_pushcfunction(L, os_execute);
+    lua_setfield(L, -2, "execute");
+    lua_pushcfunction(L, os_tmpdir);
+    lua_setfield(L, -2, "tmpdir");
+
     lua_pop(L, 1);              /* pop the table */
 }

Modified: trunk/Build/source/texk/web2c/luatexdir/lua/lstatslib.c
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/lua/lstatslib.c	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/lua/lstatslib.c	2017-03-11 00:31:06 UTC (rev 43454)
@@ -121,8 +121,23 @@
     return engine_name;
 }
 
+static const char * get_lc_ctype(void)
+{
+    return lc_ctype;
+}
 
+static const char * get_lc_collate(void)
+{
+    return lc_collate;
+}
 
+static const char * get_lc_numeric(void)
+{
+    return lc_numeric;
+}
+
+
+
 static lua_Number get_luatexhashchars(void)
 {
   return (lua_Number) LUAI_HASHLIMIT;
@@ -216,14 +231,36 @@
 static lua_Number shell_escape_state(void)
 {
     if (shellenabledp <= 0) {
+        /* No shell at all. */
         return (lua_Number) 0;
     } else if (restrictedshell == 0) {
+        /* Shell has no restriction. */
+        return (lua_Number) 2;
+    } else {
+        /* Shell has restrictions, see cnf file. */
         return (lua_Number) 1;
+    }
+}
+
+static lua_Number safer_option_state(void)
+{
+    if (safer_option == 0) {
+        return (lua_Number) 0;
     } else {
-        return (lua_Number) 2;
+        return (lua_Number) 1;
     }
 }
 
+static lua_Number kpse_used_state(void)
+{
+    if (kpse_init == 1) {
+        return (lua_Number) 1;
+    } else {
+        return (lua_Number) 0;
+    }
+}
+
+
 /* temp, for backward compat */
 static int init_pool_ptr = 0;
 
@@ -255,7 +292,7 @@
     {"log_name", 'S', (void *) &getlogname},
     {"banner", 'S', (void *) &getbanner},
     {"luatex_version", 'G', &get_luatexversion},
-    {"luatex_revision", 'S', (void *) &luatexrevision}, 
+    {"luatex_revision", 'S', (void *) &luatexrevision},
     {"luatex_hashtype", 'S', (void *) &get_luatexhashtype},
     {"luatex_hashchars", 'N',  &get_luatexhashchars},
     {"luatex_engine", 'S', (void *) &getenginename},
@@ -262,7 +299,9 @@
 
     {"ini_version", 'b', &ini_version},
 
-    {"shell_escape", 'N', &shell_escape_state}, /* be easy on old time usage */
+    {"shell_escape", 'N', &shell_escape_state},
+    {"safer_option", 'N', &safer_option_state},
+    {"kpse_used", 'N', &kpse_used_state},
     /*
      * mem stat
      */
@@ -310,8 +349,13 @@
     {"luabytecode_bytes", 'g', &luabytecode_bytes},
     {"luastate_bytes", 'g', &luastate_bytes},
     {"callbacks", 'g', &callback_count},
+
     {"indirect_callbacks", 'g', &saved_callback_count},
 
+    {"lc_ctype", 'S', (void *) &get_lc_ctype},
+    {"lc_collate", 'S', (void *) &get_lc_collate},
+    {"lc_numeric",'S', (void *) &get_lc_numeric},
+
     {NULL, 0, 0}
 };
 

Modified: trunk/Build/source/texk/web2c/luatexdir/lua/lstrlibext.c
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/lua/lstrlibext.c	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/lua/lstrlibext.c	2017-03-11 00:31:06 UTC (rev 43454)
@@ -1,5 +1,5 @@
 /* lstrlibext.c
-   
+
    Copyright 2012 Taco Hoekwater <taco at luatex.org>
 
    This file is part of LuaTeX.
@@ -25,100 +25,71 @@
 
 #define LUA_CORE
 #include "lua.h"
+#ifdef LuajitTeX
+#include "lauxlib.h"
+#include "lualib.h"
+#else
 #include "luaconf.h"
 #include "lapi.h"
 #include "lundump.h"
+#endif
 
-
-static int str_split (lua_State *L) {
-  size_t l;
-  size_t i;
-  int n;
-  char *q, *p, *orig;
-  int mult = 0;
-  const char *s = luaL_checklstring(L, 1, &l);
-  const char *joiner = luaL_optstring(L, 2, " +");
-  lua_newtable(L);
-  if (l == 0) {
-	lua_pushvalue(L,1);
-	lua_rawseti(L,-2,1);
-	return 1;
-  }
-  orig = p = malloc(l+1);
-  if (p==NULL) {
-	fprintf(stderr, "fatal: memory exhausted (malloc of %u bytes).\n",(int)(l+1));
-	exit(EXIT_FAILURE);
-  }
-  strcpy(p,s);
-  n = 1;
-  q = p;
-
-  if (*joiner == 0) {
-	for (i=0;i<l;i++) {
-	  lua_pushlstring(L,q,1); q++;
-	  lua_rawseti(L,-2,n); n++;
+static int bytepairs_aux (lua_State *L) {
+  size_t ls;
+  unsigned char i;
+  const char *s = lua_tolstring(L, lua_upvalueindex(1), &ls);
+  int ind       = lua_tointeger(L, lua_upvalueindex(2));
+  if (ind<(int)ls) {
+	if (ind+1<(int)ls) {
+	  lua_pushinteger(L, (ind+2));  /* iterator */
+	} else {
+	  lua_pushinteger(L, (ind+1));  /* iterator */
 	}
-	free(orig);
-	return 1;
-  }
-  if (*(joiner+1) == '+') {
-	mult = 1;
-	while(*p==*joiner) {
-	  p++;
-	  l--;
+	lua_replace(L, lua_upvalueindex(2));
+	i = (unsigned char)*(s+ind);
+	lua_pushinteger(L, i);     /* byte one */
+	if (ind+1<(int)ls) {
+	  i = (unsigned char)*(s+ind+1);
+	  lua_pushinteger(L, i);     /* byte two */
+	} else {
+	  lua_pushnil(L);     /* odd string length */
 	}
-	q = p;
+	return 2;
   }
-  for (i=0;i<l;i++) {
-	if (*(p+i)==*joiner) {
-	  *(p+i) = 0;
-	  lua_pushlstring(L,q,((p+i)-q));
-	  lua_rawseti(L,-2,n); n++;
-	  if (mult) {
-		while(*(p+i+1)==*joiner) {
-		  i++;
-		}
-	  }
-	  q = p+i+1;
-	}
-  }
-  if (mult && q==(p+l)) {
-	free(orig);
-	return 1;
-  }
-  if(q<=(p+l)) {
-	lua_pushlstring(L,q,strlen(q));
-	lua_rawseti(L,-2,n);
-  }
-  free(orig);
+  return 0;  /* string ended */
+}
+
+static int str_bytepairs (lua_State *L) {
+  luaL_checkstring(L, 1);
+  lua_settop(L, 1);
+  lua_pushinteger(L, 0);
+  lua_pushcclosure(L, bytepairs_aux, 2);
   return 1;
-} 
+}
 
-static int characters_aux (lua_State *L) {
+static int bytes_aux (lua_State *L) {
   size_t ls;
-  char b[2];
+  unsigned char i;
   const char *s = lua_tolstring(L, lua_upvalueindex(1), &ls);
-  int ind  = lua_tointeger(L, lua_upvalueindex(2));
+  int ind       = lua_tointeger(L, lua_upvalueindex(2));
   if (ind<(int)ls) {
     lua_pushinteger(L, (ind+1));  /* iterator */
 	lua_replace(L, lua_upvalueindex(2));
-	b[0] = *(s+ind); b[1] = 0;
-	lua_pushlstring(L, b, 1);
+	i = (unsigned char)*(s+ind);
+	lua_pushinteger(L, i);     /* byte */
 	return 1;
   }
   return 0;  /* string ended */
 }
 
-
-static int str_characters (lua_State *L) {
+static int str_bytes (lua_State *L) {
   luaL_checkstring(L, 1);
   lua_settop(L, 1);
   lua_pushinteger(L, 0);
-  lua_pushcclosure(L, characters_aux, 2);
+  lua_pushcclosure(L, bytes_aux, 2);
   return 1;
 }
 
-
 static int utf_failed(lua_State *L, int new_ind) {
   static char fffd [3] = {0xEF,0xBF,0xBD};
   lua_pushinteger(L, new_ind);  /* iterator */
@@ -152,9 +123,8 @@
     }
   }
   return utf_failed(L,ind+1); /* we found a follow byte! */
-} 
+}
 
-
 static int str_utfcharacters (lua_State *L) {
   luaL_checkstring(L, 1);
   lua_settop(L, 1);
@@ -163,7 +133,6 @@
   return 1;
 }
 
-
 static int utfvalues_aux (lua_State *L) {
   size_t ls;
   unsigned char i = 0;
@@ -180,7 +149,7 @@
 	if (i<0x80) {
 	  v = i;
 	} else if (i>=0xF0) {
-	  if ((ind+3)<(int)ls && ((unsigned)*(s+ind+1))>=0x80 
+	  if ((ind+3)<(int)ls && ((unsigned)*(s+ind+1))>=0x80
 		  && ((unsigned)*(s+ind+2))>=0x80 && ((unsigned)*(s+ind+3))>=0x80) {
 		numbytes  = 4;
 		j = ((unsigned)*(s+ind+1))-128;
@@ -211,7 +180,6 @@
   return 0;  /* string ended */
 }
 
-
 static int str_utfvalues (lua_State *L) {
   luaL_checkstring(L, 1);
   lua_settop(L, 1);
@@ -220,8 +188,6 @@
   return 1;
 }
 
-
-
 static int characterpairs_aux (lua_State *L) {
   size_t ls;
   char b[2];
@@ -237,7 +203,7 @@
 	b[0] = *(s+ind); b[1] = 0;
 	lua_pushlstring(L, b, 1);
 	if (ind+1<(int)ls) {
-	  b[0] = *(s+ind+1); 
+	  b[0] = *(s+ind+1);
 	  lua_pushlstring(L, b, 1);
 	} else {
 	  lua_pushlstring(L, b+1, 0);
@@ -247,7 +213,6 @@
   return 0;  /* string ended */
 }
 
-
 static int str_characterpairs (lua_State *L) {
   luaL_checkstring(L, 1);
   lua_settop(L, 1);
@@ -256,65 +221,96 @@
   return 1;
 }
 
-static int bytes_aux (lua_State *L) {
+static int characters_aux (lua_State *L) {
   size_t ls;
-  unsigned char i;
+  char b[2];
   const char *s = lua_tolstring(L, lua_upvalueindex(1), &ls);
-  int ind       = lua_tointeger(L, lua_upvalueindex(2));
+  int ind  = lua_tointeger(L, lua_upvalueindex(2));
   if (ind<(int)ls) {
     lua_pushinteger(L, (ind+1));  /* iterator */
 	lua_replace(L, lua_upvalueindex(2));
-	i = (unsigned char)*(s+ind);
-	lua_pushinteger(L, i);     /* byte */
+	b[0] = *(s+ind); b[1] = 0;
+	lua_pushlstring(L, b, 1);
 	return 1;
   }
   return 0;  /* string ended */
 }
 
-static int str_bytes (lua_State *L) {
+static int str_characters (lua_State *L) {
   luaL_checkstring(L, 1);
   lua_settop(L, 1);
   lua_pushinteger(L, 0);
-  lua_pushcclosure(L, bytes_aux, 2);
+  lua_pushcclosure(L, characters_aux, 2);
   return 1;
 }
 
-static int bytepairs_aux (lua_State *L) {
-  size_t ls;
-  unsigned char i;
-  const char *s = lua_tolstring(L, lua_upvalueindex(1), &ls);
-  int ind       = lua_tointeger(L, lua_upvalueindex(2));
-  if (ind<(int)ls) {
-	if (ind+1<(int)ls) {
-	  lua_pushinteger(L, (ind+2));  /* iterator */
-	} else {
-	  lua_pushinteger(L, (ind+1));  /* iterator */
+static int str_split (lua_State *L) {
+  size_t l;
+  size_t i;
+  int n;
+  char *q, *p, *orig;
+  int mult = 0;
+  const char *s = luaL_checklstring(L, 1, &l);
+  const char *joiner = luaL_optstring(L, 2, " +");
+  lua_newtable(L);
+  if (l == 0) {
+	lua_pushvalue(L,1);
+	lua_rawseti(L,-2,1);
+	return 1;
+  }
+  orig = p = malloc(l+1);
+  if (p==NULL) {
+	fprintf(stderr, "fatal: memory exhausted (malloc of %u bytes).\n",(int)(l+1));
+	exit(EXIT_FAILURE);
+  }
+  strcpy(p,s);
+  n = 1;
+  q = p;
+
+  if (*joiner == 0) {
+	for (i=0;i<l;i++) {
+	  lua_pushlstring(L,q,1); q++;
+	  lua_rawseti(L,-2,n); n++;
 	}
-	lua_replace(L, lua_upvalueindex(2));
-	i = (unsigned char)*(s+ind);
-	lua_pushinteger(L, i);     /* byte one */
-	if (ind+1<(int)ls) {
-	  i = (unsigned char)*(s+ind+1);
-	  lua_pushinteger(L, i);     /* byte two */
-	} else {
-	  lua_pushnil(L);     /* odd string length */
+	free(orig);
+	return 1;
+  }
+  if (*(joiner+1) == '+') {
+	mult = 1;
+	while(*p==*joiner) {
+	  p++;
+	  l--;
 	}
-	return 2;
+	q = p;
   }
-  return 0;  /* string ended */
-}
-
-
-static int str_bytepairs (lua_State *L) {
-  luaL_checkstring(L, 1);
-  lua_settop(L, 1);
-  lua_pushinteger(L, 0);
-  lua_pushcclosure(L, bytepairs_aux, 2);
+  for (i=0;i<l;i++) {
+	if (*(p+i)==*joiner) {
+	  *(p+i) = 0;
+	  lua_pushlstring(L,q,((p+i)-q));
+	  lua_rawseti(L,-2,n); n++;
+	  if (mult) {
+		while(*(p+i+1)==*joiner) {
+		  i++;
+		}
+	  }
+	  q = p+i+1;
+	}
+  }
+  if (mult && q==(p+l)) {
+	free(orig);
+	return 1;
+  }
+  if(q<=(p+l)) {
+	lua_pushlstring(L,q,strlen(q));
+	lua_rawseti(L,-2,n);
+  }
+  free(orig);
   return 1;
 }
 
-
-
+#ifdef LuajitTeX
+    /* dump is built in */
+#else
 static int writer (lua_State *L, const void* b, size_t size, void* B) {
   (void)L;
   luaL_addlstring((luaL_Buffer*) B, (const char *)b, size);
@@ -349,9 +345,20 @@
   luaL_pushresult(&b);
   return 1;
 }
+#endif /*ifdef LuajitTeX*/
 
+static int str_bytetable (lua_State *L) {
+    size_t l;
+    int i;
+    const char *s = luaL_checklstring(L, 1, &l);
+    lua_createtable(L,l,0);
+    for (i=0;i<l;i++) {
+        lua_pushinteger(L,(unsigned char)*(s+i));
+        lua_rawseti(L,-2,i+1);
+    }
+    return 1;
+}
 
-
 static const luaL_Reg strlibext[] = {
   {"utfvalues", str_utfvalues},
   {"utfcharacters", str_utfcharacters},
@@ -359,8 +366,13 @@
   {"characterpairs", str_characterpairs},
   {"bytes", str_bytes},
   {"bytepairs", str_bytepairs},
+  {"bytetable", str_bytetable},
   {"explode", str_split},
+#ifdef LuajitTeX
+  /* luajit has dump built in */
+#else
   {"dump", str_dump},
+#endif
   {NULL, NULL}
 };
 
@@ -374,4 +386,3 @@
     }
     lua_pop(L,1);
 }
-

Modified: trunk/Build/source/texk/web2c/luatexdir/lua/luainit.w
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/lua/luainit.w	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/lua/luainit.w	2017-03-11 00:31:06 UTC (rev 43454)
@@ -1,1095 +1,1153 @@
-% luainit.w
-%
-% Copyright 2006-2017 Taco Hoekwater <taco@@luatex.org>
-%
-% 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 <http://www.gnu.org/licenses/>.
-
-@ @c
-
-#include "ptexlib.h"
-
-#include <kpathsea/c-stat.h>
-
-#include "lua/luatex-api.h"
-
-/* internalized strings: see luatex-api.h */
-set_make_keys;
-
-@
-This file is getting a bit messy, but it is not simple to fix unilaterally.
-
-Better to wait until Karl has some time (after texlive 2008) so we can
-synchronize with kpathsea. One problem, for instance, is that I would
-like to resolve the full executable path.  |kpse_set_program_name()| does
-that, indirectly (by setting SELFAUTOLOC in the environment), but it
-does much more, making it hard to use for our purpose.
-
-In fact, it sets three C variables:
-
-  |kpse_invocation_name|  |kpse_invocation_short_name|  |kpse->program_name|
-
-and five environment variables:
-
-  SELFAUTOLOC  SELFAUTODIR  SELFAUTOPARENT  SELFAUTOGRANDPARENT  progname
-
- at c
-const_string LUATEX_IHELP[] = {
-    "Usage: " my_name " --lua=FILE [OPTION]... [TEXNAME[.tex]] [COMMANDS]",
-    "   or: " my_name " --lua=FILE [OPTION]... \\FIRST-LINE",
-    "   or: " my_name " --lua=FILE [OPTION]... &FMT ARGS",
-    "  Run " MyName " on TEXNAME, usually creating TEXNAME.pdf.",
-    "  Any remaining COMMANDS are processed as luatex input, after TEXNAME is read.",
-    "",
-    "  Alternatively, if the first non-option argument begins with a backslash,",
-    "  " my_name " interprets all non-option arguments as an input line.",
-    "",
-    "  Alternatively, if the first non-option argument begins with a &, the",
-    "  next word is taken as the FMT to read, overriding all else.  Any",
-    "  remaining arguments are processed as above.",
-    "",
-    "  If no arguments or options are specified, prompt for input.",
-    "",
-    "  The following regular options are understood: ",
-    "",
-    "   --credits                     display credits and exit",
-    "   --debug-format                enable format debugging",
-    "   --draftmode                   switch on draft mode (generates no output PDF)",
-    "   --[no-]file-line-error        disable/enable file:line:error style messages",
-    "   --[no-]file-line-error-style  aliases of --[no-]file-line-error",
-    "   --fmt=FORMAT                  load the format file FORMAT",
-    "   --halt-on-error               stop processing at the first error",
-    "   --help                        display help and exit",
-    "   --ini                         be ini" my_name ", for dumping formats",
-    "   --interaction=STRING          set interaction mode (STRING=batchmode/nonstopmode/scrollmode/errorstopmode)",
-    "   --jobname=STRING              set the job name to STRING",
-    "   --kpathsea-debug=NUMBER       set path searching debugging flags according to the bits of NUMBER",
-    "   --lua=FILE                    load and execute a lua initialization script",
-    "   --[no-]mktex=FMT              disable/enable mktexFMT generation (FMT=tex/tfm)",
-    "   --nosocket                    disable the lua socket library",
-    "   --output-comment=STRING       use STRING for DVI file comment instead of date (no effect for PDF)",
-    "   --output-directory=DIR        use existing DIR as the directory to write files in",
-    "   --output-format=FORMAT        use FORMAT for job output; FORMAT is 'dvi' or 'pdf'",
-    "   --progname=STRING             set the program name to STRING",
-    "   --recorder                    enable filename recorder",
-    "   --safer                       disable easily exploitable lua commands",
-    "   --[no-]shell-escape           disable/enable system commands",
-    "   --shell-restricted            restrict system commands to a list of commands given in texmf.cnf",
-    "   --synctex=NUMBER              enable synctex",
-    "   --utc                         init time to UTC",
-    "   --version                     display version and exit",
-    "",
-    "Alternate behaviour models can be obtained by special switches",
-    "",
-    "  --luaonly                      run a lua file, then exit",
-    "  --luaconly                     byte-compile a lua file, then exit",
-    "  --luahashchars                 the bits used by current Lua interpreter for strings hashing",
-#ifdef LuajitTeX
-    "  --jiton                        turns the JIT compiler on (default off)",
-    "  --jithash=STRING               choose the hash function for the lua strings (lua51|luajit20: default lua51)",
-#endif
-    "",
-    "See the reference manual for more information about the startup process.",
-    NULL
-};
-
-/*
-    "   --8bit                        ignored, input is assumed to be in UTF-8 encoding",
-    "   --default-translate-file=FILE ignored, input is assumed to be in UTF-8 encoding",
-    "   --etex                        ignored, the etex extensions are always active",
-    "   --disable-write18             disable \\write18{SHELL COMMAND}",
-    "   --enable-write18              enable \\write18{SHELL COMMAND}",
-    "   --[no-]parse-first-line       ignored",
-    "   --translate-file=FILE         ignored, input is assumed to be in UTF-8 encoding",
-*/
-
-@ The return value will be the directory of the executable, e.g.: \.{c:/TeX/bin}
- at c
-static char *ex_selfdir(char *argv0)
-{
-#if defined(WIN32)
-#if defined(__MINGW32__)
-    char path[PATH_MAX], *fp;
-
-    /* SearchPath() always gives back an absolute directory */
-    if (SearchPath(NULL, argv0, ".exe", PATH_MAX, path, NULL) == 0)
-        FATAL1("Can't determine where the executable %s is.\n", argv0);
-    /* slashify the dirname */
-    for (fp = path; fp && *fp; fp++)
-        if (IS_DIR_SEP(*fp))
-            *fp = DIR_SEP;
-#else /* __MINGW32__ */
-#define PATH_MAX 512
-    char short_path[PATH_MAX], path[PATH_MAX], *fp;
-
-    /* SearchPath() always gives back an absolute directory */
-    if (SearchPath(NULL, argv0, ".exe", PATH_MAX, short_path, &fp) == 0)
-        FATAL1("Can't determine where the executable %s is.\n", argv0);
-    if (getlongpath(path, short_path, sizeof(path)) == 0) {
-        FATAL1("This path points to an invalid file : %s\n", short_path);
-    }
-#endif /* __MINGW32__ */
-   return xdirname(path);
-#else /* WIN32 */
-    return kpse_selfdir(argv0);
-#endif
-}
-
-@ @c
-static void prepare_cmdline(lua_State * L, char **av, int ac, int zero_offset)
-{
-    int i;
-    char *s;
-    luaL_checkstack(L, ac + 3, "too many arguments to script");
-    lua_createtable(L, 0, 0);
-    for (i = 0; i < ac; i++) {
-        lua_pushstring(L, av[i]);
-        lua_rawseti(L, -2, (i - zero_offset));
-    }
-    lua_setglobal(L, "arg");
-    lua_getglobal(L, "os");
-    s = ex_selfdir(argv[0]);
-    lua_pushstring(L, s);
-    xfree(s);
-    lua_setfield(L, -2, "selfdir");
-    return;
-}
-
-
-@ @c
-int kpse_init = -1;
-
-@ @c
-string input_name = NULL;
-
-static string user_progname = NULL;
-
-char *startup_filename = NULL;
-int lua_only = 0;
-int lua_offset = 0;
-unsigned char show_luahashchars = 0;
-
-#ifdef LuajitTeX
-int luajiton   = 0;
-char *jithash_hashname = NULL;
-#endif
-
-int safer_option = 0;
-int nosocket_option = 0;
-int utc_option = 0;
-
-@ Reading the options.
-
-@ Test whether getopt found an option ``A''.
-Assumes the option index is in the variable |option_index|, and the
-option table in a variable |long_options|.
-
- at c
-#define ARGUMENT_IS(a) STREQ (long_options[option_index].name, a)
-
-/*
-    SunOS cc can't initialize automatic structs, so make this static.
-*/
-
-/*
-    Nota Bene: we still intercept some options that other engines handle
-    so that existing scripted usage will not fail.
-*/
-
-static struct option long_options[] = {
-    {"fmt", 1, 0, 0},
-    {"lua", 1, 0, 0},
-    {"luaonly", 0, 0, 0},
-    {"luahashchars", 0, 0, 0},
-#ifdef LuajitTeX
-    {"jiton", 0, 0, 0},
-    {"jithash", 1, 0, 0},
-#endif
-    {"safer", 0, &safer_option, 1},
-    {"utc", 0, &utc_option, 1},
-    {"nosocket", 0, &nosocket_option, 1},
-    {"help", 0, 0, 0},
-    {"ini", 0, &ini_version, 1},
-    {"interaction", 1, 0, 0},
-    {"halt-on-error", 0, &haltonerrorp, 1},
-    {"kpathsea-debug", 1, 0, 0},
-    {"progname", 1, 0, 0},
-    {"version", 0, 0, 0},
-    {"credits", 0, 0, 0},
-    {"recorder", 0, 0, 0},
-    {"etex", 0, 0, 0},
-    {"output-comment", 1, 0, 0},
-    {"output-directory", 1, 0, 0},
-    {"draftmode", 0, 0, 0},
-    {"output-format", 1, 0, 0},
-    {"shell-escape", 0, &shellenabledp, 1},
-    {"no-shell-escape", 0, &shellenabledp, -1},
-    {"enable-write18", 0, &shellenabledp, 1},
-    {"disable-write18", 0, &shellenabledp, -1},
-    {"shell-restricted", 0, 0, 0},
-    {"debug-format", 0, &debug_format_file, 1},
-    {"file-line-error-style", 0, &filelineerrorstylep, 1},
-    {"no-file-line-error-style", 0, &filelineerrorstylep, -1},
-
-    /* Shorter option names for the above. */
-
-    {"file-line-error", 0, &filelineerrorstylep, 1},
-    {"no-file-line-error", 0, &filelineerrorstylep, -1},
-    {"jobname", 1, 0, 0},
-    {"parse-first-line", 0, &parsefirstlinep, 1},
-    {"no-parse-first-line", 0, &parsefirstlinep, -1},
-    {"translate-file", 1, 0, 0},
-    {"default-translate-file", 1, 0, 0},
-    {"8bit", 0, 0, 0},
-    {"mktex", 1, 0, 0},
-    {"no-mktex", 1, 0, 0},
-
-    /* Synchronization: just like "interaction" above */
-
-    {"synctex", 1, 0, 0},
-    {0, 0, 0, 0}
-};
-
-@ @c
-int lua_numeric_field_by_index(lua_State * L, int name_index, int dflt)
-{
-    register int i = dflt;
-    lua_rawgeti(L, LUA_REGISTRYINDEX, name_index);      /* fetch the stringptr */
-    lua_rawget(L, -2);
-    if (lua_type(L, -1) == LUA_TNUMBER) {
-        i = lua_roundnumber(L, -1);
-    }
-    lua_pop(L, 1);
-    return i;
-}
-
-@ @c
-unsigned int lua_unsigned_numeric_field_by_index(lua_State * L, int name_index, int dflt)
-{
-    register unsigned int i = dflt;
-    lua_rawgeti(L, LUA_REGISTRYINDEX, name_index);      /* fetch the stringptr */
-    lua_rawget(L, -2);
-    if (lua_type(L, -1) == LUA_TNUMBER) {
-        i = lua_uroundnumber(L, -1);
-    }
-    lua_pop(L, 1);
-    return i;
-}
-
-@ @c
-static int recorderoption = 0;
-
-static void parse_options(int ac, char **av)
-{
-#ifdef WIN32
-/* save argc and argv */
-    int sargc = argc;
-    char **sargv = argv;
-#endif
-    int g;                      /* `getopt' return code.  */
-    int option_index;
-    char *firstfile = NULL;
-    opterr = 0;                 /* dont whine */
-#ifdef LuajitTeX
-    if ((strstr(argv[0], "luajittexlua") != NULL) ||
-        (strstr(argv[0], "texluajit") != NULL)) {
-#else
-    if ((strstr(argv[0], "luatexlua") != NULL) ||
-        (strstr(argv[0], "texlua") != NULL)) {
-#endif
-        lua_only = 1;
-        luainit = 1;
-    }
-    for (;;) {
-        g = getopt_long_only(ac, av, "+", long_options, &option_index);
-
-        if (g == -1)            /* End of arguments, exit the loop.  */
-            break;
-        if (g == '?')  {         /* Unknown option.  */
-          if (!luainit)
-            fprintf(stderr,"%s: unrecognized option '%s'\n", argv[0], argv[optind-1]);
-          continue;
-        }
-
-        assert(g == 0);         /* We have no short option names.  */
-
-        if (ARGUMENT_IS("luaonly")) {
-            lua_only = 1;
-            lua_offset = optind;
-            luainit = 1;
-        } else if (ARGUMENT_IS("lua")) {
-            startup_filename = optarg;
-            lua_offset = (optind - 1);
-            luainit = 1;
-#ifdef LuajitTeX
-        } else if (ARGUMENT_IS("jiton")) {
-            luajiton = 1;
-        } else if (ARGUMENT_IS("jithash")) {
-        size_t len = strlen(optarg);
-        if (len<16) {
-            jithash_hashname = optarg;
-        } else {
-            WARNING2("hash name truncated to 15 characters from %d. (%s)", (int) len, optarg);
-            jithash_hashname = (string) xmalloc(16);
-            strncpy(jithash_hashname, optarg, 15);
-            jithash_hashname[15] = 0;
-      }
-#endif
-        } else if (ARGUMENT_IS("luahashchars")) {
-            show_luahashchars = 1;
-        } else if (ARGUMENT_IS("kpathsea-debug")) {
-            kpathsea_debug |= atoi(optarg);
-        } else if (ARGUMENT_IS("progname")) {
-            user_progname = optarg;
-        } else if (ARGUMENT_IS("jobname")) {
-            c_job_name = optarg;
-        } else if (ARGUMENT_IS("fmt")) {
-            dump_name = optarg;
-        } else if (ARGUMENT_IS("output-directory")) {
-            output_directory = optarg;
-        } else if (ARGUMENT_IS("output-comment")) {
-            size_t len = strlen(optarg);
-            if (len < 256) {
-                output_comment = optarg;
-            } else {
-                WARNING2("Comment truncated to 255 characters from %d. (%s)", (int) len, optarg);
-                output_comment = (string) xmalloc(256);
-                strncpy(output_comment, optarg, 255);
-                output_comment[255] = 0;
-            }
-        } else if (ARGUMENT_IS("shell-restricted")) {
-            shellenabledp = 1;
-            restrictedshell = 1;
-        } else if (ARGUMENT_IS("output-format")) {
-            output_mode_option = 1;
-            if (strcmp(optarg, "dvi") == 0) {
-                output_mode_value = 0;
-            } else if (strcmp(optarg, "pdf") == 0) {
-                output_mode_value = 1;
-            } else {
-                WARNING1("Ignoring unknown value `%s' for --output-format",optarg);
-                output_mode_option = 0;
-            }
-        } else if (ARGUMENT_IS("draftmode")) {
-            draft_mode_option = 1;
-            draft_mode_value = 1;
-        } else if (ARGUMENT_IS("mktex")) {
-            kpse_maketex_option(optarg, true);
-        } else if (ARGUMENT_IS("no-mktex")) {
-            kpse_maketex_option(optarg, false);
-        } else if (ARGUMENT_IS("interaction")) {
-            /* These numbers match CPP defines */
-            if (STREQ(optarg, "batchmode")) {
-                interactionoption = 0;
-            } else if (STREQ(optarg, "nonstopmode")) {
-                interactionoption = 1;
-            } else if (STREQ(optarg, "scrollmode")) {
-                interactionoption = 2;
-            } else if (STREQ(optarg, "errorstopmode")) {
-                interactionoption = 3;
-            } else {
-                WARNING1("Ignoring unknown argument `%s' to --interaction", optarg);
-            }
-        } else if (ARGUMENT_IS("synctex")) {
-            /* Synchronize TeXnology: catching the command line option as a long  */
-            synctexoption = (int) strtol(optarg, NULL, 0);
-        } else if (ARGUMENT_IS("recorder")) {
-            recorderoption = 1 ;
-        } else if (ARGUMENT_IS("help")) {
-            usagehelp(LUATEX_IHELP, BUG_ADDRESS);
-        } else if (ARGUMENT_IS("version")) {
-            print_version_banner();
-            /* *INDENT-OFF* */
-            puts("\n\nExecute  '" my_name " --credits'  for credits and version details.\n\n"
-                 "There is NO warranty. Redistribution of this software is covered by\n"
-                 "the terms of the GNU General Public License, version 2 or (at your option)\n"
-                 "any later version. For more information about these matters, see the file\n"
-                 "named COPYING and the LuaTeX source.\n\n"
-                 "LuaTeX is Copyright 2017 Taco Hoekwater and the LuaTeX Team.\n");
-            /* *INDENT-ON* */
-            uexit(0);
-        } else if (ARGUMENT_IS("credits")) {
-            char *versions;
-            initversionstring(&versions);
-            print_version_banner();
-            /* *INDENT-OFF* */
-            puts("\n\nThe LuaTeX team is Hans Hagen, Hartmut Henkel, Taco Hoekwater, Luigi Scarso.\n\n"
-                 MyName " merges and builds upon (parts of) the code from these projects:\n\n"
-                 "tex       : Donald Knuth\n"
-                 "etex      : Peter Breitenlohner, Phil Taylor and friends\n"
-                 "omega     : John Plaice and Yannis Haralambous\n"
-                 "aleph     : Giuseppe Bilotta\n"
-                 "pdftex    : Han The Thanh and friends\n"
-                 "kpathsea  : Karl Berry, Olaf Weber and others\n"
-                 "lua       : Roberto Ierusalimschy, Waldemar Celes and Luiz Henrique de Figueiredo\n"
-                 "metapost  : John Hobby, Taco Hoekwater and friends\n"
-                 "poppler   : Derek Noonburg, Kristian H\\ogsberg (partial)\n"
-                 "fontforge : George Williams (partial)\n"
-                 "luajit    : Mike Pall (used in LuajitTeX)\n");
-            /* *INDENT-ON* */
-            puts(versions);
-            uexit(0);
-        }
-    }
-    /* attempt to find |input_name| / |dump_name| */
-    if (lua_only) {
-        if (argv[optind]) {
-            startup_filename = xstrdup(argv[optind]);
-            lua_offset = optind;
-        }
-    } else if (argv[optind] && argv[optind][0] == '&') {
-        dump_name = xstrdup(argv[optind] + 1);
-    } else if (argv[optind] && argv[optind][0] != '\\') {
-        if (argv[optind][0] == '*') {
-            input_name = xstrdup(argv[optind] + 1);
-        } else {
-            firstfile = xstrdup(argv[optind]);
-            if ((strstr(firstfile, ".lua") ==
-                 firstfile + strlen(firstfile) - 4)
-                || (strstr(firstfile, ".luc") ==
-                    firstfile + strlen(firstfile) - 4)
-                || (strstr(firstfile, ".LUA") ==
-                    firstfile + strlen(firstfile) - 4)
-                || (strstr(firstfile, ".LUC") ==
-                    firstfile + strlen(firstfile) - 4)) {
-                if (startup_filename == NULL) {
-                    startup_filename = firstfile;
-                    lua_offset = optind;
-                    lua_only = 1;
-                    luainit = 1;
-                }
-            } else {
-                input_name = firstfile;
-            }
-        }
-#ifdef WIN32
-    } else if (sargc > 1 && sargv[sargc-1] && sargv[sargc-1][0] != '-' &&
-               sargv[sargc-1][0] != '\\') {
-        if (sargv[sargc-1][0] == '&')
-            dump_name = xstrdup(sargv[sargc-1] + 1);
-        else  {
-            if (sargv[sargc-1][0] == '*')
-                input_name = xstrdup(sargv[sargc-1] + 1);
-            else
-                input_name = xstrdup(sargv[sargc-1]);
-            sargv[sargc-1] = normalize_quotes(input_name, "argument");
-        }
-        if (safer_option)      /* --safer implies --nosocket */
-            nosocket_option = 1;
-        return;
-#endif
-    }
-    if (safer_option)           /* --safer implies --nosocket */
-        nosocket_option = 1;
-    /* Finalize the input filename. */
-    if (input_name != NULL) {
-        argv[optind] = normalize_quotes(input_name, "argument");
-    }
-}
-
-@ test for readability
- at c
-#define is_readable(a) (stat(a,&finfo)==0) && S_ISREG(finfo.st_mode) &&  \
-  (f=fopen(a,"r")) != NULL && !fclose(f)
-
-@ @c
-static char *find_filename(char *name, const char *envkey)
-{
-    struct stat finfo;
-    char *dirname = NULL;
-    char *filename = NULL;
-    FILE *f;
-    if (is_readable(name)) {
-        return name;
-    } else {
-        dirname = getenv(envkey);
-        if ((dirname != NULL) && strlen(dirname)) {
-            dirname = xstrdup(getenv(envkey));
-            if (*(dirname + strlen(dirname) - 1) == '/') {
-                *(dirname + strlen(dirname) - 1) = 0;
-            }
-            filename = xmalloc((unsigned) (strlen(dirname) + strlen(name) + 2));
-            filename = concat3(dirname, "/", name);
-            xfree(dirname);
-            if (is_readable(filename)) {
-                return filename;
-            }
-            xfree(filename);
-        }
-    }
-    return NULL;
-}
-
-@ @c
-
-static void init_kpse(void)
-{
-    if (!user_progname) {
-        user_progname = dump_name;
-    } else if (!dump_name) {
-        dump_name = user_progname;
-    }
-    if (!user_progname) {
-        if (ini_version) {
-            if (input_name) {
-                char *p = input_name + strlen(input_name) - 1;
-                while (p >= input_name) {
-                    if (IS_DIR_SEP (*p)) {
-                        p++;
-                        input_name = p;
-                        break;
-                    }
-                    p--;
-                }
-                user_progname = remove_suffix (input_name);
-            }
-            if (!user_progname) {
-                user_progname = kpse_program_basename(argv[0]);
-            }
-        } else {
-            if (!dump_name) {
-                dump_name = kpse_program_basename(argv[0]);
-            }
-            user_progname = dump_name;
-        }
-    }
-    kpse_set_program_enabled(kpse_fmt_format, MAKE_TEX_FMT_BY_DEFAULT,
-                             kpse_src_compile);
-
-    kpse_set_program_name(argv[0], user_progname);
-    init_shell_escape();        /* set up 'restrictedshell' */
-    init_start_time();
-    program_name_set = 1 ;
-    if (recorderoption) {
-        recorder_enabled = 1;
-    }
-}
-
-@ @c
-static void fix_dumpname(void)
-{
-    int dist;
-    if (dump_name) {
-        /* adjust array for Pascal and provide extension, if needed */
-        dist = (int) (strlen(dump_name) - strlen(DUMP_EXT));
-        if (strstr(dump_name, DUMP_EXT) == dump_name + dist)
-            TEX_format_default = dump_name;
-        else
-            TEX_format_default = concat(dump_name, DUMP_EXT);
-    } else {
-        /* For |dump_name| to be NULL is a bug.  */
-        if (!ini_version) {
-          fprintf(stdout, "no format given, quitting\n");
-          exit(1);
-        }
-    }
-}
-
-@ lua require patch
-
-@ Auxiliary function for kpse search
-
- at c
-static const char *luatex_kpse_find_aux(lua_State *L, const char *name,
-        kpse_file_format_type format, const char *errname)
-{
-    const char *filename;
-    const char *altname;
-    altname = luaL_gsub(L, name, ".", "/"); /* Lua convention */
-    filename = kpse_find_file(altname, format, false);
-    if (filename == NULL) {
-        filename = kpse_find_file(name, format, false);
-    }
-    if (filename == NULL) {
-        lua_pushfstring(L, "\n\t[kpse %s searcher] file not found: " LUA_QS, errname, name);
-    }
-    return filename;
-}
-
-@ The lua search function.
-
-When kpathsea is not initialized, then it runs the
-normal lua function that is saved in the registry, otherwise
-it uses kpathsea.
-
-two registry ref variables are needed: one for the actual lua
-function, the other for its environment .
-
- at c
-static int lua_loader_function = 0;
-
-static int luatex_kpse_lua_find(lua_State * L)
-{
-    const char *filename;
-    const char *name;
-    name = luaL_checkstring(L, 1);
-    if (program_name_set == 0) {
-        lua_rawgeti(L, LUA_REGISTRYINDEX, lua_loader_function);
-        lua_pushvalue(L, -2);
-        lua_call(L, 1, 1);
-        return 1;
-    }
-    filename = luatex_kpse_find_aux(L, name, kpse_lua_format, "lua");
-    if (filename == NULL)
-        return 1;               /* library not found in this path */
-    if (luaL_loadfile(L, filename) != 0) {
-        luaL_error(L, "error loading module %s from file %s:\n\t%s",
-                   lua_tostring(L, 1), filename, lua_tostring(L, -1));
-    }
-    return 1;                   /* library loaded successfully */
-}
-
-@ @c
-static int clua_loader_function = 0;
-extern int searcher_C_luatex (lua_State *L, const char *name, const char *filename);
-
-static int luatex_kpse_clua_find(lua_State * L)
-{
-    const char *filename;
-    const char *name;
-    if (safer_option) {
-        lua_pushliteral(L, "\n\t[C searcher disabled in safer mode]");
-        return 1;               /* library not found in this path */
-    }
-    name = luaL_checkstring(L, 1);
-    if (program_name_set == 0) {
-        lua_rawgeti(L, LUA_REGISTRYINDEX, clua_loader_function);
-        lua_pushvalue(L, -2);
-        lua_call(L, 1, 1);
-        return 1;
-    } else {
-        const char *path_saved;
-        char *prefix, *postfix, *p, *total;
-        char *extensionless;
-        char *temp_name;
-        int j;
-        filename = luatex_kpse_find_aux(L, name, kpse_clua_format, "C");
-        if (filename == NULL)
-           return 1;               /* library not found in this path */
-        extensionless = strdup(filename);
-        if (!extensionless)
-            return 1;  /* allocation failure */
-        /* Fix Issue 850: replace '.' with LUA_DIRSEP */
-        temp_name = strdup(name);
-        for(j=0; ; j++){
-          if ((unsigned char)temp_name[j]=='\0') {
-            break;
-          }
-          if ((unsigned char)temp_name[j]=='.'){
-            temp_name[j]=LUA_DIRSEP[0];
-          }
-        }
-        p = strstr(extensionless, temp_name);
-        if (!p) return 1;  /* this would be exceedingly weird */
-        *p = '\0';
-        prefix = strdup(extensionless);
-        if (!prefix) return 1;  /* allocation failure */
-        postfix = strdup(p+strlen(name));
-        if (!postfix) return 1;  /* allocation failure */
-        total = malloc(strlen(prefix)+strlen(postfix)+2);
-        if (!total) return 1;  /* allocation failure */
-        snprintf(total,strlen(prefix)+strlen(postfix)+2, "%s?%s", prefix, postfix);
-        /* save package.path */
-        lua_getglobal(L,"package");
-        lua_getfield(L,-1,"cpath");
-        path_saved = lua_tostring(L,-1);
-        lua_pop(L,1);
-        /* set package.path = "?" */
-        lua_pushstring(L,total);
-        lua_setfield(L,-2,"cpath");
-        lua_pop(L,1); /* pop "package" */
-        /* run function */
-        lua_rawgeti(L, LUA_REGISTRYINDEX, clua_loader_function);
-        lua_pushstring(L, name);
-        lua_call(L, 1, 1);
-        /* restore package.path */
-        lua_getglobal(L,"package");
-        lua_pushstring(L,path_saved);
-        lua_setfield(L,-2,"cpath");
-        lua_pop(L,1); /* pop "package" */
-        free(extensionless);
-        free(total);
-        free(temp_name);
-        return 1;
-    }
-}
-
-@ Setting up the new search functions.
-
-This replaces package.searchers[2] and package.searchers[3] with the
-functions defined above.
-
- at c
-static void setup_lua_path(lua_State * L)
-{
-    lua_getglobal(L, "package");
-#ifdef LuajitTeX
-    lua_getfield(L, -1, "loaders");
-#else
-    lua_getfield(L, -1, "searchers");
-#endif
-    lua_rawgeti(L, -1, 2);      /* package.searchers[2] */
-    lua_loader_function = luaL_ref(L, LUA_REGISTRYINDEX);
-    lua_pushcfunction(L, luatex_kpse_lua_find);
-    lua_rawseti(L, -2, 2);      /* replace the normal lua loader */
-
-    lua_rawgeti(L, -1, 3);      /* package.searchers[3] */
-    clua_loader_function = luaL_ref(L, LUA_REGISTRYINDEX);
-    lua_pushcfunction(L, luatex_kpse_clua_find);
-    lua_rawseti(L, -2, 3);      /* replace the normal lua lib loader */
-
-    lua_pop(L, 2);              /* pop the array and table */
-}
-
-@ helper variables for the safe keeping of table ids
-
- at c
-int tex_table_id;
-int pdf_table_id;
-int token_table_id;
-int node_table_id;
-
-@ @c
-int l_pack_type_index       [PACK_TYPE_SIZE] ;
-int l_group_code_index      [GROUP_CODE_SIZE];
-int l_local_par_index       [LOCAL_PAR_SIZE];
-int l_math_style_name_index [MATH_STYLE_NAME_SIZE];
-int l_dir_par_index         [DIR_PAR_SIZE];
-int l_dir_text_index        [DIR_TEXT_SIZE];
-
-int img_parms               [img_parms_max];
-int img_pageboxes           [img_pageboxes_max];
-
-int lua_show_valid_list(lua_State *L, const char **list, int max)
-{
-    int i;
-    lua_newtable(L);
-    for (i = 0; i < max; i++) {
-        lua_pushinteger(L,i+1);
-        lua_pushstring(L, list[i]);
-        lua_settable(L, -3);
-    }
-    return 1;
-}
-
-int lua_show_valid_keys(lua_State *L, int *list, int max)
-{
-    int i;
-    lua_newtable(L);
-    for (i = 0; i < max; i++) {
-        lua_pushinteger(L,i+1);
-        lua_rawgeti(L, LUA_REGISTRYINDEX, list[i]);
-        lua_settable(L, -3);
-    }
-    return 1;
-}
-
-#if defined(WIN32) || defined(__MINGW32__) || defined(__CYGWIN__)
-char **suffixlist;
-
-#  define EXE_SUFFIXES ".com;.exe;.bat;.cmd;.vbs;.vbe;.js;.jse;.wsf;.wsh;.ws;.tcl;.py;.pyw"
-
-@ @c
-static void mk_suffixlist(void)
-{
-    char **p;
-    char *q, *r, *v;
-    int n;
-
-#  if defined(__CYGWIN__)
-    v = xstrdup(EXE_SUFFIXES);
-#  else
-    v = (char *) getenv("PATHEXT");
-    if (v)                      /* strlwr() exists also in MingW */
-        v = (char *) strlwr(xstrdup(v));
-    else
-        v = xstrdup(EXE_SUFFIXES);
-#  endif
-
-    q = v;
-    n = 0;
-
-    while ((r = strchr(q, ';')) != NULL) {
-        n++;
-        r++;
-        q = r;
-    }
-    if (*q)
-        n++;
-    suffixlist = (char **) xmalloc((n + 2) * sizeof(char *));
-    p = suffixlist;
-    *p = xstrdup(".dll");
-    p++;
-    q = v;
-    while ((r = strchr(q, ';')) != NULL) {
-        *r = '\0';
-        *p = xstrdup(q);
-        p++;
-        r++;
-        q = r;
-    }
-    if (*q) {
-        *p = xstrdup(q);
-        p++;
-    }
-    *p = NULL;
-    free(v);
-}
-#endif
-
-@ @c
-void lua_initialize(int ac, char **av)
-{
-    char *given_file = NULL;
-    char *banner;
-    /*int kpse_init;*/
-    size_t len;
-    int starttime;
-    int utc;
-    static char LC_CTYPE_C[] = "LC_CTYPE=C";
-    static char LC_COLLATE_C[] = "LC_COLLATE=C";
-    static char LC_NUMERIC_C[] = "LC_NUMERIC=C";
-    static char engine_luatex[] = "engine=" my_name;
-    /* Save to pass along to topenin.  */
-    const char *fmt = "This is " MyName ", Version %s" WEB2CVERSION;
-    argc = ac;
-    argv = av;
-    len = strlen(fmt) + strlen(luatex_version_string) ;
-    banner = xmalloc(len);
-    sprintf(banner, fmt, luatex_version_string);
-    luatex_banner = banner;
-    kpse_invocation_name = kpse_program_basename(argv[0]);
-
-    /* be 'luac' */
-    if (argc >1) {
-#ifdef LuajitTeX
-        if (FILESTRCASEEQ(kpse_invocation_name, "texluajitc"))
-            exit(luac_main(ac, av));
-        if (STREQ(argv[1], "--luaconly") || STREQ(argv[1], "--luac")) {
-            char *argv1 = xmalloc (strlen ("luajittex") + 1);
-            av[1] = argv1;
-            strcpy (av[1], "luajittex");
-            exit(luac_main(--ac, ++av));
-        }
-#else
-        if (FILESTRCASEEQ(kpse_invocation_name, "texluac"))
-            exit(luac_main(ac, av));
-        if (STREQ(argv[1], "--luaconly") || STREQ(argv[1], "--luac")) {
-            strcpy(av[1], "luatex");
-            exit(luac_main(--ac, ++av));
-        }
-#endif
-    }
-#if defined(WIN32) || defined(__MINGW32__) || defined(__CYGWIN__)
-    mk_suffixlist();
-#endif
-
-    /* Must be initialized before options are parsed.  */
-    interactionoption = 4;
-    dump_name = NULL;
-
-    /* 0 means "disable Synchronize TeXnology".
-     synctexoption is a *.web variable.
-     We initialize it to a weird value to catch the -synctex command line flag
-     At runtime, if synctexoption is not |INT_MAX|, then it contains the command line option provided,
-     otherwise no such option was given by the user. */
-#define SYNCTEX_NO_OPTION INT_MAX
-    synctexoption = SYNCTEX_NO_OPTION;
-
-    /* parse commandline */
-    parse_options(ac, av);
-    if (lua_only)
-        shellenabledp = true;
-
-    /* make sure that the locale is 'sane' (for lua) */
-
-    putenv(LC_CTYPE_C);
-    putenv(LC_COLLATE_C);
-    putenv(LC_NUMERIC_C);
-
-    /* this is sometimes needed */
-    putenv(engine_luatex);
-
-    luainterpreter();
-
-    /* init internalized strings */
-    set_init_keys;
-
-    lua_pushstring(Luas,"lua.functions");
-    lua_newtable(Luas);
-    lua_settable(Luas,LUA_REGISTRYINDEX);
-
-    /* here start the key definitions */
-    set_l_pack_type_index;
-    set_l_group_code_index;
-    set_l_local_par_index;
-    set_l_math_style_name_index;
-    set_l_dir_par_index;
-    set_l_dir_text_index;
-
-    set_l_img_keys_index;
-    set_l_img_pageboxes_index;
-
-    prepare_cmdline(Luas, argv, argc, lua_offset);      /* collect arguments */
-    setup_lua_path(Luas);
-
-    if (startup_filename != NULL) {
-        given_file = xstrdup(startup_filename);
-        if (lua_only) {
-          xfree(startup_filename);
-        }
-        startup_filename = find_filename(given_file, "LUATEXDIR");
-    }
-    /* now run the file */
-    if (startup_filename != NULL) {
-        char *v1;
-        /* hide the 'tex' and 'pdf' table */
-        tex_table_id = hide_lua_table(Luas, "tex");
-        token_table_id = hide_lua_table(Luas, "token");
-        node_table_id = hide_lua_table(Luas, "node");
-        pdf_table_id = hide_lua_table(Luas, "pdf");
-
-        if (luaL_loadfile(Luas, startup_filename)) {
-            fprintf(stdout, "%s\n", lua_tostring(Luas, -1));
-            exit(1);
-        }
-        /* */
-        init_tex_table(Luas);
-        if (lua_pcall(Luas, 0, 0, 0)) {
-            fprintf(stdout, "%s\n", lua_tostring(Luas, -1));
-        lua_traceback(Luas);
-            exit(1);
-        }
-        /* no filename? quit now! */
-        if (!input_name) {
-            get_lua_string("texconfig", "jobname", &input_name);
-        }
-        if (!dump_name) {
-            get_lua_string("texconfig", "formatname", &dump_name);
-        }
-        if (lua_only) {
-            if (given_file)
-                free(given_file);
-            /* this is not strictly needed but it pleases valgrind */
-            lua_close(Luas);
-            exit(0);
-        }
-        /* unhide the 'tex' and 'pdf' table */
-        unhide_lua_table(Luas, "tex", tex_table_id);
-        unhide_lua_table(Luas, "pdf", pdf_table_id);
-        unhide_lua_table(Luas, "token", token_table_id);
-        unhide_lua_table(Luas, "node", node_table_id);
-
-        /* |kpse_init| */
-        kpse_init = -1;
-        get_lua_boolean("texconfig", "kpse_init", &kpse_init);
-
-        if (kpse_init != 0) {
-            luainit = 0;        /* re-enable loading of texmf.cnf values, see luatex.ch */
-            init_kpse();
-        }
-        /* |prohibit_file_trace| (boolean) */
-        tracefilenames = 1;
-        get_lua_boolean("texconfig", "trace_file_names", &tracefilenames);
-
-        /* |file_line_error| */
-        filelineerrorstylep = false;
-        get_lua_boolean("texconfig", "file_line_error", &filelineerrorstylep);
-
-        /* |halt_on_error| */
-        haltonerrorp = false;
-        get_lua_boolean("texconfig", "halt_on_error", &haltonerrorp);
-
-        /* |restrictedshell| */
-        v1 = NULL;
-        get_lua_string("texconfig", "shell_escape", &v1);
-        if (v1) {
-            if (*v1 == 't' || *v1 == 'y' || *v1 == '1') {
-                shellenabledp = 1;
-            } else if (*v1 == 'p') {
-                shellenabledp = 1;
-                restrictedshell = 1;
-            }
-            free(v1);
-        }
-        /* If shell escapes are restricted, get allowed cmds from cnf.  */
-        if (shellenabledp && restrictedshell == 1) {
-            v1 = NULL;
-            get_lua_string("texconfig", "shell_escape_commands", &v1);
-            if (v1) {
-                mk_shellcmdlist(v1);
-            free(v1);
-            }
-        }
-
-        starttime = -1 ;
-        get_lua_number("texconfig", "start_time", &starttime);
-        if (starttime < 0) {
-            /*
-                We provide this one for compatibility reasons and therefore also in
-                uppercase.
-            */
-            get_lua_number("texconfig", "SOURCE_DATE_EPOCH", &starttime);
-        }
-        if (starttime >= 0) {
-            set_start_time(starttime);
-        }
-
-        utc = -1 ;
-        get_lua_boolean("texconfig", "use_utc_time", &utc);
-        if (utc >= 0 && utc <= 1) {
-            utc_option = utc;
-        }
-
-        fix_dumpname();
-    } else {
-        if (luainit) {
-            if (given_file) {
-                fprintf(stdout, "%s file %s not found\n", (lua_only ? "Script" : "Configuration"), given_file);
-                free(given_file);
-            } else {
-                fprintf(stdout, "No %s file given\n", (lua_only ? "script" : "configuration"));
-            }
-            exit(1);
-        } else {
-            /* init */
-            init_kpse();
-            fix_dumpname();
-        }
-    }
-    /* Maybe we can extend this way to the others tables, using luac. */
-    if ( safer_option || ((shellenabledp == 0) || (shellenabledp == 1 && restrictedshell == 1)) ) {
-       (void)   luaL_dostring(Luas,"ffi=require[[ffi]]; for k,_ in pairs(ffi) do if k~='gc' then ffi[k]=nil end; end; ffi=nil;");
-    }
-}
-
-@ @c
-void check_texconfig_init(void)
-{
-    if (Luas != NULL) {
-        lua_getglobal(Luas, "texconfig");
-        if (lua_istable(Luas, -1)) {
-            lua_getfield(Luas, -1, "init");
-            if (lua_isfunction(Luas, -1)) {
-                int i = lua_pcall(Luas, 0, 0, 0);
-                if (i != 0) {
-                    /* Can't be more precise here, called before TeX initialization  */
-                    fprintf(stderr, "This went wrong: %s\n", lua_tostring(Luas, -1));
-                    error();
-                }
-            }
-        }
-    }
-}
+% luainit.w
+%
+% Copyright 2006-2017 Taco Hoekwater <taco@@luatex.org>
+%
+% 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 <http://www.gnu.org/licenses/>.
+
+@ @c
+
+#include "ptexlib.h"
+
+#include <kpathsea/c-stat.h>
+
+#include "lua/luatex-api.h"
+
+#include <locale.h>
+
+extern int load_luatex_core_lua (lua_State * L);
+
+/* internalized strings: see luatex-api.h */
+set_make_keys;
+
+@
+This file is getting a bit messy, but it is not simple to fix unilaterally.
+
+Better to wait until Karl has some time (after texlive 2008) so we can
+synchronize with kpathsea. One problem, for instance, is that I would
+like to resolve the full executable path.  |kpse_set_program_name()| does
+that, indirectly (by setting SELFAUTOLOC in the environment), but it
+does much more, making it hard to use for our purpose.
+
+In fact, it sets three C variables:
+
+  |kpse_invocation_name|  |kpse_invocation_short_name|  |kpse->program_name|
+
+and five environment variables:
+
+  SELFAUTOLOC  SELFAUTODIR  SELFAUTOPARENT  SELFAUTOGRANDPARENT  progname
+
+ at c
+const_string LUATEX_IHELP[] = {
+    "Usage: " my_name " --lua=FILE [OPTION]... [TEXNAME[.tex]] [COMMANDS]",
+    "   or: " my_name " --lua=FILE [OPTION]... \\FIRST-LINE",
+    "   or: " my_name " --lua=FILE [OPTION]... &FMT ARGS",
+    "  Run " MyName " on TEXNAME, usually creating TEXNAME.pdf.",
+    "  Any remaining COMMANDS are processed as luatex input, after TEXNAME is read.",
+    "",
+    "  Alternatively, if the first non-option argument begins with a backslash,",
+    "  " my_name " interprets all non-option arguments as an input line.",
+    "",
+    "  Alternatively, if the first non-option argument begins with a &, the",
+    "  next word is taken as the FMT to read, overriding all else.  Any",
+    "  remaining arguments are processed as above.",
+    "",
+    "  If no arguments or options are specified, prompt for input.",
+    "",
+    "  The following regular options are understood: ",
+    "",
+    "   --credits                     display credits and exit",
+    "   --debug-format                enable format debugging",
+    "   --draftmode                   switch on draft mode (generates no output PDF)",
+    "   --[no-]file-line-error        disable/enable file:line:error style messages",
+    "   --[no-]file-line-error-style  aliases of --[no-]file-line-error",
+    "   --fmt=FORMAT                  load the format file FORMAT",
+    "   --halt-on-error               stop processing at the first error",
+    "   --help                        display help and exit",
+    "   --ini                         be ini" my_name ", for dumping formats",
+    "   --interaction=STRING          set interaction mode (STRING=batchmode/nonstopmode/scrollmode/errorstopmode)",
+    "   --jobname=STRING              set the job name to STRING",
+    "   --kpathsea-debug=NUMBER       set path searching debugging flags according to the bits of NUMBER",
+    "   --lua=FILE                    load and execute a lua initialization script",
+    "   --[no-]mktex=FMT              disable/enable mktexFMT generation (FMT=tex/tfm)",
+    "   --nosocket                    disable the lua socket library",
+    "   --output-comment=STRING       use STRING for DVI file comment instead of date (no effect for PDF)",
+    "   --output-directory=DIR        use existing DIR as the directory to write files in",
+    "   --output-format=FORMAT        use FORMAT for job output; FORMAT is 'dvi' or 'pdf'",
+    "   --progname=STRING             set the program name to STRING",
+    "   --recorder                    enable filename recorder",
+    "   --safer                       disable easily exploitable lua commands",
+    "   --[no-]shell-escape           disable/enable system commands",
+    "   --shell-restricted            restrict system commands to a list of commands given in texmf.cnf",
+    "   --synctex=NUMBER              enable synctex",
+    "   --utc                         init time to UTC",
+    "   --version                     display version and exit",
+    "",
+    "Alternate behaviour models can be obtained by special switches",
+    "",
+    "  --luaonly                      run a lua file, then exit",
+    "  --luaconly                     byte-compile a lua file, then exit",
+    "  --luahashchars                 the bits used by current Lua interpreter for strings hashing",
+#ifdef LuajitTeX
+    "  --jiton                        turns the JIT compiler on (default off)",
+    "  --jithash=STRING               choose the hash function for the lua strings (lua51|luajit20: default lua51)",
+#endif
+    "",
+    "See the reference manual for more information about the startup process.",
+    NULL
+};
+
+@ Later we will put on environment |LC_CTYPE|, |LC_COLLATE| and
+|LC_NUMERIC| set to |C|, so we need a place where to store the old values.
+ at c
+const char *lc_ctype;
+const char *lc_collate;
+const char *lc_numeric;
+
+/*
+    "   --8bit                        ignored, input is assumed to be in UTF-8 encoding",
+    "   --default-translate-file=FILE ignored, input is assumed to be in UTF-8 encoding",
+    "   --etex                        ignored, the etex extensions are always active",
+    "   --disable-write18             disable \\write18{SHELL COMMAND}",
+    "   --enable-write18              enable \\write18{SHELL COMMAND}",
+    "   --[no-]parse-first-line       ignored",
+    "   --translate-file=FILE         ignored, input is assumed to be in UTF-8 encoding",
+*/
+
+@ The return value will be the directory of the executable, e.g.: \.{c:/TeX/bin}
+ at c
+static char *ex_selfdir(char *argv0)
+{
+#if defined(WIN32)
+#if defined(__MINGW32__)
+    char path[PATH_MAX], *fp;
+
+    /* SearchPath() always gives back an absolute directory */
+    if (SearchPath(NULL, argv0, ".exe", PATH_MAX, path, NULL) == 0)
+        FATAL1("Can't determine where the executable %s is.\n", argv0);
+    /* slashify the dirname */
+    for (fp = path; fp && *fp; fp++)
+        if (IS_DIR_SEP(*fp))
+            *fp = DIR_SEP;
+#else /* __MINGW32__ */
+#define PATH_MAX 512
+    char short_path[PATH_MAX], path[PATH_MAX], *fp;
+
+    /* SearchPath() always gives back an absolute directory */
+    if (SearchPath(NULL, argv0, ".exe", PATH_MAX, short_path, &fp) == 0)
+        FATAL1("Can't determine where the executable %s is.\n", argv0);
+    if (getlongpath(path, short_path, sizeof(path)) == 0) {
+        FATAL1("This path points to an invalid file : %s\n", short_path);
+    }
+#endif /* __MINGW32__ */
+    return xdirname(path);
+#else /* WIN32 */
+    return kpse_selfdir(argv0);
+#endif
+}
+
+@ @c
+static void prepare_cmdline(lua_State * L, char **av, int ac, int zero_offset)
+{
+    int i;
+    char *s;
+    luaL_checkstack(L, ac + 3, "too many arguments to script");
+    lua_createtable(L, 0, 0);
+    for (i = 0; i < ac; i++) {
+        lua_pushstring(L, av[i]);
+        lua_rawseti(L, -2, (i - zero_offset));
+    }
+    lua_setglobal(L, "arg");
+    lua_getglobal(L, "os");
+    s = ex_selfdir(argv[0]);
+    lua_pushstring(L, s);
+    xfree(s);
+    lua_setfield(L, -2, "selfdir");
+    return;
+}
+
+
+@ @c
+int kpse_init = -1;
+
+@ @c
+string input_name = NULL;
+
+static string user_progname = NULL;
+
+char *startup_filename = NULL;
+int lua_only = 0;
+int lua_offset = 0;
+unsigned char show_luahashchars = 0;
+
+#ifdef LuajitTeX
+int luajiton   = 0;
+char *jithash_hashname = NULL;
+#endif
+
+int safer_option = 0;
+int nosocket_option = 0;
+int utc_option = 0;
+
+@ Reading the options.
+
+@ Test whether getopt found an option ``A''.
+Assumes the option index is in the variable |option_index|, and the
+option table in a variable |long_options|.
+
+ at c
+#define ARGUMENT_IS(a) STREQ (long_options[option_index].name, a)
+
+/*
+    SunOS cc can't initialize automatic structs, so make this static.
+*/
+
+/*
+    Nota Bene: we still intercept some options that other engines handle
+    so that existing scripted usage will not fail.
+*/
+
+static struct option long_options[] = {
+    {"fmt", 1, 0, 0},
+    {"lua", 1, 0, 0},
+    {"luaonly", 0, 0, 0},
+    {"luahashchars", 0, 0, 0},
+#ifdef LuajitTeX
+    {"jiton", 0, 0, 0},
+    {"jithash", 1, 0, 0},
+#endif
+    {"safer", 0, &safer_option, 1},
+    {"utc", 0, &utc_option, 1},
+    {"nosocket", 0, &nosocket_option, 1},
+    {"help", 0, 0, 0},
+    {"ini", 0, &ini_version, 1},
+    {"interaction", 1, 0, 0},
+    {"halt-on-error", 0, &haltonerrorp, 1},
+    {"kpathsea-debug", 1, 0, 0},
+    {"progname", 1, 0, 0},
+    {"version", 0, 0, 0},
+    {"credits", 0, 0, 0},
+    {"recorder", 0, 0, 0},
+    {"etex", 0, 0, 0},
+    {"output-comment", 1, 0, 0},
+    {"output-directory", 1, 0, 0},
+    {"draftmode", 0, 0, 0},
+    {"output-format", 1, 0, 0},
+    {"shell-escape", 0, &shellenabledp, 1},
+    {"no-shell-escape", 0, &shellenabledp, -1},
+    {"enable-write18", 0, &shellenabledp, 1},
+    {"disable-write18", 0, &shellenabledp, -1},
+    {"shell-restricted", 0, 0, 0},
+    {"debug-format", 0, &debug_format_file, 1},
+    {"file-line-error-style", 0, &filelineerrorstylep, 1},
+    {"no-file-line-error-style", 0, &filelineerrorstylep, -1},
+
+    /* Shorter option names for the above. */
+
+    {"file-line-error", 0, &filelineerrorstylep, 1},
+    {"no-file-line-error", 0, &filelineerrorstylep, -1},
+    {"jobname", 1, 0, 0},
+    {"parse-first-line", 0, &parsefirstlinep, 1},
+    {"no-parse-first-line", 0, &parsefirstlinep, -1},
+    {"translate-file", 1, 0, 0},
+    {"default-translate-file", 1, 0, 0},
+    {"8bit", 0, 0, 0},
+    {"mktex", 1, 0, 0},
+    {"no-mktex", 1, 0, 0},
+
+    /* Synchronization: just like "interaction" above */
+
+    {"synctex", 1, 0, 0},
+    {0, 0, 0, 0}
+};
+
+@ @c
+int lua_numeric_field_by_index(lua_State * L, int name_index, int dflt)
+{
+    register int i = dflt;
+    lua_rawgeti(L, LUA_REGISTRYINDEX, name_index);      /* fetch the stringptr */
+    lua_rawget(L, -2);
+    if (lua_type(L, -1) == LUA_TNUMBER) {
+        i = lua_roundnumber(L, -1);
+    }
+    lua_pop(L, 1);
+    return i;
+}
+
+@ @c
+unsigned int lua_unsigned_numeric_field_by_index(lua_State * L, int name_index, int dflt)
+{
+    register unsigned int i = dflt;
+    lua_rawgeti(L, LUA_REGISTRYINDEX, name_index);      /* fetch the stringptr */
+    lua_rawget(L, -2);
+    if (lua_type(L, -1) == LUA_TNUMBER) {
+        i = lua_uroundnumber(L, -1);
+    }
+    lua_pop(L, 1);
+    return i;
+}
+
+@ @c
+static int recorderoption = 0;
+
+static void parse_options(int ac, char **av)
+{
+#ifdef WIN32
+/* save argc and argv */
+    int sargc = argc;
+    char **sargv = argv;
+#endif
+    int g;                      /* `getopt' return code.  */
+    int option_index;
+    char *firstfile = NULL;
+    opterr = 0;                 /* dont whine */
+#ifdef LuajitTeX
+    if ((strstr(argv[0], "luajittexlua") != NULL) ||
+        (strstr(argv[0], "texluajit") != NULL)) {
+#else
+    if ((strstr(argv[0], "luatexlua") != NULL) ||
+        (strstr(argv[0], "texlua") != NULL)) {
+#endif
+        lua_only = 1;
+        luainit = 1;
+    }
+
+    for (;;) {
+        g = getopt_long_only(ac, av, "+", long_options, &option_index);
+        if (g == -1)            /* End of arguments, exit the loop.  */
+            break;
+        if (g == '?')  {         /* Unknown option.  */
+          if (!luainit)
+            fprintf(stderr,"%s: unrecognized option '%s'\n", argv[0], argv[optind-1]);
+          continue;
+        }
+
+        assert(g == 0);         /* We have no short option names.  */
+
+        if (ARGUMENT_IS("luaonly")) {
+            lua_only = 1;
+            lua_offset = optind;
+            luainit = 1;
+        } else if (ARGUMENT_IS("lua")) {
+            startup_filename = optarg;
+            lua_offset = (optind - 1);
+            luainit = 1;
+#ifdef LuajitTeX
+        } else if (ARGUMENT_IS("jiton")) {
+            luajiton = 1;
+        } else if (ARGUMENT_IS("jithash")) {
+        size_t len = strlen(optarg);
+        if (len<16) {
+            jithash_hashname = optarg;
+        } else {
+            WARNING2("hash name truncated to 15 characters from %d. (%s)", (int) len, optarg);
+            jithash_hashname = (string) xmalloc(16);
+            strncpy(jithash_hashname, optarg, 15);
+            jithash_hashname[15] = 0;
+      }
+#endif
+        } else if (ARGUMENT_IS("luahashchars")) {
+            show_luahashchars = 1;
+        } else if (ARGUMENT_IS("kpathsea-debug")) {
+            kpathsea_debug |= atoi(optarg);
+        } else if (ARGUMENT_IS("progname")) {
+            user_progname = optarg;
+        } else if (ARGUMENT_IS("jobname")) {
+            c_job_name = optarg;
+        } else if (ARGUMENT_IS("fmt")) {
+            dump_name = optarg;
+        } else if (ARGUMENT_IS("output-directory")) {
+            output_directory = optarg;
+        } else if (ARGUMENT_IS("output-comment")) {
+            size_t len = strlen(optarg);
+            if (len < 256) {
+                output_comment = optarg;
+            } else {
+                WARNING2("Comment truncated to 255 characters from %d. (%s)", (int) len, optarg);
+                output_comment = (string) xmalloc(256);
+                strncpy(output_comment, optarg, 255);
+                output_comment[255] = 0;
+            }
+        } else if (ARGUMENT_IS("shell-restricted")) {
+            shellenabledp = 1;
+            restrictedshell = 1;
+        } else if (ARGUMENT_IS("output-format")) {
+            output_mode_option = 1;
+            if (strcmp(optarg, "dvi") == 0) {
+                output_mode_value = 0;
+            } else if (strcmp(optarg, "pdf") == 0) {
+                output_mode_value = 1;
+            } else {
+                WARNING1("Ignoring unknown value `%s' for --output-format",optarg);
+                output_mode_option = 0;
+            }
+        } else if (ARGUMENT_IS("draftmode")) {
+            draft_mode_option = 1;
+            draft_mode_value = 1;
+        } else if (ARGUMENT_IS("mktex")) {
+            kpse_maketex_option(optarg, true);
+        } else if (ARGUMENT_IS("no-mktex")) {
+            kpse_maketex_option(optarg, false);
+        } else if (ARGUMENT_IS("interaction")) {
+            /* These numbers match CPP defines */
+            if (STREQ(optarg, "batchmode")) {
+                interactionoption = 0;
+            } else if (STREQ(optarg, "nonstopmode")) {
+                interactionoption = 1;
+            } else if (STREQ(optarg, "scrollmode")) {
+                interactionoption = 2;
+            } else if (STREQ(optarg, "errorstopmode")) {
+                interactionoption = 3;
+            } else {
+                WARNING1("Ignoring unknown argument `%s' to --interaction", optarg);
+            }
+        } else if (ARGUMENT_IS("synctex")) {
+            /* Synchronize TeXnology: catching the command line option as a long  */
+            synctexoption = (int) strtol(optarg, NULL, 0);
+        } else if (ARGUMENT_IS("recorder")) {
+            recorderoption = 1 ;
+        } else if (ARGUMENT_IS("help")) {
+            usagehelp(LUATEX_IHELP, BUG_ADDRESS);
+        } else if (ARGUMENT_IS("version")) {
+            print_version_banner();
+            /* *INDENT-OFF* */
+            puts("\n\nExecute  '" my_name " --credits'  for credits and version details.\n\n"
+                 "There is NO warranty. Redistribution of this software is covered by\n"
+                 "the terms of the GNU General Public License, version 2 or (at your option)\n"
+                 "any later version. For more information about these matters, see the file\n"
+                 "named COPYING and the LuaTeX source.\n\n"
+                 "LuaTeX is Copyright 2017 Taco Hoekwater and the LuaTeX Team.\n");
+            /* *INDENT-ON* */
+            uexit(0);
+        } else if (ARGUMENT_IS("credits")) {
+            char *versions;
+            initversionstring(&versions);
+            print_version_banner();
+            /* *INDENT-OFF* */
+            puts("\n\nThe LuaTeX team is Hans Hagen, Hartmut Henkel, Taco Hoekwater, Luigi Scarso.\n\n"
+                 MyName " merges and builds upon (parts of) the code from these projects:\n\n"
+                 "tex       : Donald Knuth\n"
+                 "etex      : Peter Breitenlohner, Phil Taylor and friends\n"
+                 "omega     : John Plaice and Yannis Haralambous\n"
+                 "aleph     : Giuseppe Bilotta\n"
+                 "pdftex    : Han The Thanh and friends\n"
+                 "kpathsea  : Karl Berry, Olaf Weber and others\n"
+                 "lua       : Roberto Ierusalimschy, Waldemar Celes and Luiz Henrique de Figueiredo\n"
+                 "metapost  : John Hobby, Taco Hoekwater and friends\n"
+                 "poppler   : Derek Noonburg, Kristian H\\ogsberg (partial)\n"
+                 "fontforge : George Williams (partial)\n"
+                 "luajit    : Mike Pall (used in LuajitTeX)\n");
+            /* *INDENT-ON* */
+            puts(versions);
+            uexit(0);
+        }
+    }
+    /* attempt to find |input_name| / |dump_name| */
+    if (lua_only) {
+        if (argv[optind]) {
+            startup_filename = xstrdup(argv[optind]);
+            lua_offset = optind;
+        }
+    } else if (argv[optind] && argv[optind][0] == '&') {
+        dump_name = xstrdup(argv[optind] + 1);
+    } else if (argv[optind] && argv[optind][0] != '\\') {
+        if (argv[optind][0] == '*') {
+            input_name = xstrdup(argv[optind] + 1);
+        } else {
+            firstfile = xstrdup(argv[optind]);
+            if ((strstr(firstfile, ".lua") ==
+                 firstfile + strlen(firstfile) - 4)
+                || (strstr(firstfile, ".luc") ==
+                    firstfile + strlen(firstfile) - 4)
+                || (strstr(firstfile, ".LUA") ==
+                    firstfile + strlen(firstfile) - 4)
+                || (strstr(firstfile, ".LUC") ==
+                    firstfile + strlen(firstfile) - 4)) {
+                if (startup_filename == NULL) {
+                    startup_filename = firstfile;
+                    lua_offset = optind;
+                    lua_only = 1;
+                    luainit = 1;
+                }
+            } else {
+                input_name = firstfile;
+            }
+        }
+#ifdef WIN32
+    } else if (sargc > 1 && sargv[sargc-1] && sargv[sargc-1][0] != '-' &&
+               sargv[sargc-1][0] != '\\') {
+        if (sargv[sargc-1][0] == '&')
+            dump_name = xstrdup(sargv[sargc-1] + 1);
+        else  {
+            if (sargv[sargc-1][0] == '*')
+                input_name = xstrdup(sargv[sargc-1] + 1);
+            else
+                input_name = xstrdup(sargv[sargc-1]);
+            sargv[sargc-1] = normalize_quotes(input_name, "argument");
+        }
+        if (safer_option)      /* --safer implies --nosocket */
+            nosocket_option = 1;
+        return;
+#endif
+    }
+    if (safer_option)           /* --safer implies --nosocket */
+        nosocket_option = 1;
+    /* Finalize the input filename. */
+    if (input_name != NULL) {
+        argv[optind] = normalize_quotes(input_name, "argument");
+    }
+}
+
+@ test for readability
+ at c
+#define is_readable(a) (stat(a,&finfo)==0) && S_ISREG(finfo.st_mode) &&  \
+  (f=fopen(a,"r")) != NULL && !fclose(f)
+
+@ @c
+static char *find_filename(char *name, const char *envkey)
+{
+    struct stat finfo;
+    char *dirname = NULL;
+    char *filename = NULL;
+    FILE *f;
+    if (is_readable(name)) {
+        return name;
+    } else {
+        dirname = getenv(envkey);
+        if ((dirname != NULL) && strlen(dirname)) {
+            dirname = xstrdup(getenv(envkey));
+            if (*(dirname + strlen(dirname) - 1) == '/') {
+                *(dirname + strlen(dirname) - 1) = 0;
+            }
+            filename = xmalloc((unsigned) (strlen(dirname) + strlen(name) + 2));
+            filename = concat3(dirname, "/", name);
+            xfree(dirname);
+            if (is_readable(filename)) {
+                return filename;
+            }
+            xfree(filename);
+        }
+    }
+    return NULL;
+}
+
+@ @c
+
+static void init_kpse(void)
+{
+    if (!user_progname) {
+        user_progname = dump_name;
+    } else if (!dump_name) {
+        dump_name = user_progname;
+    }
+    if (!user_progname) {
+        if (ini_version) {
+            if (input_name) {
+                char *p = input_name + strlen(input_name) - 1;
+                while (p >= input_name) {
+                    if (IS_DIR_SEP (*p)) {
+                        p++;
+                        input_name = p;
+                        break;
+                    }
+                    p--;
+                }
+                user_progname = remove_suffix (input_name);
+            }
+            if (!user_progname) {
+                user_progname = kpse_program_basename(argv[0]);
+            }
+        } else {
+            if (!dump_name) {
+                dump_name = kpse_program_basename(argv[0]);
+            }
+            user_progname = dump_name;
+        }
+    }
+    kpse_set_program_enabled(kpse_fmt_format, MAKE_TEX_FMT_BY_DEFAULT,
+                             kpse_src_compile);
+
+    kpse_set_program_name(argv[0], user_progname);
+    init_shell_escape();        /* set up 'restrictedshell' */
+    init_start_time();
+    program_name_set = 1 ;
+    if (recorderoption) {
+        recorder_enabled = 1;
+    }
+}
+
+@ @c
+static void fix_dumpname(void)
+{
+    int dist;
+    if (dump_name) {
+        /* adjust array for Pascal and provide extension, if needed */
+        dist = (int) (strlen(dump_name) - strlen(DUMP_EXT));
+        if (strstr(dump_name, DUMP_EXT) == dump_name + dist)
+            TEX_format_default = dump_name;
+        else
+            TEX_format_default = concat(dump_name, DUMP_EXT);
+    } else {
+        /* For |dump_name| to be NULL is a bug.  */
+        if (!ini_version) {
+          fprintf(stdout, "no format given, quitting\n");
+          exit(1);
+        }
+    }
+}
+
+@ lua require patch
+
+@ Auxiliary function for kpse search
+
+ at c
+static const char *luatex_kpse_find_aux(lua_State *L, const char *name,
+        kpse_file_format_type format, const char *errname)
+{
+    const char *filename;
+    const char *altname;
+    altname = luaL_gsub(L, name, ".", "/"); /* Lua convention */
+    filename = kpse_find_file(altname, format, false);
+    if (filename == NULL) {
+        filename = kpse_find_file(name, format, false);
+    }
+    if (filename == NULL) {
+        lua_pushfstring(L, "\n\t[kpse %s searcher] file not found: " LUA_QS, errname, name);
+    }
+    return filename;
+}
+
+@ The lua search function.
+
+When kpathsea is not initialized, then it runs the
+normal lua function that is saved in the registry, otherwise
+it uses kpathsea.
+
+two registry ref variables are needed: one for the actual lua
+function, the other for its environment .
+
+ at c
+static int lua_loader_function = 0;
+
+static int luatex_kpse_lua_find(lua_State * L)
+{
+    const char *filename;
+    const char *name;
+    name = luaL_checkstring(L, 1);
+    if (program_name_set == 0) {
+        lua_rawgeti(L, LUA_REGISTRYINDEX, lua_loader_function);
+        lua_pushvalue(L, -2);
+        lua_call(L, 1, 1);
+        return 1;
+    }
+    filename = luatex_kpse_find_aux(L, name, kpse_lua_format, "lua");
+    if (filename == NULL)
+        return 1;               /* library not found in this path */
+    if (luaL_loadfile(L, filename) != 0) {
+        luaL_error(L, "error loading module %s from file %s:\n\t%s",
+                   lua_tostring(L, 1), filename, lua_tostring(L, -1));
+    }
+    return 1;                   /* library loaded successfully */
+}
+
+@ @c
+static int clua_loader_function = 0;
+extern int searcher_C_luatex (lua_State *L, const char *name, const char *filename);
+
+static int luatex_kpse_clua_find(lua_State * L)
+{
+    const char *filename;
+    const char *name;
+    if (safer_option) {
+        lua_pushliteral(L, "\n\t[C searcher disabled in safer mode]");
+        return 1;               /* library not found in this path */
+    }
+    name = luaL_checkstring(L, 1);
+    if (program_name_set == 0) {
+        lua_rawgeti(L, LUA_REGISTRYINDEX, clua_loader_function);
+        lua_pushvalue(L, -2);
+        lua_call(L, 1, 1);
+        return 1;
+    } else {
+        const char *path_saved;
+        char *prefix, *postfix, *p, *total;
+        char *extensionless;
+        char *temp_name;
+        int j;
+        filename = luatex_kpse_find_aux(L, name, kpse_clua_format, "C");
+        if (filename == NULL)
+           return 1;               /* library not found in this path */
+        extensionless = strdup(filename);
+        if (!extensionless)
+            return 1;  /* allocation failure */
+        /* Fix Issue 850: replace '.' with LUA_DIRSEP */
+        temp_name = strdup(name);
+        for(j=0; ; j++){
+          if ((unsigned char)temp_name[j]=='\0') {
+            break;
+          }
+          if ((unsigned char)temp_name[j]=='.'){
+            temp_name[j]=LUA_DIRSEP[0];
+          }
+        }
+        p = strstr(extensionless, temp_name);
+        if (!p) return 1;  /* this would be exceedingly weird */
+        *p = '\0';
+        prefix = strdup(extensionless);
+        if (!prefix) return 1;  /* allocation failure */
+        postfix = strdup(p+strlen(name));
+        if (!postfix) return 1;  /* allocation failure */
+        total = malloc(strlen(prefix)+strlen(postfix)+2);
+        if (!total) return 1;  /* allocation failure */
+        snprintf(total,strlen(prefix)+strlen(postfix)+2, "%s?%s", prefix, postfix);
+        /* save package.path */
+        lua_getglobal(L,"package");
+        lua_getfield(L,-1,"cpath");
+        path_saved = lua_tostring(L,-1);
+        lua_pop(L,1);
+        /* set package.path = "?" */
+        lua_pushstring(L,total);
+        lua_setfield(L,-2,"cpath");
+        lua_pop(L,1); /* pop "package" */
+        /* run function */
+        lua_rawgeti(L, LUA_REGISTRYINDEX, clua_loader_function);
+        lua_pushstring(L, name);
+        lua_call(L, 1, 1);
+        /* restore package.path */
+        lua_getglobal(L,"package");
+        lua_pushstring(L,path_saved);
+        lua_setfield(L,-2,"cpath");
+        lua_pop(L,1); /* pop "package" */
+        free(extensionless);
+        free(total);
+        free(temp_name);
+        return 1;
+    }
+}
+
+@ Setting up the new search functions.
+
+This replaces package.searchers[2] and package.searchers[3] with the
+functions defined above.
+
+ at c
+static void setup_lua_path(lua_State * L)
+{
+    lua_getglobal(L, "package");
+#ifdef LuajitTeX
+    lua_getfield(L, -1, "loaders");
+#else
+    lua_getfield(L, -1, "searchers");
+#endif
+    lua_rawgeti(L, -1, 2);      /* package.searchers[2] */
+    lua_loader_function = luaL_ref(L, LUA_REGISTRYINDEX);
+    lua_pushcfunction(L, luatex_kpse_lua_find);
+    lua_rawseti(L, -2, 2);      /* replace the normal lua loader */
+
+    lua_rawgeti(L, -1, 3);      /* package.searchers[3] */
+    clua_loader_function = luaL_ref(L, LUA_REGISTRYINDEX);
+    lua_pushcfunction(L, luatex_kpse_clua_find);
+    lua_rawseti(L, -2, 3);      /* replace the normal lua lib loader */
+
+    lua_pop(L, 2);              /* pop the array and table */
+}
+
+@ helper variables for the safe keeping of table ids
+
+ at c
+int tex_table_id;
+int pdf_table_id;
+int token_table_id;
+int node_table_id;
+
+@ @c
+int l_pack_type_index       [PACK_TYPE_SIZE] ;
+int l_group_code_index      [GROUP_CODE_SIZE];
+int l_local_par_index       [LOCAL_PAR_SIZE];
+int l_math_style_name_index [MATH_STYLE_NAME_SIZE];
+int l_dir_par_index         [DIR_PAR_SIZE];
+int l_dir_text_index        [DIR_TEXT_SIZE];
+
+int img_parms               [img_parms_max];
+int img_pageboxes           [img_pageboxes_max];
+
+int lua_show_valid_list(lua_State *L, const char **list, int max)
+{
+    int i;
+    lua_newtable(L);
+    for (i = 0; i < max; i++) {
+        lua_pushinteger(L,i+1);
+        lua_pushstring(L, list[i]);
+        lua_settable(L, -3);
+    }
+    return 1;
+}
+
+int lua_show_valid_keys(lua_State *L, int *list, int max)
+{
+    int i;
+    lua_newtable(L);
+    for (i = 0; i < max; i++) {
+        lua_pushinteger(L,i+1);
+        lua_rawgeti(L, LUA_REGISTRYINDEX, list[i]);
+        lua_settable(L, -3);
+    }
+    return 1;
+}
+
+#if defined(WIN32) || defined(__MINGW32__) || defined(__CYGWIN__)
+char **suffixlist;
+
+#  define EXE_SUFFIXES ".com;.exe;.bat;.cmd;.vbs;.vbe;.js;.jse;.wsf;.wsh;.ws;.tcl;.py;.pyw"
+
+@ @c
+static void mk_suffixlist(void)
+{
+    char **p;
+    char *q, *r, *v;
+    int n;
+
+#  if defined(__CYGWIN__)
+    v = xstrdup(EXE_SUFFIXES);
+#  else
+    v = (char *) getenv("PATHEXT");
+    if (v)                      /* strlwr() exists also in MingW */
+        v = (char *) strlwr(xstrdup(v));
+    else
+        v = xstrdup(EXE_SUFFIXES);
+#  endif
+
+    q = v;
+    n = 0;
+
+    while ((r = strchr(q, ';')) != NULL) {
+        n++;
+        r++;
+        q = r;
+    }
+    if (*q)
+        n++;
+    suffixlist = (char **) xmalloc((n + 2) * sizeof(char *));
+    p = suffixlist;
+    *p = xstrdup(".dll");
+    p++;
+    q = v;
+    while ((r = strchr(q, ';')) != NULL) {
+        *r = '\0';
+        *p = xstrdup(q);
+        p++;
+        r++;
+        q = r;
+    }
+    if (*q) {
+        *p = xstrdup(q);
+        p++;
+    }
+    *p = NULL;
+    free(v);
+}
+#endif
+
+@ @c
+void lua_initialize(int ac, char **av)
+{
+    char *given_file = NULL;
+    char *banner;
+    /*int kpse_init;*/
+    size_t len;
+    int starttime;
+    int utc;
+    static char LC_CTYPE_C[] = "LC_CTYPE=C";
+    static char LC_COLLATE_C[] = "LC_COLLATE=C";
+    static char LC_NUMERIC_C[] = "LC_NUMERIC=C";
+    static char engine_luatex[] = "engine=" my_name;
+    char *old_locale = NULL;
+    char *env_locale = NULL;
+    char *tmp = NULL;
+    /* Save to pass along to topenin.  */
+    const char *fmt = "This is " MyName ", Version %s" WEB2CVERSION;
+    argc = ac;
+    argv = av;
+    len = strlen(fmt) + strlen(luatex_version_string) ;
+    banner = xmalloc(len);
+    sprintf(banner, fmt, luatex_version_string);
+    luatex_banner = banner;
+    kpse_invocation_name = kpse_program_basename(argv[0]);
+
+    /* be 'luac' */
+    if (argc >1) {
+#ifdef LuajitTeX
+        if (FILESTRCASEEQ(kpse_invocation_name, "texluajitc"))
+            exit(luac_main(ac, av));
+        if (STREQ(argv[1], "--luaconly") || STREQ(argv[1], "--luac")) {
+            char *argv1 = xmalloc (strlen ("luajittex") + 1);
+            av[1] = argv1;
+            strcpy (av[1], "luajittex");
+            exit(luac_main(--ac, ++av));
+        }
+#else
+        if (FILESTRCASEEQ(kpse_invocation_name, "texluac"))
+            exit(luac_main(ac, av));
+        if (STREQ(argv[1], "--luaconly") || STREQ(argv[1], "--luac")) {
+            strcpy(av[1], "luatex");
+            exit(luac_main(--ac, ++av));
+        }
+#endif
+    }
+#if defined(WIN32) || defined(__MINGW32__) || defined(__CYGWIN__)
+    mk_suffixlist();
+#endif
+
+    /* Must be initialized before options are parsed.  */
+    interactionoption = 4;
+    dump_name = NULL;
+
+    /* 0 means "disable Synchronize TeXnology".
+     synctexoption is a *.web variable.
+     We initialize it to a weird value to catch the -synctex command line flag
+     At runtime, if synctexoption is not |INT_MAX|, then it contains the command line option provided,
+     otherwise no such option was given by the user. */
+#define SYNCTEX_NO_OPTION INT_MAX
+    synctexoption = SYNCTEX_NO_OPTION;
+
+    /* parse commandline */
+    parse_options(ac, av);
+    if (lua_only) {
+        /* Shell has no restrictions. */
+        shellenabledp = true;
+        restrictedshell = false;
+        safer_option = 0;
+    }
+    /* Get the current locale (it should be C )          */
+    /* and save LC_CTYPE, LC_COLLATE and LC_NUMERIC.     */
+    /* Later luainterpreter() will consciously use them. */
+    old_locale = setlocale (LC_ALL, NULL);
+    lc_ctype = NULL;
+    lc_collate = NULL;
+    lc_numeric = NULL;
+    if (old_locale) {
+        /* If setlocale fails here, then the state   */
+        /* could be compromised, and we exit.        */
+        env_locale = setlocale (LC_ALL, "");
+	if (!env_locale) {
+	  fprintf(stderr,"Unable to read environment locale:exit now.\n");
+	  exit(1);
+	}
+        tmp = setlocale (LC_CTYPE, NULL);
+	if (tmp) {
+	  lc_ctype = xstrdup(tmp);
+        }
+	tmp = setlocale (LC_COLLATE, NULL);
+	if (tmp){
+	  lc_collate = xstrdup(tmp);
+        }
+	tmp = setlocale (LC_NUMERIC, NULL);
+	if (tmp){
+	  lc_numeric = xstrdup(tmp);
+        }
+	/* Back to the previous locale if possible,   */
+	/* otherwise it's a serious error and we exit:*/
+	/* we can't ensure a 'sane' locale for lua.   */
+	env_locale = setlocale (LC_ALL, old_locale);
+	if (!env_locale) {
+	  fprintf(stderr,"Unable to restore original locale:exit now.\n");
+	  exit(1);
+	}
+    } else {
+       fprintf(stderr,"Unable to store environment locale.\n");
+    }
+
+    /* make sure that the locale is 'sane' (for lua) */
+    putenv(LC_CTYPE_C);
+    putenv(LC_COLLATE_C);
+    putenv(LC_NUMERIC_C);
+
+    /* this is sometimes needed */
+    putenv(engine_luatex);
+
+    luainterpreter();
+
+    /* init internalized strings */
+    set_init_keys;
+
+    lua_pushstring(Luas,"lua.functions");
+    lua_newtable(Luas);
+    lua_settable(Luas,LUA_REGISTRYINDEX);
+
+    /* here start the key definitions */
+    set_l_pack_type_index;
+    set_l_group_code_index;
+    set_l_local_par_index;
+    set_l_math_style_name_index;
+    set_l_dir_par_index;
+    set_l_dir_text_index;
+
+    set_l_img_keys_index;
+    set_l_img_pageboxes_index;
+
+    prepare_cmdline(Luas, argv, argc, lua_offset);      /* collect arguments */
+    setup_lua_path(Luas);
+
+    if (startup_filename != NULL) {
+        given_file = xstrdup(startup_filename);
+        if (lua_only) {
+          xfree(startup_filename);
+        }
+        startup_filename = find_filename(given_file, "LUATEXDIR");
+    }
+    /* now run the file */
+    if (startup_filename != NULL) {
+        char *v1;
+        /* hide the 'tex' and 'pdf' table */
+        tex_table_id = hide_lua_table(Luas, "tex");
+        token_table_id = hide_lua_table(Luas, "token");
+        node_table_id = hide_lua_table(Luas, "node");
+        pdf_table_id = hide_lua_table(Luas, "pdf");
+
+        if (luaL_loadfile(Luas, startup_filename)) {
+            fprintf(stdout, "%s\n", lua_tostring(Luas, -1));
+            exit(1);
+        }
+        /* */
+        init_tex_table(Luas);
+        if (lua_pcall(Luas, 0, 0, 0)) {
+            fprintf(stdout, "%s\n", lua_tostring(Luas, -1));
+        lua_traceback(Luas);
+            exit(1);
+        }
+        /* no filename? quit now! */
+        if (!input_name) {
+            get_lua_string("texconfig", "jobname", &input_name);
+        }
+        if (!dump_name) {
+            get_lua_string("texconfig", "formatname", &dump_name);
+        }
+        if (lua_only) {
+            if (given_file)
+                free(given_file);
+            /* this is not strictly needed but it pleases valgrind */
+            lua_close(Luas);
+            exit(0);
+        }
+        /* unhide the 'tex' and 'pdf' table */
+        unhide_lua_table(Luas, "tex", tex_table_id);
+        unhide_lua_table(Luas, "pdf", pdf_table_id);
+        unhide_lua_table(Luas, "token", token_table_id);
+        unhide_lua_table(Luas, "node", node_table_id);
+
+        /* |kpse_init| */
+        kpse_init = -1;
+        get_lua_boolean("texconfig", "kpse_init", &kpse_init);
+
+        if (kpse_init != 0) {
+            luainit = 0;        /* re-enable loading of texmf.cnf values, see luatex.ch */
+            init_kpse();
+            kpse_init = 1;
+        }
+        /* |prohibit_file_trace| (boolean) */
+        tracefilenames = 1;
+        get_lua_boolean("texconfig", "trace_file_names", &tracefilenames);
+
+        /* |file_line_error| */
+        filelineerrorstylep = false;
+        get_lua_boolean("texconfig", "file_line_error", &filelineerrorstylep);
+
+        /* |halt_on_error| */
+        haltonerrorp = false;
+        get_lua_boolean("texconfig", "halt_on_error", &haltonerrorp);
+
+        /* |restrictedshell| */
+        v1 = NULL;
+        get_lua_string("texconfig", "shell_escape", &v1);
+        if (v1) {
+            if (*v1 == 't' || *v1 == 'y' || *v1 == '1') {
+                shellenabledp = 1;
+            } else if (*v1 == 'p') {
+                shellenabledp = 1;
+                restrictedshell = 1;
+            }
+            free(v1);
+        }
+        /* If shell escapes are restricted, get allowed cmds from cnf.  */
+        if (shellenabledp && restrictedshell == 1) {
+            v1 = NULL;
+            get_lua_string("texconfig", "shell_escape_commands", &v1);
+            if (v1) {
+                mk_shellcmdlist(v1);
+            free(v1);
+            }
+        }
+
+        starttime = -1 ;
+        get_lua_number("texconfig", "start_time", &starttime);
+        if (starttime < 0) {
+            /*
+                We provide this one for compatibility reasons and therefore also in
+                uppercase.
+            */
+            get_lua_number("texconfig", "SOURCE_DATE_EPOCH", &starttime);
+        }
+        if (starttime >= 0) {
+            set_start_time(starttime);
+        }
+
+        utc = -1 ;
+        get_lua_boolean("texconfig", "use_utc_time", &utc);
+        if (utc >= 0 && utc <= 1) {
+            utc_option = utc;
+        }
+
+        fix_dumpname();
+    } else {
+        if (luainit) {
+            if (given_file) {
+                fprintf(stdout, "%s file %s not found\n", (lua_only ? "Script" : "Configuration"), given_file);
+                free(given_file);
+            } else {
+                fprintf(stdout, "No %s file given\n", (lua_only ? "script" : "configuration"));
+            }
+            exit(1);
+        } else {
+            /* init */
+            init_kpse();
+            kpse_init = 1;
+            fix_dumpname();
+        }
+    }
+
+    /* Here we load luatex-core.lua which takes care of some protection on demand. */
+    if (load_luatex_core_lua(Luas))
+      fprintf(stderr, "Error in execution of luatex-core.lua .\n");
+    /* Done. */
+}
+
+@ @c
+void check_texconfig_init(void)
+{
+    if (Luas != NULL) {
+        lua_getglobal(Luas, "texconfig");
+        if (lua_istable(Luas, -1)) {
+            lua_getfield(Luas, -1, "init");
+            if (lua_isfunction(Luas, -1)) {
+                int i = lua_pcall(Luas, 0, 0, 0);
+                if (i != 0) {
+                    /* Can't be more precise here, called before TeX initialization  */
+                    fprintf(stderr, "This went wrong: %s\n", lua_tostring(Luas, -1));
+                    error();
+                }
+            }
+        }
+    }
+}

Modified: trunk/Build/source/texk/web2c/luatexdir/lua/luastuff.w
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/lua/luastuff.w	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/lua/luastuff.w	2017-03-11 00:31:06 UTC (rev 43454)
@@ -1,563 +1,679 @@
-% luastuff.w
-%
-% Copyright 2006-2013 Taco Hoekwater <taco@@luatex.org>
-%
-% 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 <http://www.gnu.org/licenses/>.
-
-@ @c
-#include "ptexlib.h"
-#include "lua/luatex-api.h"
-
-@ @c
-lua_State *Luas = NULL;
-
-int luastate_bytes = 0;
-int lua_active = 0;
-
-@ @c
-void make_table(lua_State * L, const char *tab, const char *mttab, const char *getfunc,
-                const char *setfunc)
-{
-    /* make the table *//* |[{<tex>}]| */
-    lua_pushstring(L, tab);     /* |[{<tex>},"dimen"]| */
-    lua_newtable(L);            /* |[{<tex>},"dimen",{}]| */
-    lua_settable(L, -3);        /* |[{<tex>}]| */
-    /* fetch it back */
-    lua_pushstring(L, tab);     /* |[{<tex>},"dimen"]| */
-    lua_gettable(L, -2);        /* |[{<tex>},{<dimen>}]| */
-    /* make the meta entries */
-    luaL_newmetatable(L, mttab);  /* |[{<tex>},{<dimen>},{<dimen_m>}]| */
-    lua_pushstring(L, "__index");       /* |[{<tex>},{<dimen>},{<dimen_m>},"__index"]| */
-    lua_pushstring(L, getfunc); /* |[{<tex>},{<dimen>},{<dimen_m>},"__index","getdimen"]| */
-    lua_gettable(L, -5);        /* |[{<tex>},{<dimen>},{<dimen_m>},"__index",<tex.getdimen>]| */
-    lua_settable(L, -3);        /* |[{<tex>},{<dimen>},{<dimen_m>}]|  */
-    lua_pushstring(L, "__newindex");    /* |[{<tex>},{<dimen>},{<dimen_m>},"__newindex"]| */
-    lua_pushstring(L, setfunc); /* |[{<tex>},{<dimen>},{<dimen_m>},"__newindex","setdimen"]| */
-    lua_gettable(L, -5);        /* |[{<tex>},{<dimen>},{<dimen_m>},"__newindex",<tex.setdimen>]| */
-    lua_settable(L, -3);        /* |[{<tex>},{<dimen>},{<dimen_m>}]| */
-    lua_setmetatable(L, -2);    /* |[{<tex>},{<dimen>}]| : assign the metatable */
-    lua_pop(L, 1);              /* |[{<tex>}]| : clean the stack */
-}
-
-@ @c
-static const char *getS(lua_State * L, void *ud, size_t * size)
-{
-    LoadS *ls = (LoadS *) ud;
-    (void) L;
-    if (ls->size == 0)
-        return NULL;
-    *size = ls->size;
-    ls->size = 0;
-    return ls->s;
-}
-
-@ @c
-static void *my_luaalloc(void *ud, void *ptr, size_t osize, size_t nsize)
-{
-    void *ret = NULL;
-    (void) ud;                  /* for -Wunused */
-    if (nsize == 0)
-        free(ptr);
-    else
-        ret = realloc(ptr, nsize);
-    luastate_bytes += (int) (nsize - osize);
-    return ret;
-}
-
-@ @c
-static int my_luapanic(lua_State * L)
-{
-    (void) L;                   /* to avoid warnings */
-    fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n", lua_tostring(L, -1));
-    return 0;
-}
-
-@ @c
-void luafunctioncall(int slot)
-{
-    int i ;
-    int stacktop = lua_gettop(Luas);
-    lua_active++;
-    lua_rawgeti(Luas, LUA_REGISTRYINDEX, lua_key_index(lua_functions));
-    lua_gettable(Luas, LUA_REGISTRYINDEX);
-    lua_rawgeti(Luas, -1,slot);
-    if (lua_isfunction(Luas,-1)) {
-        int base = lua_gettop(Luas); /* function index */
-        lua_pushinteger(Luas, slot);
-        lua_pushcfunction(Luas, lua_traceback); /* push traceback function */
-        lua_insert(Luas, base); /* put it under chunk  */
-        i = lua_pcall(Luas, 1, 0, base);
-        lua_remove(Luas, base); /* remove traceback function */
-        if (i != 0) {
-            lua_gc(Luas, LUA_GCCOLLECT, 0);
-            Luas = luatex_error(Luas, (i == LUA_ERRRUN ? 0 : 1));
-        }
-    }
-    lua_settop(Luas,stacktop);
-    lua_active--;
-}
-
-@ @c
-static const luaL_Reg lualibs[] = {
-    {"", luaopen_base},
-    {"package", luaopen_package},
-    {"coroutine", luaopen_coroutine},
-    {"table", luaopen_table},
-    {"io", open_iolibext},
-    {"os", luaopen_os},
-    {"string", luaopen_string},
-    {"math", luaopen_math},
-    {"debug", luaopen_debug},
-    {"unicode", luaopen_unicode},
-    {"zip", luaopen_zip},
-    {"bit32", luaopen_bit32},
-    {"md5", luaopen_md5},
-    {"ffi", luaopen_ffi},
-    {"lfs", luaopen_lfs},
-    {"profiler", luaopen_profiler},
-    {"lpeg", luaopen_lpeg},
-    {NULL, NULL}
-};
-
-@ @c
-static void do_openlibs(lua_State * L)
-{
-    const luaL_Reg *lib;
-    for (lib = lualibs; lib->func; lib++) {
-        luaL_requiref(L, lib->name, lib->func, 1);
-    	lua_pop(L, 1);  /* remove lib */
-    }
-}
-
-@ @c
-static int load_aux (lua_State *L, int status) {
-  if (status == 0)  /* OK? */
-    return 1;
-  else {
-    lua_pushnil(L);
-    lua_insert(L, -2);  /* put before error message */
-    return 2;  /* return nil plus error message */
-  }
-}
-
-@ @c
-static int luatex_loadfile (lua_State *L) {
-  int status = 0;
-  const char *fname = luaL_optstring(L, 1, NULL);
-  const char *mode = luaL_optstring(L, 2, NULL);
-  int env = !lua_isnone(L, 3);  /* 'env' parameter? */
-  if (!lua_only && !fname && interaction == batch_mode) {
-     lua_pushnil(L);
-     lua_pushstring(L, "reading from stdin is disabled in batch mode");
-     return 2;  /* return nil plus error message */
-  }
-  status = luaL_loadfilex(L, fname, mode);
-  if (status == LUA_OK) {
-    recorder_record_input(fname);
-    if (env) {  /* 'env' parameter? */
-      lua_pushvalue(L, 3);
-      lua_setupvalue(L, -2, 1);  /* set it as 1st upvalue of loaded chunk */
-    }
-  }
-  return load_aux(L, status);
-}
-
-@ @c
-static int luatex_dofile (lua_State *L) {
-  const char *fname = luaL_optstring(L, 1, NULL);
-  int n = lua_gettop(L);
-  if (!lua_only && !fname) {
-      if (interaction == batch_mode) {
-	  lua_pushnil(L);
-	  lua_pushstring(L, "reading from stdin is disabled in batch mode");
-	  return 2;  /* return nil plus error message */
-      } else {
-	  tprint_nl("lua> ");
-      }
-  }
-  if (luaL_loadfile(L, fname) != 0) lua_error(L);
-  recorder_record_input(fname);
-  lua_call(L, 0, LUA_MULTRET);
-  return lua_gettop(L) - n;
-}
-
-@ @c
-void luainterpreter(void)
-{
-    lua_State *L;
-    L = lua_newstate(my_luaalloc, NULL);
-    if (L == NULL) {
-        fprintf(stderr, "Can't create the Lua state.\n");
-        return;
-    }
-    lua_atpanic(L, &my_luapanic);
-
-    do_openlibs(L);             /* does all the 'simple' libraries */
-
-    lua_pushcfunction(L,luatex_dofile);
-    lua_setglobal(L, "dofile");
-    lua_pushcfunction(L,luatex_loadfile);
-    lua_setglobal(L, "loadfile");
-
-    luatex_md5_lua_open(L);
-
-    open_oslibext(L, safer_option);
-/*
-    open_iolibext(L);
-*/
-    open_strlibext(L);
-    open_lfslibext(L);
-
-
-    /* luasockets */
-    /* socket and mime are a bit tricky to open because
-     they use a load-time  dependency that has to be
-     worked around for luatex, where the C module is
-     loaded way before the lua module.
-     */
-    if (!nosocket_option) {
-        lua_getglobal(L, "package");
-        lua_getfield(L, -1, "loaded");
-        if (!lua_istable(L, -1)) {
-            lua_newtable(L);
-            lua_setfield(L, -2, "loaded");
-            lua_getfield(L, -1, "loaded");
-        }
-        luaopen_socket_core(L);
-        lua_setfield(L, -2, "socket.core");
-        lua_pushnil(L);
-        lua_setfield(L, -2, "socket");  /* package.loaded.socket = nil */
-
-        luaopen_mime_core(L);
-        lua_setfield(L, -2, "mime.core");
-        lua_pushnil(L);
-        lua_setfield(L, -2, "mime");    /* package.loaded.mime = nil */
-        lua_pop(L, 2);          /* pop the tables */
-
-        luatex_socketlua_open(L);       /* preload the pure lua modules */
-    }
-    /* zlib. slightly odd calling convention */
-    luaopen_zlib(L);
-    lua_setglobal(L, "zlib");
-    luaopen_gzip(L);
-
-    /* our own libraries */
-    luaopen_ff(L);
-    luaopen_tex(L);
-    luaopen_token(L);
-    luaopen_node(L);
-    luaopen_texio(L);
-    luaopen_kpse(L);
-    luaopen_callback(L);
-    luaopen_lua(L, startup_filename);
-    luaopen_stats(L);
-    luaopen_font(L);
-    luaopen_lang(L);
-    luaopen_mplib(L);
-    luaopen_vf(L);
-
-    /* |luaopen_pdf(L);| */
-    /* environment table at |LUA_ENVIRONINDEX| needs to load this way: */
-    lua_pushcfunction(L, luaopen_pdf);
-    lua_pushstring(L, "pdf");
-    lua_call(L, 1, 0);
-
-    if (!lua_only) {
-        luaL_requiref(L, "img", luaopen_img, 1);
-        lua_pop(L, 1);
-    }
-
-    luaL_requiref(L, "epdf", luaopen_epdf, 1);
-    lua_pop(L, 1);
-
-    /* |luaopen_pdfscanner(L);| */
-    lua_pushcfunction(L, luaopen_pdfscanner);
-    lua_pushstring(L, "pdfscanner");
-    lua_call(L, 1, 0);
-
-    lua_createtable(L, 0, 0);
-    lua_setglobal(L, "texconfig");
-
-    if (safer_option) {
-        /* disable some stuff if --safer */
-        (void) hide_lua_value(L, "os", "execute");
-        (void) hide_lua_value(L, "os", "rename");
-        (void) hide_lua_value(L, "os", "remove");
-        (void) hide_lua_value(L, "io", "popen");
-        /* make io.open only read files */
-        luaL_checkstack(L, 2, "out of stack space");
-        lua_getglobal(L, "io");
-        lua_getfield(L, -1, "open_ro");
-        lua_setfield(L, -2, "open");
-        (void) hide_lua_value(L, "io", "tmpfile");
-        (void) hide_lua_value(L, "io", "output");
-        (void) hide_lua_value(L, "lfs", "chdir");
-        (void) hide_lua_value(L, "lfs", "lock");
-        (void) hide_lua_value(L, "lfs", "touch");
-        (void) hide_lua_value(L, "lfs", "rmdir");
-        (void) hide_lua_value(L, "lfs", "mkdir");
-    }
-    Luas = L;
-}
-
-@ @c
-int hide_lua_table(lua_State * L, const char *name)
-{
-    int r = 0;
-    lua_getglobal(L, name);
-    if (lua_istable(L, -1)) {
-        r = luaL_ref(L, LUA_REGISTRYINDEX);
-        lua_pushnil(L);
-        lua_setglobal(L, name);
-    }
-    return r;
-}
-
-@ @c
-void unhide_lua_table(lua_State * L, const char *name, int r)
-{
-    lua_rawgeti(L, LUA_REGISTRYINDEX, r);
-    lua_setglobal(L, name);
-    luaL_unref(L, LUA_REGISTRYINDEX, r);
-}
-
-@ @c
-int hide_lua_value(lua_State * L, const char *name, const char *item)
-{
-    int r = 0;
-    lua_getglobal(L, name);
-    if (lua_istable(L, -1)) {
-        lua_getfield(L, -1, item);
-        r = luaL_ref(L, LUA_REGISTRYINDEX);
-        lua_pushnil(L);
-        lua_setfield(L, -2, item);
-    }
-    return r;
-}
-
-@ @c
-void unhide_lua_value(lua_State * L, const char *name, const char *item, int r)
-{
-    lua_getglobal(L, name);
-    if (lua_istable(L, -1)) {
-        lua_rawgeti(L, LUA_REGISTRYINDEX, r);
-        lua_setfield(L, -2, item);
-        luaL_unref(L, LUA_REGISTRYINDEX, r);
-    }
-}
-
-@ @c
-int lua_traceback(lua_State * L)
-{
-    lua_getglobal(L, "debug");
-    if (!lua_istable(L, -1)) {
-        lua_pop(L, 1);
-        return 1;
-    }
-    lua_getfield(L, -1, "traceback");
-    if (!lua_isfunction(L, -1)) {
-        lua_pop(L, 2);
-        return 1;
-    }
-    lua_pushvalue(L, 1);        /* pass error message */
-    lua_pushinteger(L, 2);      /* skip this function and traceback */
-    lua_call(L, 2, 1);          /* call debug.traceback */
-    return 1;
-}
-
-@ @c
-static void luacall(int p, int nameptr, boolean is_string) /* hh-ls: optimized lua_id resolving */
-{
-    LoadS ls;
-    int i;
-    size_t ll = 0;
-    char *lua_id;
-    char *s = NULL;
-
-    if (Luas == NULL) {
-        luainterpreter();
-    }
-    lua_active++;
-    if (is_string) {
-        const char *ss = NULL;
-        lua_rawgeti(Luas, LUA_REGISTRYINDEX, p);
-        if (lua_isfunction(Luas,-1)) {
-            int base = lua_gettop(Luas);        /* function index */
-            lua_checkstack(Luas, 1);
-            lua_pushcfunction(Luas, lua_traceback);     /* push traceback function */
-            lua_insert(Luas, base);     /* put it under chunk  */
-            i = lua_pcall(Luas, 0, 0, base);
-            lua_remove(Luas, base);     /* remove traceback function */
-            if (i != 0) {
-                lua_gc(Luas, LUA_GCCOLLECT, 0);
-                Luas = luatex_error(Luas, (i == LUA_ERRRUN ? 0 : 1));
-            }
-            lua_active--;
-            return ;
-        }
-        ss = lua_tolstring(Luas, -1, &ll);
-        s = xmalloc(ll+1);
-        memcpy(s,ss,ll+1);
-        lua_pop(Luas,1);
-    } else {
-        int l = 0;
-        s = tokenlist_to_cstring(p, 1, &l);
-        ll = (size_t)l;
-    }
-    ls.s = s;
-    ls.size = ll;
-    if (ls.size > 0) {
-        if (nameptr > 0) {
-            int l = 0; /* not used */
-            lua_id = tokenlist_to_cstring(nameptr, 1, &l);
-            i = lua_load(Luas, getS, &ls, lua_id, NULL);
-            xfree(lua_id);
-        } else if (nameptr < 0) {
-            lua_id = get_lua_name((nameptr + 65536));
-            if (lua_id != NULL) {
-                i = lua_load(Luas, getS, &ls, lua_id, NULL);
-            } else {
-                i = lua_load(Luas, getS, &ls, "=[\\latelua]", NULL);
-            }
-        } else {
-            i = lua_load(Luas, getS, &ls, "=[\\latelua]", NULL);
-        }
-        if (i != 0) {
-            Luas = luatex_error(Luas, (i == LUA_ERRSYNTAX ? 0 : 1));
-        } else {
-            int base = lua_gettop(Luas);        /* function index */
-            lua_checkstack(Luas, 1);
-            lua_pushcfunction(Luas, lua_traceback);     /* push traceback function */
-            lua_insert(Luas, base);     /* put it under chunk  */
-            i = lua_pcall(Luas, 0, 0, base);
-            lua_remove(Luas, base);     /* remove traceback function */
-            if (i != 0) {
-                lua_gc(Luas, LUA_GCCOLLECT, 0);
-                Luas = luatex_error(Luas, (i == LUA_ERRRUN ? 0 : 1));
-            }
-        }
-        xfree(ls.s);
-    }
-    lua_active--;
-}
-
-@ @c
-void late_lua(PDF pdf, halfword p)
-{
-    (void) pdf;
-    if (late_lua_type(p)==normal) {
-        expand_macros_in_tokenlist(p);      /* sets |def_ref| */
-        luacall(def_ref, late_lua_name(p), false);
-        flush_list(def_ref);
-    } else {
-        luacall(late_lua_data(p), late_lua_name(p), true);
-    }
-}
-
-@ @c
-void luatokencall(int p, int nameptr) /* hh-ls: optimized lua_id resolving */
-{
-    LoadS ls;
-    int i, l;
-    char *s = NULL;
-    char *lua_id;
-    assert(Luas);
-    l = 0;
-    lua_active++;
-    s = tokenlist_to_cstring(p, 1, &l);
-    ls.s = s;
-    ls.size = (size_t) l;
-    if (ls.size > 0) {
-        if (nameptr > 0) {
-            lua_id = tokenlist_to_cstring(nameptr, 1, &l);
-            i = lua_load(Luas, getS, &ls, lua_id, NULL);
-	    xfree(lua_id);
-        } else if (nameptr < 0) {
-            lua_id = get_lua_name((nameptr + 65536));
-            if (lua_id != NULL) {
-                i = lua_load(Luas, getS, &ls, lua_id, NULL);
-            } else {
-                i = lua_load(Luas, getS, &ls, "=[\\directlua]", NULL);
-            }
-        } else {
-            i = lua_load(Luas, getS, &ls, "=[\\directlua]", NULL);
-        }
-        xfree(s);
-        if (i != 0) {
-            Luas = luatex_error(Luas, (i == LUA_ERRSYNTAX ? 0 : 1));
-        } else {
-            int base = lua_gettop(Luas);        /* function index */
-            lua_checkstack(Luas, 1);
-            lua_pushcfunction(Luas, lua_traceback);     /* push traceback function */
-            lua_insert(Luas, base);     /* put it under chunk  */
-            i = lua_pcall(Luas, 0, 0, base);
-            lua_remove(Luas, base);     /* remove traceback function */
-            if (i != 0) {
-                lua_gc(Luas, LUA_GCCOLLECT, 0);
-                Luas = luatex_error(Luas, (i == LUA_ERRRUN ? 0 : 1));
-            }
-        }
-    }
-    lua_active--;
-}
-
-@ @c
-lua_State *luatex_error(lua_State * L, int is_fatal)
-{
-
-    const_lstring luaerr;
-    char *err = NULL;
-    if (lua_type(L, -1) == LUA_TSTRING) {
-        luaerr.s = lua_tolstring(L, -1, &luaerr.l);
-        /* free last one ? */
-        err = (char *) xmalloc((unsigned) (luaerr.l + 1));
-        snprintf(err, (luaerr.l + 1), "%s", luaerr.s);
-        last_lua_error = err; /* hm, what if we have several .. not freed */
-    }
-    if (is_fatal > 0) {
-        /* Normally a memory error from lua.
-           The pool may overflow during the |maketexlstring()|, but we
-           are crashing anyway so we may as well abort on the pool size */
-        normal_error("lua",err);
-        /* never reached */
-        lua_close(L);
-        return (lua_State *) NULL;
-    } else {
-        normal_warning("lua",err);
-        return L;
-    }
-}
-
-@ @c
-void preset_environment(lua_State * L, const parm_struct * p, const char *s)
-{
-    int i;
-    assert(L != NULL);
-    /* double call with same s gives assert(0) */
-    lua_pushstring(L, s);       /* s */
-    lua_gettable(L, LUA_REGISTRYINDEX); /* t */
-    assert(lua_isnil(L, -1));
-    lua_pop(L, 1);              /* - */
-    lua_pushstring(L, s);       /* s */
-    lua_newtable(L);            /* t s */
-    for (i = 1, ++p; p->name != NULL; i++, p++) {
-        assert(i == p->idx);
-        lua_pushstring(L, p->name);     /* k t s */
-        lua_pushinteger(L, p->idx);     /* v k t s */
-        lua_settable(L, -3);    /* t s */
-    }
-    lua_settable(L, LUA_REGISTRYINDEX); /* - */
-}
+% luastuff.w
+%
+% Copyright 2006-2013 Taco Hoekwater <taco@@luatex.org>
+%
+% 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 <http://www.gnu.org/licenses/>.
+
+@ @c
+#include "ptexlib.h"
+#include "lua/luatex-api.h"
+#ifdef LuajitTeX
+#include "lua/lauxlib_bridge.h"
+#endif
+
+@ @c
+lua_State *Luas = NULL;
+
+int luastate_bytes = 0;
+int lua_active = 0;
+
+#ifdef LuajitTeX
+#define Luas_load(Luas,getS,ls,lua_id) \
+    lua_load(Luas,getS,ls,lua_id);
+#define Luas_open(name,luaopen_lib) \
+    lua_pushcfunction(L, luaopen_lib); \
+    lua_pushstring(L, name); \
+    lua_call(L, 1, 0);
+#else
+#define Luas_load(Luas,getS,ls,lua_id) \
+    lua_load(Luas,getS,ls,lua_id,NULL);
+#define Luas_open(name,luaopen_lib) \
+    luaL_requiref(L, name, luaopen_lib, 1); \
+    lua_pop(L, 1);
+#endif
+
+@ @c
+void make_table(lua_State * L, const char *tab, const char *mttab, const char *getfunc, const char *setfunc)
+{
+    /* make the table *//* |[{<tex>}]| */
+    lua_pushstring(L, tab);           /* |[{<tex>},"dimen"]| */
+    lua_newtable(L);                  /* |[{<tex>},"dimen",{}]| */
+    lua_settable(L, -3);              /* |[{<tex>}]| */
+    /* fetch it back */
+    lua_pushstring(L, tab);           /* |[{<tex>},"dimen"]| */
+    lua_gettable(L, -2);              /* |[{<tex>},{<dimen>}]| */
+    /* make the meta entries */
+    luaL_newmetatable(L, mttab);      /* |[{<tex>},{<dimen>},{<dimen_m>}]| */
+    lua_pushstring(L, "__index");     /* |[{<tex>},{<dimen>},{<dimen_m>},"__index"]| */
+    lua_pushstring(L, getfunc);       /* |[{<tex>},{<dimen>},{<dimen_m>},"__index","getdimen"]| */
+    lua_gettable(L, -5);              /* |[{<tex>},{<dimen>},{<dimen_m>},"__index",<tex.getdimen>]| */
+    lua_settable(L, -3);              /* |[{<tex>},{<dimen>},{<dimen_m>}]|  */
+    lua_pushstring(L, "__newindex");  /* |[{<tex>},{<dimen>},{<dimen_m>},"__newindex"]| */
+    lua_pushstring(L, setfunc);       /* |[{<tex>},{<dimen>},{<dimen_m>},"__newindex","setdimen"]| */
+    lua_gettable(L, -5);              /* |[{<tex>},{<dimen>},{<dimen_m>},"__newindex",<tex.setdimen>]| */
+    lua_settable(L, -3);              /* |[{<tex>},{<dimen>},{<dimen_m>}]| */
+    lua_setmetatable(L, -2);          /* |[{<tex>},{<dimen>}]| : assign the metatable */
+    lua_pop(L, 1);                    /* |[{<tex>}]| : clean the stack */
+}
+
+@ @c
+static const char *getS(lua_State * L, void *ud, size_t * size)
+{
+    LoadS *ls = (LoadS *) ud;
+    (void) L;
+    if (ls->size == 0)
+        return NULL;
+    *size = ls->size;
+    ls->size = 0;
+    return ls->s;
+}
+
+@ @c
+#ifdef LuajitTeX
+    /* Luatex has its own memory allocator, LuajitTeX uses the */
+    /* standard one from the stock. We left this space as      */
+    /* reference, but be careful: memory allocator is a key    */
+    /* component in luajit, it's easy to get sub-optimal       */
+    /* performances.                                           */
+#else
+static void *my_luaalloc(void *ud, void *ptr, size_t osize, size_t nsize)
+{
+    void *ret = NULL;
+    (void) ud; /* for -Wunused */
+    if (nsize == 0)
+        free(ptr);
+    else
+        ret = realloc(ptr, nsize);
+    luastate_bytes += (int) (nsize - osize);
+    return ret;
+}
+#endif
+
+@ @c
+static int my_luapanic(lua_State * L)
+{
+    (void) L;  /* to avoid warnings */
+    fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n", lua_tostring(L, -1));
+    return 0;
+}
+
+@ @c
+void luafunctioncall(int slot)
+{
+    int i ;
+    int stacktop = lua_gettop(Luas);
+    lua_active++;
+    lua_rawgeti(Luas, LUA_REGISTRYINDEX, lua_key_index(lua_functions));
+    lua_gettable(Luas, LUA_REGISTRYINDEX);
+    lua_rawgeti(Luas, -1,slot);
+    if (lua_isfunction(Luas,-1)) {
+        int base = lua_gettop(Luas); /* function index */
+        lua_pushinteger(Luas, slot);
+        lua_pushcfunction(Luas, lua_traceback); /* push traceback function */
+        lua_insert(Luas, base); /* put it under chunk  */
+        i = lua_pcall(Luas, 1, 0, base);
+        lua_remove(Luas, base); /* remove traceback function */
+        if (i != 0) {
+            lua_gc(Luas, LUA_GCCOLLECT, 0);
+            Luas = luatex_error(Luas, (i == LUA_ERRRUN ? 0 : 1));
+        }
+    }
+    lua_settop(Luas,stacktop);
+    lua_active--;
+}
+
+@ @c
+static const luaL_Reg lualibs[] = {
+    /* standard lua libraries */
+    { "_G",        luaopen_base },
+    { "package",   luaopen_package },
+    { "table",     luaopen_table },
+    { "io",        luaopen_io },
+    { "os",        luaopen_os },
+    { "string",    luaopen_string },
+    { "math",      luaopen_math },
+    { "debug",     luaopen_debug },
+    { "lpeg",      luaopen_lpeg },
+    { "bit32",     luaopen_bit32 },
+#ifdef LuajitTeX
+    /* bit is only in luajit,               */
+    /* coroutine is loaded in a special way */
+    { "bit",	   luaopen_bit },
+#else
+    { "coroutine", luaopen_coroutine },
+#endif
+    /* additional (public) libraries */
+    { "unicode",   luaopen_unicode },
+    { "zip",       luaopen_zip },
+    { "md5",       luaopen_md5 },
+    { "lfs",       luaopen_lfs },
+    /* extra standard lua libraries */
+#ifdef LuajitTeX
+    { "jit",       luaopen_jit },
+#endif
+    { "ffi",       luaopen_ffi },
+    /* obsolete, undocumented and for oru own testing only */
+    /*{ "profiler",  luaopen_profiler }, */
+    /* more libraries will be loaded later */
+    { NULL,        NULL }
+};
+
+@ @c
+static void do_openlibs(lua_State * L)
+{
+    const luaL_Reg *lib = lualibs;
+    for (; lib->func; lib++) {
+        Luas_open(lib->name,lib->func);
+    }
+}
+
+@ @c
+#ifdef LuajitTeX
+    /* in luajit load_aux is not used.*/
+#else
+static int load_aux (lua_State *L, int status) {
+    if (status == 0)  /* OK? */
+        return 1;
+    else {
+        lua_pushnil(L);
+        lua_insert(L, -2);  /* put before error message */
+        return 2;  /* return nil plus error message */
+    }
+}
+#endif
+
+@ @c
+static int luatex_loadfile (lua_State *L) {
+    int status = 0;
+    const char *fname = luaL_optstring(L, 1, NULL);
+    const char *mode = luaL_optstring(L, 2, NULL);
+#ifdef LuajitTeX
+    /* 5.1 */
+#else
+    int env = !lua_isnone(L, 3);  /* 'env' parameter? */
+#endif
+    if (!lua_only && !fname && interaction == batch_mode) {
+        lua_pushnil(L);
+        lua_pushstring(L, "reading from stdin is disabled in batch mode");
+        return 2;  /* return nil plus error message */
+    }
+    status = luaL_loadfilex(L, fname, mode);
+    if (status == LUA_OK) {
+        recorder_record_input(fname);
+#ifdef LuajitTeX
+    /* 5.1 */
+#else
+        if (env) {  /* 'env' parameter? */
+            lua_pushvalue(L, 3);
+            lua_setupvalue(L, -2, 1);  /* set it as 1st upvalue of loaded chunk */
+        }
+#endif
+    }
+#ifdef LuajitTeX
+    return RESERVED_load_aux_JIT(L, status,3);
+#else
+    return load_aux(L, status);
+#endif
+}
+
+@ @c
+static int luatex_dofile (lua_State *L) {
+    const char *fname = luaL_optstring(L, 1, NULL);
+    int n = lua_gettop(L);
+    if (!lua_only && !fname) {
+        if (interaction == batch_mode) {
+            lua_pushnil(L);
+            lua_pushstring(L, "reading from stdin is disabled in batch mode");
+            return 2;  /* return nil plus error message */
+        } else {
+            tprint_nl("lua> ");
+        }
+    }
+    if (luaL_loadfile(L, fname) != 0)
+        lua_error(L);
+    recorder_record_input(fname);
+    lua_call(L, 0, LUA_MULTRET);
+    return lua_gettop(L) - n;
+}
+
+@ @c
+void luainterpreter(void)
+{
+    lua_State *L;
+#ifdef LuajitTeX
+    if (jithash_hashname == NULL) {
+        /* default lua51 */
+        luajittex_choose_hash_function = 0;
+        jithash_hashname = (char *) xmalloc(strlen("lua51") + 1);
+        jithash_hashname = strcpy ( jithash_hashname, "lua51");
+    } else if (strcmp((const char*)jithash_hashname,"lua51") == 0) {
+        luajittex_choose_hash_function = 0;
+    } else if (strcmp((const char*)jithash_hashname,"luajit20") == 0) {
+        luajittex_choose_hash_function = 1;
+    } else {
+        /* default lua51 */
+        luajittex_choose_hash_function = 0;
+        jithash_hashname = strcpy ( jithash_hashname, "lua51");
+    }
+    L = luaL_newstate() ;
+#else
+    L = lua_newstate(my_luaalloc, NULL);
+#endif
+    if (L == NULL) {
+        fprintf(stderr, "Can't create the Lua state.\n");
+        return;
+    }
+    lua_atpanic(L, &my_luapanic);
+
+    do_openlibs(L);             /* does all the 'simple' libraries */
+#ifdef LuajitTeX
+    if (luajiton){
+       luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE|LUAJIT_MODE_ON);
+    }
+    else {
+       luaJIT_setmode(L, 0, LUAJIT_MODE_ENGINE|LUAJIT_MODE_OFF);
+    }
+#endif
+
+    lua_pushcfunction(L,luatex_dofile);
+    lua_setglobal(L, "dofile");
+    lua_pushcfunction(L,luatex_loadfile);
+    lua_setglobal(L, "loadfile");
+
+    open_oslibext(L);
+    open_strlibext(L);
+    open_lfslibext(L);
+
+    /*
+        The socket and mime libraries are a bit tricky to open because they use a load-time
+        dependency that has to be worked around for luatex, where the C module is loaded
+        way before the lua module.
+    */
+
+    if (!nosocket_option) {
+        /* todo: move this to common */
+        lua_getglobal(L, "package");
+        lua_getfield(L, -1, "loaded");
+        if (!lua_istable(L, -1)) {
+            lua_newtable(L);
+            lua_setfield(L, -2, "loaded");
+            lua_getfield(L, -1, "loaded");
+        }
+        luaopen_socket_core(L);
+        lua_setfield(L, -2, "socket.core");
+        lua_pushnil(L);
+        lua_setfield(L, -2, "socket");  /* package.loaded.socket = nil */
+
+        luaopen_mime_core(L);
+        lua_setfield(L, -2, "mime.core");
+        lua_pushnil(L);
+        lua_setfield(L, -2, "mime");    /* package.loaded.mime = nil */
+        lua_pop(L, 2);                  /* pop the tables */
+
+        luatex_socketlua_open(L);       /* preload the pure lua modules */
+    }
+
+    /* zlib. slightly odd calling convention */
+    luaopen_zlib(L);
+    lua_setglobal(L, "zlib");
+
+    luaopen_gzip(L);
+
+    /* our own libraries register themselves */
+
+    luaopen_fio(L);
+    luaopen_ff(L);
+    luaopen_tex(L);
+    luaopen_token(L);
+    luaopen_node(L);
+    luaopen_texio(L);
+    luaopen_kpse(L);
+    luaopen_callback(L);
+
+    luaopen_lua(L, startup_filename);
+
+    luaopen_stats(L);
+    luaopen_font(L);
+    luaopen_lang(L);
+    luaopen_mplib(L);
+    luaopen_vf(L);
+    luaopen_pdf(L);
+    luaopen_epdf(L);
+    luaopen_pdfscanner(L);
+
+    if (!lua_only) {
+        luaopen_img(L);
+    }
+
+    lua_createtable(L, 0, 0);
+    lua_setglobal(L, "texconfig");
+
+    Luas = L;
+}
+
+@ @c
+int hide_lua_table(lua_State * L, const char *name)
+{
+    int r = 0;
+    lua_getglobal(L, name);
+    if (lua_istable(L, -1)) {
+        r = luaL_ref(L, LUA_REGISTRYINDEX);
+        lua_pushnil(L);
+        lua_setglobal(L, name);
+    }
+    return r;
+}
+
+@ @c
+void unhide_lua_table(lua_State * L, const char *name, int r)
+{
+    lua_rawgeti(L, LUA_REGISTRYINDEX, r);
+    lua_setglobal(L, name);
+    luaL_unref(L, LUA_REGISTRYINDEX, r);
+}
+
+@ @c
+int hide_lua_value(lua_State * L, const char *name, const char *item)
+{
+    int r = 0;
+    lua_getglobal(L, name);
+    if (lua_istable(L, -1)) {
+        lua_getfield(L, -1, item);
+        r = luaL_ref(L, LUA_REGISTRYINDEX);
+        lua_pushnil(L);
+        lua_setfield(L, -2, item);
+    }
+    return r;
+}
+
+@ @c
+void unhide_lua_value(lua_State * L, const char *name, const char *item, int r)
+{
+    lua_getglobal(L, name);
+    if (lua_istable(L, -1)) {
+        lua_rawgeti(L, LUA_REGISTRYINDEX, r);
+        lua_setfield(L, -2, item);
+        luaL_unref(L, LUA_REGISTRYINDEX, r);
+    }
+}
+
+@ @c
+int lua_traceback(lua_State * L)
+{
+    lua_getglobal(L, "debug");
+    if (!lua_istable(L, -1)) {
+        lua_pop(L, 1);
+        return 1;
+    }
+    lua_getfield(L, -1, "traceback");
+    if (!lua_isfunction(L, -1)) {
+        lua_pop(L, 2);
+        return 1;
+    }
+    lua_pushvalue(L, 1);        /* pass error message */
+    lua_pushinteger(L, 2);      /* skip this function and traceback */
+    lua_call(L, 2, 1);          /* call debug.traceback */
+    return 1;
+}
+
+@ @c
+static void luacall(int p, int nameptr, boolean is_string) /* hh-ls: optimized lua_id resolving */
+{
+    LoadS ls;
+    int i;
+    size_t ll = 0;
+    char *lua_id;
+    char *s = NULL;
+
+    if (Luas == NULL) {
+        luainterpreter();
+    }
+    lua_active++;
+    if (is_string) {
+        const char *ss = NULL;
+        lua_rawgeti(Luas, LUA_REGISTRYINDEX, p);
+        if (lua_isfunction(Luas,-1)) {
+            int base = lua_gettop(Luas);        /* function index */
+            lua_checkstack(Luas, 1);
+            lua_pushcfunction(Luas, lua_traceback);     /* push traceback function */
+            lua_insert(Luas, base);     /* put it under chunk  */
+            i = lua_pcall(Luas, 0, 0, base);
+            lua_remove(Luas, base);     /* remove traceback function */
+            if (i != 0) {
+                lua_gc(Luas, LUA_GCCOLLECT, 0);
+                Luas = luatex_error(Luas, (i == LUA_ERRRUN ? 0 : 1));
+            }
+            lua_active--;
+            return ;
+        }
+        ss = lua_tolstring(Luas, -1, &ll);
+        s = xmalloc(ll+1);
+        memcpy(s,ss,ll+1);
+        lua_pop(Luas,1);
+    } else {
+        int l = 0;
+        s = tokenlist_to_cstring(p, 1, &l);
+        ll = (size_t)l;
+    }
+    ls.s = s;
+    ls.size = ll;
+    if (ls.size > 0) {
+        if (nameptr > 0) {
+            int l = 0; /* not used */
+            lua_id = tokenlist_to_cstring(nameptr, 1, &l);
+            i = Luas_load(Luas, getS, &ls, lua_id);
+            xfree(lua_id);
+        } else if (nameptr < 0) {
+            lua_id = get_lua_name((nameptr + 65536));
+            if (lua_id != NULL) {
+                i = Luas_load(Luas, getS, &ls, lua_id);
+            } else {
+                i = Luas_load(Luas, getS, &ls, "=[\\latelua]");
+            }
+        } else {
+            i = Luas_load(Luas, getS, &ls, "=[\\latelua]");
+        }
+        if (i != 0) {
+            Luas = luatex_error(Luas, (i == LUA_ERRSYNTAX ? 0 : 1));
+        } else {
+            int base = lua_gettop(Luas);        /* function index */
+            lua_checkstack(Luas, 1);
+            lua_pushcfunction(Luas, lua_traceback);     /* push traceback function */
+            lua_insert(Luas, base);     /* put it under chunk  */
+            i = lua_pcall(Luas, 0, 0, base);
+            lua_remove(Luas, base);     /* remove traceback function */
+            if (i != 0) {
+                lua_gc(Luas, LUA_GCCOLLECT, 0);
+                Luas = luatex_error(Luas, (i == LUA_ERRRUN ? 0 : 1));
+            }
+        }
+        xfree(ls.s);
+    }
+    lua_active--;
+}
+
+@ @c
+void late_lua(PDF pdf, halfword p)
+{
+    (void) pdf;
+    if (late_lua_type(p)==normal) {
+        expand_macros_in_tokenlist(p);      /* sets |def_ref| */
+        luacall(def_ref, late_lua_name(p), false);
+        flush_list(def_ref);
+    } else {
+        luacall(late_lua_data(p), late_lua_name(p), true);
+    }
+}
+
+@ @c
+void luatokencall(int p, int nameptr) /* hh-ls: optimized lua_id resolving */
+{
+    LoadS ls;
+    int i, l;
+    char *s = NULL;
+    char *lua_id;
+    assert(Luas);
+    l = 0;
+    lua_active++;
+    s = tokenlist_to_cstring(p, 1, &l);
+    ls.s = s;
+    ls.size = (size_t) l;
+    if (ls.size > 0) {
+        if (nameptr > 0) {
+            lua_id = tokenlist_to_cstring(nameptr, 1, &l);
+            i = Luas_load(Luas, getS, &ls, lua_id);
+	    xfree(lua_id);
+        } else if (nameptr < 0) {
+            lua_id = get_lua_name((nameptr + 65536));
+            if (lua_id != NULL) {
+                i = Luas_load(Luas, getS, &ls, lua_id);
+            } else {
+                i = Luas_load(Luas, getS, &ls, "=[\\directlua]");
+            }
+        } else {
+            i = Luas_load(Luas, getS, &ls, "=[\\directlua]");
+        }
+        xfree(s);
+        if (i != 0) {
+            Luas = luatex_error(Luas, (i == LUA_ERRSYNTAX ? 0 : 1));
+        } else {
+            int base = lua_gettop(Luas);        /* function index */
+            lua_checkstack(Luas, 1);
+            lua_pushcfunction(Luas, lua_traceback);     /* push traceback function */
+            lua_insert(Luas, base);     /* put it under chunk  */
+            i = lua_pcall(Luas, 0, 0, base);
+            lua_remove(Luas, base);     /* remove traceback function */
+            if (i != 0) {
+                lua_gc(Luas, LUA_GCCOLLECT, 0);
+                Luas = luatex_error(Luas, (i == LUA_ERRRUN ? 0 : 1));
+            }
+        }
+    }
+    lua_active--;
+}
+
+@ @c
+lua_State *luatex_error(lua_State * L, int is_fatal)
+{
+
+    const_lstring luaerr;
+    char *err = NULL;
+    if (lua_type(L, -1) == LUA_TSTRING) {
+        luaerr.s = lua_tolstring(L, -1, &luaerr.l);
+        /* free last one ? */
+        err = (char *) xmalloc((unsigned) (luaerr.l + 1));
+        snprintf(err, (luaerr.l + 1), "%s", luaerr.s);
+        last_lua_error = err; /* hm, what if we have several .. not freed */
+    }
+    if (is_fatal > 0) {
+        /* Normally a memory error from lua.
+           The pool may overflow during the |maketexlstring()|, but we
+           are crashing anyway so we may as well abort on the pool size */
+        normal_error("lua",err);
+        /* never reached */
+        lua_close(L);
+        return (lua_State *) NULL;
+    } else {
+        normal_warning("lua",err);
+        return L;
+    }
+}
+
+@ @c
+void preset_environment(lua_State * L, const parm_struct * p, const char *s)
+{
+    int i;
+    assert(L != NULL);
+    /* double call with same s gives assert(0) */
+    lua_pushstring(L, s);       /* s */
+    lua_gettable(L, LUA_REGISTRYINDEX); /* t */
+    assert(lua_isnil(L, -1));
+    lua_pop(L, 1);              /* - */
+    lua_pushstring(L, s);       /* s */
+    lua_newtable(L);            /* t s */
+    for (i = 1, ++p; p->name != NULL; i++, p++) {
+        assert(i == p->idx);
+        lua_pushstring(L, p->name);     /* k t s */
+        lua_pushinteger(L, p->idx);     /* v k t s */
+        lua_settable(L, -3);    /* t s */
+    }
+    lua_settable(L, LUA_REGISTRYINDEX); /* - */
+}
+
+
+@ @c
+/*
+    luajit compatibility layer for luatex lua5.2
+*/
+#ifdef LuajitTeX
+LUALIB_API void *luaL_testudata (lua_State *L, int ud, const char *tname) {
+    void *p = lua_touserdata(L, ud);
+    if (p != NULL) {  /* value is a userdata? */
+        if (lua_getmetatable(L, ud)) {  /* does it have a metatable? */
+            luaL_getmetatable(L, tname);  /* get correct metatable */
+        if (!lua_rawequal(L, -1, -2))  /* not the same? */
+            p = NULL;  /* value is a userdata with wrong metatable */
+        lua_pop(L, 2);  /* remove both metatables */
+        return p;
+        }
+    }
+    return NULL;  /* value is not a userdata with a metatable */
+}
+
+@ @c
+/* It's not ok. See lua-users.org/wiki/CompatibilityWithLuaFive for another solution */
+LUALIB_API void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup) {
+    /*luaL_checkversion(L);*/
+    luaL_checkstack(L, nup, "too many upvalues");
+    for (; l->name != NULL; l++) {  /* fill the table with given functions */
+        int i;
+        for (i = 0; i < nup; i++)  /* copy upvalues to the top */
+            lua_pushvalue(L, -nup);
+        lua_pushcclosure(L, l->func, nup);  /* closure with those upvalues */
+        lua_setfield(L, -(nup + 2), l->name);
+    }
+    lua_pop(L, nup);  /* remove upvalues */
+}
+
+@ @c
+LUALIB_API char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz) {
+    lua_State *L = B->L;
+    if (sz > LUAL_BUFFERSIZE )
+        luaL_error(L, "buffer too large");
+    return luaL_prepbuffer(B) ;
+}
+
+@ @c
+LUA_API int lua_compare (lua_State *L, int o1, int o2, int op) {
+    /*StkId o1, o2;*/
+    int i = 0;
+    lua_lock(L);  /* may call tag method */
+    /* o1 = index2addr(L, index1); */
+    /* o2 = index2addr(L, index2); */
+    /*if (isvalid(o1) && isvalid(o2)) {*/
+    switch (op) {
+        case LUA_OPEQ: i = lua_equal(L, o1, o2); break;
+        case LUA_OPLT: i = lua_lessthan(L, o1, o2); break;
+        case LUA_OPLE: i = (lua_lessthan(L, o1, o2) || lua_equal(L, o1, o2)) ; break;
+        default: luaL_error(L, "invalid option");
+    }
+    /*}*/
+    lua_unlock(L);
+    return i;
+}
+
+@ @c
+#endif

Modified: trunk/Build/source/texk/web2c/luatexdir/lua/luatex-api.h
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/lua/luatex-api.h	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/lua/luatex-api.h	2017-03-11 00:31:06 UTC (rev 43454)
@@ -105,9 +105,9 @@
 extern int luaopen_epdf(lua_State * L);
 extern int luaopen_pdfscanner(lua_State * L);
 extern int luaopen_mplib(lua_State * L);
+extern int luaopen_fio(lua_State * L);
 
-extern void open_oslibext(lua_State * L, int safer_option);
-extern int open_iolibext(lua_State * L);
+extern void open_oslibext(lua_State * L);
 extern void open_strlibext(lua_State * L);
 extern void open_lfslibext(lua_State * L);
 
@@ -154,6 +154,12 @@
 extern void undump_luac_registers(void);
 
 extern int lua_only;
+extern const char *lc_ctype;
+extern const char *lc_collate;
+extern const char *lc_numeric;
+
+
+
 #ifdef LuajitTeX
 extern int luajiton;
 extern char *jithash_hashname ;
@@ -1246,10 +1252,10 @@
     } \
 } while(0)
 
-#ifdef __MINGW32__
-extern FILE *_cairo_win32_tmpfile( void );
-#define tmpfile() _cairo_win32_tmpfile()
-#endif /* __MINGW32__ */
+#ifdef _WIN32
+extern FILE *_cairo_win_tmpfile( void );
+#define tmpfile() _cairo_win_tmpfile()
+#endif /* _WIN32 */
 
 #endif                          /* LUATEX_API_H */
 

Added: trunk/Build/source/texk/web2c/luatexdir/lua/luatex-core.c
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/lua/luatex-core.c	                        (rev 0)
+++ trunk/Build/source/texk/web2c/luatexdir/lua/luatex-core.c	2017-03-11 00:31:06 UTC (rev 43454)
@@ -0,0 +1,253 @@
+/* generated from and by luatex-core.lua */
+
+#include "lua.h"
+#include "lauxlib.h"
+
+int load_luatex_core_lua (lua_State * L);
+
+int load_luatex_core_lua (lua_State * L)
+{
+  static unsigned char luatex_core_lua[] = {
+    0x2d, 0x2d, 0x20, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
+    0x73, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x20, 0x3d,
+    0x20, 0x7b, 0x20, 0x7d, 0x20, 0x65, 0x6e, 0x64, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73,
+    0x20, 0x5b, 0x27, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x78, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x27, 0x5d,
+    0x20, 0x3d, 0x20, 0x7b, 0x0a, 0x2d, 0x2d, 0x20, 0x20, 0x20, 0x20, 0x20, 0x76, 0x65, 0x72, 0x73,
+    0x69, 0x6f, 0x6e, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x30, 0x31, 0x2c, 0x0a, 0x2d,
+    0x2d, 0x20, 0x20, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x20, 0x20,
+    0x3d, 0x20, 0x27, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20,
+    0x6c, 0x75, 0x61, 0x74, 0x65, 0x78, 0x27, 0x2c, 0x0a, 0x2d, 0x2d, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x27, 0x48, 0x61, 0x6e,
+    0x73, 0x20, 0x48, 0x61, 0x67, 0x65, 0x6e, 0x20, 0x26, 0x20, 0x4c, 0x75, 0x69, 0x67, 0x69, 0x20,
+    0x53, 0x63, 0x61, 0x72, 0x73, 0x6f, 0x27, 0x2c, 0x0a, 0x2d, 0x2d, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x63, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x3d, 0x20, 0x27, 0x4c, 0x75, 0x61,
+    0x54, 0x65, 0x58, 0x20, 0x44, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x20,
+    0x54, 0x65, 0x61, 0x6d, 0x27, 0x2c, 0x0a, 0x2d, 0x2d, 0x20, 0x7d, 0x0a, 0x0a, 0x4c, 0x55, 0x41,
+    0x54, 0x45, 0x58, 0x43, 0x4f, 0x52, 0x45, 0x56, 0x45, 0x52, 0x53, 0x49, 0x4f, 0x4e, 0x20, 0x3d,
+    0x20, 0x31, 0x2e, 0x30, 0x30, 0x32, 0x0a, 0x0a, 0x2d, 0x2d, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20,
+    0x66, 0x69, 0x6c, 0x65, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x20, 0x73,
+    0x6f, 0x6d, 0x65, 0x20, 0x4c, 0x75, 0x61, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e,
+    0x73, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x72, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x20,
+    0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
+    0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x0a, 0x2d, 0x2d, 0x20, 0x66, 0x75, 0x6e,
+    0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x20, 0x61, 0x73, 0x20, 0x4c, 0x75,
+    0x61, 0x54, 0x65, 0x58, 0x20, 0x3c, 0x3d, 0x20, 0x31, 0x2e, 0x30, 0x34, 0x20, 0x61, 0x6e, 0x64,
+    0x20, 0x64, 0x6f, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x74, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x77,
+    0x61, 0x79, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x73, 0x20, 0x75, 0x73, 0x20, 0x74, 0x6f,
+    0x20, 0x6b, 0x65, 0x65, 0x70, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x2d, 0x2d, 0x20, 0x6f, 0x72, 0x69,
+    0x67, 0x69, 0x6e, 0x61, 0x6c, 0x20, 0x69, 0x6f, 0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69,
+    0x65, 0x73, 0x20, 0x63, 0x6c, 0x65, 0x61, 0x6e, 0x2e, 0x20, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72,
+    0x6d, 0x61, 0x6e, 0x63, 0x65, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x62, 0x61, 0x62, 0x6c,
+    0x79, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x61, 0x20, 0x62, 0x69, 0x74, 0x20, 0x62, 0x65, 0x74,
+    0x74, 0x65, 0x72, 0x20, 0x6e, 0x6f, 0x77, 0x2e, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20,
+    0x74, 0x79, 0x70, 0x65, 0x2c, 0x20, 0x6e, 0x65, 0x78, 0x74, 0x2c, 0x20, 0x67, 0x65, 0x74, 0x6d,
+    0x65, 0x74, 0x61, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2c, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72,
+    0x65, 0x20, 0x3d, 0x20, 0x74, 0x79, 0x70, 0x65, 0x2c, 0x20, 0x6e, 0x65, 0x78, 0x74, 0x2c, 0x20,
+    0x67, 0x65, 0x74, 0x6d, 0x65, 0x74, 0x61, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2c, 0x20, 0x72, 0x65,
+    0x71, 0x75, 0x69, 0x72, 0x65, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x69, 0x6e, 0x64,
+    0x2c, 0x20, 0x67, 0x73, 0x75, 0x62, 0x20, 0x3d, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x2e,
+    0x66, 0x69, 0x6e, 0x64, 0x2c, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x67, 0x73, 0x75,
+    0x62, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x69, 0x6f, 0x5f, 0x6f, 0x70, 0x65, 0x6e,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x69,
+    0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x69, 0x6f, 0x5f,
+    0x70, 0x6f, 0x70, 0x65, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x3d, 0x20, 0x69, 0x6f, 0x2e, 0x70, 0x6f, 0x70, 0x65, 0x6e, 0x0a, 0x6c, 0x6f, 0x63, 0x61,
+    0x6c, 0x20, 0x69, 0x6f, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x69, 0x6f, 0x2e, 0x6c, 0x69, 0x6e, 0x65, 0x73,
+    0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x69, 0x6f, 0x5f, 0x72, 0x65, 0x61, 0x64,
+    0x6c, 0x69, 0x6e, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x66, 0x69,
+    0x6f, 0x2e, 0x72, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
+    0x20, 0x66, 0x69, 0x6f, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73,
+    0x73, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x20, 0x66, 0x69, 0x6f, 0x2e, 0x63, 0x68, 0x65, 0x63, 0x6b,
+    0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
+    0x20, 0x66, 0x69, 0x6f, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x66, 0x69, 0x6c, 0x65, 0x6e,
+    0x61, 0x6d, 0x65, 0x20, 0x20, 0x3d, 0x20, 0x66, 0x69, 0x6f, 0x2e, 0x72, 0x65, 0x63, 0x6f, 0x72,
+    0x64, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
+    0x20, 0x6d, 0x74, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x67, 0x65, 0x74, 0x6d, 0x65, 0x74, 0x61, 0x74, 0x61,
+    0x62, 0x6c, 0x65, 0x28, 0x69, 0x6f, 0x2e, 0x73, 0x74, 0x64, 0x65, 0x72, 0x72, 0x29, 0x0a, 0x6c,
+    0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6d, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x6d, 0x74, 0x2e, 0x6c, 0x69,
+    0x6e, 0x65, 0x73, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x61, 0x66, 0x65, 0x72, 0x6f,
+    0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20,
+    0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x73, 0x61, 0x66, 0x65, 0x72, 0x5f, 0x6f, 0x70, 0x74,
+    0x69, 0x6f, 0x6e, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x68, 0x65, 0x6c, 0x6c, 0x65,
+    0x73, 0x63, 0x61, 0x70, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20,
+    0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x73, 0x68, 0x65, 0x6c, 0x6c, 0x5f, 0x65, 0x73, 0x63,
+    0x61, 0x70, 0x65, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x20, 0x30, 0x20, 0x28, 0x64, 0x69,
+    0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x29, 0x20, 0x31, 0x20, 0x28, 0x72, 0x65, 0x73, 0x74, 0x72,
+    0x69, 0x63, 0x74, 0x65, 0x64, 0x29, 0x20, 0x32, 0x20, 0x28, 0x65, 0x76, 0x65, 0x72, 0x79, 0x74,
+    0x68, 0x69, 0x6e, 0x67, 0x29, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6b, 0x70, 0x73, 0x65,
+    0x75, 0x73, 0x65, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x3d, 0x20, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x6b, 0x70, 0x73, 0x65, 0x5f, 0x75, 0x73,
+    0x65, 0x64, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x20, 0x30, 0x20, 0x31,
+    0x0a, 0x0a, 0x69, 0x6f, 0x2e, 0x73, 0x61, 0x76, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x69, 0x6f,
+    0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x20, 0x20, 0x2d, 0x2d, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65,
+    0x20, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x0a, 0x69, 0x6f, 0x2e, 0x73, 0x61,
+    0x76, 0x65, 0x64, 0x5f, 0x70, 0x6f, 0x70, 0x65, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x69, 0x6f, 0x5f, 0x70, 0x6f, 0x70, 0x65, 0x6e, 0x20,
+    0x2d, 0x2d, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x65, 0x63,
+    0x74, 0x65, 0x64, 0x0a, 0x69, 0x6f, 0x2e, 0x73, 0x61, 0x76, 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x6e,
+    0x65, 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20,
+    0x69, 0x6f, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x20, 0x2d, 0x2d, 0x20, 0x61, 0x6c, 0x77, 0x61,
+    0x79, 0x73, 0x20, 0x72, 0x65, 0x61, 0x64, 0x6f, 0x6e, 0x6c, 0x79, 0x0a, 0x6d, 0x74, 0x2e, 0x73,
+    0x61, 0x76, 0x65, 0x64, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x6d, 0x74, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x73,
+    0x20, 0x2d, 0x2d, 0x20, 0x61, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x20, 0x72, 0x65, 0x61, 0x64, 0x6f,
+    0x6e, 0x6c, 0x79, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74,
+    0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6f, 0x5f, 0x6f, 0x70,
+    0x65, 0x6e, 0x28, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x68, 0x6f, 0x77, 0x29, 0x0a, 0x20, 0x20, 0x20,
+    0x20, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x68, 0x6f, 0x77, 0x20, 0x74, 0x68, 0x65, 0x6e,
+    0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x6f, 0x77, 0x20, 0x3d, 0x20, 0x27,
+    0x72, 0x27, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c,
+    0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x20, 0x3d, 0x20, 0x69, 0x6f, 0x5f, 0x6f, 0x70, 0x65, 0x6e,
+    0x28, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x68, 0x6f, 0x77, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69,
+    0x66, 0x20, 0x66, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x69, 0x66, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x68, 0x6f, 0x77, 0x29, 0x20, 0x3d, 0x3d,
+    0x20, 0x27, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x27, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x66, 0x69,
+    0x6e, 0x64, 0x28, 0x68, 0x6f, 0x77, 0x2c, 0x27, 0x77, 0x27, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e,
+    0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x69, 0x6f,
+    0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x28,
+    0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x27, 0x77, 0x27, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x66, 0x69, 0x6f, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x66, 0x69, 0x6c,
+    0x65, 0x6e, 0x61, 0x6d, 0x65, 0x28, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x27, 0x72, 0x27, 0x29, 0x0a,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20,
+    0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x66,
+    0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63,
+    0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6f, 0x5f, 0x6f,
+    0x70, 0x65, 0x6e, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x6f, 0x6e, 0x6c, 0x79, 0x28, 0x6e, 0x61, 0x6d,
+    0x65, 0x2c, 0x68, 0x6f, 0x77, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x68, 0x6f,
+    0x77, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68,
+    0x6f, 0x77, 0x20, 0x3d, 0x20, 0x27, 0x72, 0x27, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73,
+    0x65, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x6f, 0x77, 0x20, 0x3d, 0x20,
+    0x67, 0x73, 0x75, 0x62, 0x28, 0x68, 0x6f, 0x77, 0x2c, 0x27, 0x5b, 0x5e, 0x72, 0x62, 0x5d, 0x27,
+    0x2c, 0x27, 0x27, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20,
+    0x68, 0x6f, 0x77, 0x20, 0x3d, 0x3d, 0x20, 0x27, 0x27, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x68, 0x6f, 0x77, 0x20, 0x3d,
+    0x20, 0x27, 0x72, 0x27, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64,
+    0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x63,
+    0x61, 0x6c, 0x20, 0x66, 0x20, 0x3d, 0x20, 0x69, 0x6f, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x28, 0x6e,
+    0x61, 0x6d, 0x65, 0x2c, 0x68, 0x6f, 0x77, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20,
+    0x66, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66,
+    0x69, 0x6f, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d,
+    0x65, 0x28, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x27, 0x72, 0x27, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20,
+    0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x66,
+    0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63,
+    0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6f, 0x5f, 0x70,
+    0x6f, 0x70, 0x65, 0x6e, 0x28, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x2e, 0x2e, 0x2e, 0x29, 0x0a, 0x20,
+    0x20, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6f, 0x6b, 0x61, 0x79, 0x2c, 0x20, 0x66,
+    0x6f, 0x75, 0x6e, 0x64, 0x20, 0x3d, 0x20, 0x66, 0x69, 0x6f, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b,
+    0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x28, 0x6e, 0x61, 0x6d, 0x65, 0x29,
+    0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6f, 0x6b, 0x61, 0x79, 0x20, 0x61, 0x6e, 0x64,
+    0x20, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x69, 0x6f, 0x5f, 0x70, 0x6f,
+    0x70, 0x65, 0x6e, 0x28, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x2c, 0x2e, 0x2e, 0x2e, 0x29, 0x0a, 0x20,
+    0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61,
+    0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x75, 0x61, 0x74, 0x65,
+    0x78, 0x5f, 0x69, 0x6f, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x28, 0x6e, 0x61, 0x6d, 0x65, 0x29,
+    0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x20, 0x3d, 0x20, 0x69,
+    0x6f, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x28, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x27, 0x72, 0x27, 0x29,
+    0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x66, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x66, 0x75,
+    0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x66, 0x69, 0x6f, 0x5f,
+    0x72, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x28, 0x66, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a,
+    0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74,
+    0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6f, 0x5f, 0x72, 0x65,
+    0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x28, 0x66, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65,
+    0x74, 0x75, 0x72, 0x6e, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x29, 0x0a,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x66,
+    0x69, 0x6f, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x28, 0x66, 0x29, 0x0a, 0x20,
+    0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x69, 0x6f, 0x2e, 0x6c,
+    0x69, 0x6e, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6f,
+    0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x0a, 0x6d, 0x74, 0x2e, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x20,
+    0x3d, 0x20, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6f, 0x5f, 0x72, 0x65, 0x61, 0x64,
+    0x6c, 0x69, 0x6e, 0x65, 0x0a, 0x0a, 0x2d, 0x2d, 0x20, 0x57, 0x65, 0x20, 0x61, 0x73, 0x73, 0x75,
+    0x6d, 0x65, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x74, 0x6f,
+    0x20, 0x62, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20,
+    0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x20,
+    0x6f, 0x66, 0x20, 0x6b, 0x70, 0x73, 0x65, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73,
+    0x20, 0x74, 0x68, 0x65, 0x0a, 0x2d, 0x2d, 0x20, 0x63, 0x61, 0x73, 0x65, 0x20, 0x69, 0x6e, 0x20,
+    0x43, 0x6f, 0x6e, 0x54, 0x65, 0x58, 0x74, 0x2e, 0x0a, 0x0a, 0x69, 0x66, 0x20, 0x6b, 0x70, 0x73,
+    0x65, 0x75, 0x73, 0x65, 0x64, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
+    0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x20, 0x20, 0x3d, 0x20,
+    0x6c, 0x75, 0x61, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6f, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x0a, 0x20,
+    0x20, 0x20, 0x20, 0x69, 0x6f, 0x2e, 0x70, 0x6f, 0x70, 0x65, 0x6e, 0x20, 0x3d, 0x20, 0x6c, 0x75,
+    0x61, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6f, 0x5f, 0x70, 0x6f, 0x70, 0x65, 0x6e, 0x0a, 0x0a, 0x65,
+    0x6e, 0x64, 0x0a, 0x0a, 0x69, 0x66, 0x20, 0x73, 0x61, 0x66, 0x65, 0x72, 0x6f, 0x70, 0x74, 0x69,
+    0x6f, 0x6e, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x0a, 0x20, 0x20,
+    0x20, 0x20, 0x6f, 0x73, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x20, 0x3d, 0x20, 0x6e,
+    0x69, 0x6c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x73, 0x2e, 0x73, 0x70, 0x61, 0x77, 0x6e, 0x20,
+    0x20, 0x20, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x73, 0x2e, 0x65,
+    0x78, 0x65, 0x63, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x0a, 0x20, 0x20, 0x20,
+    0x20, 0x6f, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x65, 0x6e, 0x76, 0x20, 0x20, 0x3d, 0x20, 0x6e, 0x69,
+    0x6c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x6d, 0x70, 0x64, 0x69, 0x72,
+    0x20, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6f, 0x2e, 0x70,
+    0x6f, 0x70, 0x65, 0x6e, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x0a, 0x20, 0x20, 0x20,
+    0x20, 0x69, 0x6f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x6e, 0x69,
+    0x6c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x73, 0x2e, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65,
+    0x20, 0x20, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x73, 0x2e, 0x72,
+    0x65, 0x6d, 0x6f, 0x76, 0x65, 0x20, 0x20, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x0a, 0x0a, 0x20, 0x20,
+    0x20, 0x20, 0x69, 0x6f, 0x2e, 0x74, 0x6d, 0x70, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0x6e,
+    0x69, 0x6c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6f, 0x2e, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74,
+    0x20, 0x20, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x66, 0x73,
+    0x2e, 0x63, 0x68, 0x64, 0x69, 0x72, 0x20, 0x20, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x0a, 0x20, 0x20,
+    0x20, 0x20, 0x6c, 0x66, 0x73, 0x2e, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x6e,
+    0x69, 0x6c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x66, 0x73, 0x2e, 0x74, 0x6f, 0x75, 0x63, 0x68,
+    0x20, 0x20, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x66, 0x73, 0x2e,
+    0x72, 0x6d, 0x64, 0x69, 0x72, 0x20, 0x20, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x0a, 0x20, 0x20, 0x20,
+    0x20, 0x6c, 0x66, 0x73, 0x2e, 0x6d, 0x6b, 0x64, 0x69, 0x72, 0x20, 0x20, 0x3d, 0x20, 0x6e, 0x69,
+    0x6c, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6f, 0x2e, 0x73, 0x61, 0x76, 0x65, 0x64, 0x5f,
+    0x70, 0x6f, 0x70, 0x65, 0x6e, 0x20, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x0a, 0x20, 0x20, 0x20, 0x20,
+    0x69, 0x6f, 0x2e, 0x73, 0x61, 0x76, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x20, 0x20, 0x3d,
+    0x20, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x78, 0x5f, 0x69, 0x6f, 0x5f, 0x6f, 0x70, 0x65, 0x6e, 0x5f,
+    0x72, 0x65, 0x61, 0x64, 0x6f, 0x6e, 0x6c, 0x79, 0x0a, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x69,
+    0x66, 0x20, 0x73, 0x61, 0x66, 0x65, 0x72, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x3d, 0x3d,
+    0x20, 0x31, 0x20, 0x6f, 0x72, 0x20, 0x73, 0x68, 0x65, 0x6c, 0x6c, 0x65, 0x73, 0x63, 0x61, 0x70,
+    0x65, 0x20, 0x7e, 0x3d, 0x20, 0x32, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x0a, 0x20, 0x20, 0x20,
+    0x20, 0x66, 0x66, 0x69, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x28, 0x27,
+    0x66, 0x66, 0x69, 0x27, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6b, 0x2c,
+    0x20, 0x76, 0x20, 0x69, 0x6e, 0x20, 0x6e, 0x65, 0x78, 0x74, 0x2c, 0x20, 0x66, 0x66, 0x69, 0x20,
+    0x64, 0x6f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6b, 0x20,
+    0x7e, 0x3d, 0x20, 0x27, 0x67, 0x63, 0x27, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x66, 0x69, 0x5b, 0x6b, 0x5d, 0x20,
+    0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e,
+    0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x66,
+    0x69, 0x20, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x0a, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x2d, 0x2d, 0x20,
+    0x6f, 0x73, 0x2e, 0x5b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x7c, 0x6f, 0x73, 0x2e, 0x73,
+    0x70, 0x61, 0x77, 0x6e, 0x7c, 0x6f, 0x73, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x5d, 0x20, 0x61, 0x6c,
+    0x72, 0x65, 0x61, 0x64, 0x79, 0x20, 0x61, 0x72, 0x65, 0x20, 0x73, 0x68, 0x65, 0x6c, 0x6c, 0x65,
+    0x73, 0x63, 0x61, 0x70, 0x65, 0x20, 0x61, 0x77, 0x61, 0x72, 0x65, 0x29, 0x0a, 0x0a, 0x0a, 0x69,
+    0x66, 0x20, 0x6d, 0x64, 0x35, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
+    0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x75, 0x6d, 0x20, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x6d,
+    0x64, 0x35, 0x2e, 0x73, 0x75, 0x6d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
+    0x20, 0x67, 0x73, 0x75, 0x62, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67,
+    0x2e, 0x67, 0x73, 0x75, 0x62, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20,
+    0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x3d, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x2e,
+    0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
+    0x20, 0x62, 0x79, 0x74, 0x65, 0x20, 0x20, 0x20, 0x3d, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67,
+    0x2e, 0x62, 0x79, 0x74, 0x65, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74,
+    0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x64, 0x35, 0x2e, 0x73, 0x75, 0x6d, 0x68, 0x65, 0x78, 0x61, 0x28,
+    0x6b, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72,
+    0x6e, 0x20, 0x28, 0x67, 0x73, 0x75, 0x62, 0x28, 0x73, 0x75, 0x6d, 0x28, 0x6b, 0x29, 0x2c, 0x20,
+    0x22, 0x2e, 0x22, 0x2c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x63, 0x29,
+    0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74,
+    0x75, 0x72, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x28, 0x22, 0x25, 0x30, 0x32, 0x78,
+    0x22, 0x2c, 0x62, 0x79, 0x74, 0x65, 0x28, 0x63, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64,
+    0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6d,
+    0x64, 0x35, 0x2e, 0x73, 0x75, 0x6d, 0x48, 0x45, 0x58, 0x41, 0x28, 0x6b, 0x29, 0x0a, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x28, 0x67, 0x73,
+    0x75, 0x62, 0x28, 0x73, 0x75, 0x6d, 0x28, 0x6b, 0x29, 0x2c, 0x20, 0x22, 0x2e, 0x22, 0x2c, 0x20,
+    0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x63, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20,
+    0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x66,
+    0x6f, 0x72, 0x6d, 0x61, 0x74, 0x28, 0x22, 0x25, 0x30, 0x32, 0x58, 0x22, 0x2c, 0x62, 0x79, 0x74,
+    0x65, 0x28, 0x63, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e,
+    0x64, 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, 0x0a, 0x65, 0x6e, 0x64,
+    0x0a, 0x0a, 0x00
+  };
+  return luaL_dostring(L, (const char*) luatex_core_lua);
+}
\ No newline at end of file

Added: trunk/Build/source/texk/web2c/luatexdir/lua/luatex-core.lua
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/lua/luatex-core.lua	                        (rev 0)
+++ trunk/Build/source/texk/web2c/luatexdir/lua/luatex-core.lua	2017-03-11 00:31:06 UTC (rev 43454)
@@ -0,0 +1,209 @@
+-- if not modules then modules = { } end modules ['luatex-core'] = {
+--     version   = 1.001,
+--     comment   = 'companion to luatex',
+--     author    = 'Hans Hagen & Luigi Scarso',
+--     copyright = 'LuaTeX Development Team',
+-- }
+
+LUATEXCOREVERSION = 1.002
+
+-- This file overloads some Lua functions. The readline variants provide the same
+-- functionality as LuaTeX <= 1.04 and doing it this way permits us to keep the
+-- original io libraries clean. Performance is probably even a bit better now.
+
+local type, next, getmetatable, require = type, next, getmetatable, require
+local find, gsub = string.find, string.gsub
+
+local io_open             = io.open
+local io_popen            = io.popen
+local io_line             = io.lines
+
+local fio_readline        = fio.readline
+local fio_checkpermission = fio.checkpermission
+local fio_recordfilename  = fio.recordfilename
+
+local mt                  = getmetatable(io.stderr)
+local mt_lines            = mt.lines
+local saferoption         = status.safer_option
+local shellescape         = status.shell_escape     -- 0 (disabled) 1 (restricted) 2 (everything)
+local kpseused            = status.kpse_used        -- 0 1
+
+io.saved_open             = io_open  -- can be protected
+io.saved_popen            = io_popen -- can be protected
+io.saved_lines            = io_lines -- always readonly
+mt.saved_lines            = mt_lines -- always readonly
+
+local function luatex_io_open(name,how)
+    if not how then
+        how = 'r'
+    end
+    local f = io_open(name,how)
+    if f then
+        if type(how) == 'string' and find(how,'w') then
+            fio_recordfilename(name,'w')
+        else
+            fio_recordfilename(name,'r')
+        end
+    end
+    return f
+end
+
+local function luatex_io_open_readonly(name,how)
+    if how then
+        how = 'r'
+    else
+        how = gsub(how,'[^rb]','')
+        if how == '' then
+            how = 'r'
+        end
+    end
+    local f = io_open(name,how)
+    if f then
+        fio_recordfilename(name,'r')
+    end
+    return f
+end
+
+local function luatex_io_popen(name,...)
+    local okay, found = fio_checkpermission(name)
+    if okay and found then
+        return io_popen(found,...)
+    end
+end
+
+local function luatex_io_lines(name)
+    local f = io_open(name,'r')
+    if f then
+        return function()
+            return fio_readline(f)
+        end
+    end
+end
+
+local function luatex_io_readline(f)
+    return function()
+        return fio_readline(f)
+    end
+end
+
+io.lines = luatex_io_lines
+mt.lines = luatex_io_readline
+
+-- We assume management to be provided by the replacement of kpse. This is the
+-- case in ConTeXt.
+
+if kpseused == 1 then
+
+    io.open  = luatex_io_open
+    io.popen = luatex_io_popen
+
+end
+
+if saferoption == 1 then
+
+    os.execute = nil
+    os.spawn   = nil
+    os.exec    = nil
+    os.setenv  = nil
+    os.tempdir = nil
+
+    io.popen   = nil
+    io.open    = nil
+
+    os.rename  = nil
+    os.remove  = nil
+
+    io.tmpfile = nil
+    io.output  = nil
+
+    lfs.chdir  = nil
+    lfs.lock   = nil
+    lfs.touch  = nil
+    lfs.rmdir  = nil
+    lfs.mkdir  = nil
+
+    io.saved_popen = nil
+    io.saved_open  = luatex_io_open_readonly
+
+end
+
+if saferoption == 1 or shellescape ~= 2 then
+
+    ffi = require('ffi')
+    for k, v in next, ffi do
+        if k ~= 'gc' then
+            ffi[k] = nil
+        end
+    end
+    ffi = nil
+end
+
+-- os.[execute|os.spawn|os.exec] already are shellescape aware)
+
+
+if md5 then
+
+    local sum    = md5.sum
+    local gsub   = string.gsub
+    local format = string.format
+    local byte   = string.byte
+
+    function md5.sumhexa(k)
+        return (gsub(sum(k), ".", function(c)
+            return format("%02x",byte(c))
+        end))
+    end
+
+    function md5.sumHEXA(k)
+        return (gsub(sum(k), ".", function(c)
+            return format("%02X",byte(c))
+        end))
+    end
+
+end
+
+if utilities and utilities.merger and utilities.merger.compact then
+
+    local byte, format, gmatch = string.byte, string.format, string.gmatch
+    local concat = table.concat
+
+    local data = gsub(io.loaddata('luatex-core.lua'),'if%s+utilities.*','')
+    local t = { }
+    local r = { }
+    local n = 0
+    local d = gsub(data,'\r\n','\n')      -- be nice for unix
+    local s = utilities.merger.compact(d) -- no comments and less spaces
+
+    t[#t+1] = '/* generated from and by luatex-core.lua */'
+    t[#t+1] = ''
+ -- t[#t+1] = format('/*\n\n%s\n\n*/',d)
+ -- t[#t+1] = ''
+    t[#t+1] = '#include "lua.h"'
+    t[#t+1] = '#include "lauxlib.h"'
+    t[#t+1] = ''
+    t[#t+1] = 'int load_luatex_core_lua (lua_State * L);'
+    t[#t+1] = ''
+    t[#t+1] = 'int load_luatex_core_lua (lua_State * L)'
+    t[#t+1] = '{'
+    t[#t+1] = '  static unsigned char luatex_core_lua[] = {'
+    for c in gmatch(d,'.') do
+        if n == 16 then
+            n = 1
+            t[#t+1] = '    ' .. concat(r,', ') .. ','
+        else
+            n = n + 1
+        end
+        r[n] = format('0x%02x',byte(c))
+    end
+    n = n + 1
+    r[n] = '0x00'
+    t[#t+1] = '    ' .. concat(r,', ',1,n)
+    t[#t+1] = '  };'
+ -- t[#t+1] = format('unsigned int luatex_core_lua_len = 0x%x;',#d+1)
+    t[#t+1] = '  return luaL_dostring(L, (const char*) luatex_core_lua);'
+    t[#t+1] = '}'
+
+    io.savedata('luatex-core.c',concat(t,'\n'))
+    io.savedata('luatex-core-stripped.lua',s)
+
+end

Added: trunk/Build/source/texk/web2c/luatexdir/lua/mplibstuff.w
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/lua/mplibstuff.w	                        (rev 0)
+++ trunk/Build/source/texk/web2c/luatexdir/lua/mplibstuff.w	2017-03-11 00:31:06 UTC (rev 43454)
@@ -0,0 +1,93 @@
+% mplibstuff.w
+%
+% Copyright 2017 LuaTeX team <bugs@@luatex.org>
+%
+% 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 <http://www.gnu.org/licenses/>.
+
+@ PNG and SVG backends are not available in \LuaTeX, because it's complex
+to manage the math formulas at run time. In this respect |PostScript| and the highlevel |objects|
+are better, and they are the standard way. Another problem is how to emit the warning:
+the |normal\_warning| function is not available when \LuaTeX\ is called as LUA only. 
+
+
+
+ at c
+#include <stdio.h>
+
+extern void normal_warning(const char *t, const char *p);
+extern int lua_only;
+#define mplibstuff_message(BACKEND)  do {                          \
+ if (lua_only) {                                                   \
+   fprintf(stdout,"mplib: " #BACKEND " backend not available.\n"); \
+ } else {                                                          \
+   normal_warning("mplib",  #BACKEND " backend not available.");   \
+ }                                                                 \
+} while (0)
+
+ 
+@ @c
+void mp_png_backend_initialize (void *mp);
+void mp_png_backend_free (void *mp);
+int mp_png_gr_ship_out (void *hh, void *options, int standalone);
+int mp_png_ship_out (void *hh, const char *options);
+
+@ @c
+void mp_svg_backend_initialize (void *mp);
+void mp_svg_backend_free (void *mp);
+int mp_svg_ship_out (void *hh, int prologues);
+int mp_svg_gr_ship_out (void *hh, int qprologues, int standalone);
+
+@ @c
+void mp_png_backend_initialize (void *mp)                         {return; }  /*{mplibstuff_message(1png);return;}*/
+void mp_png_backend_free (void *mp)                               {return; }   /*{mplibstuff_message(png);return;}*/
+int mp_png_gr_ship_out (void *hh, void *options, int standalone)  {mplibstuff_message(png);return 1;}
+int mp_png_ship_out (void *hh, const char *options)               {mplibstuff_message(png);return 1;}
+
+@ @c
+void mp_svg_backend_initialize (void *mp)                         {return;}   /*{mplibstuff_message(svg);return;}*/
+void mp_svg_backend_free (void *mp)                               {return;}   /*{mplibstuff_message(svg);return;}*/
+int mp_svg_ship_out (void *hh, int prologues)                     {mplibstuff_message(svg);return 1;}
+int mp_svg_gr_ship_out (void *hh, int qprologues, int standalone) {mplibstuff_message(svg);return 1;}
+
+@ @c
+const char*
+cairo_version_string (void);
+const char*
+pixman_version_string (void);
+#define CAIRO_VERSION_STRING "CAIRO NOT AVAILABLE"
+const char *COMPILED_CAIRO_VERSION_STRING = CAIRO_VERSION_STRING;
+#define PIXMAN_VERSION_STRING "PIXMAN NOT AVAILABLE"
+const char *COMPILED_PIXMAN_VERSION_STRING = PIXMAN_VERSION_STRING;
+
+const char*
+cairo_version_string (void)
+{
+ return CAIRO_VERSION_STRING;
+}
+
+const char*
+pixman_version_string (void)
+{
+ return PIXMAN_VERSION_STRING;
+}
+
+
+
+
+
+@ @c
+char png_libpng_ver[] =      "PNG NOT AVAILABLE";
+

Modified: trunk/Build/source/texk/web2c/luatexdir/luaffi/README
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/luaffi/README	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/luaffi/README	2017-03-11 00:31:06 UTC (rev 43454)
@@ -8,7 +8,7 @@
 The ffi module is available for:
  
     archictures       : ARCH_X86 and ARCH_X64,
-    operating systems : OS_CE, OS_WIN, OS_LINUX, OS BD and OS_POSIX
+    operating systems : OS_CE, OS_WIN, OS_LINUX, OS_BSD and OS_POSIX
 
 The ARM processor is currently not supported. There are subtle
 differences between this module and the one in luajitTeX 

Modified: trunk/Build/source/texk/web2c/luatexdir/luaffi/ffi.c
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/luaffi/ffi.c	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/luaffi/ffi.c	2017-03-11 00:31:06 UTC (rev 43454)
@@ -3567,7 +3567,7 @@
 "The ffi module is available for:\n"
 "\n" 
 "    archictures       : ARCH_X86 and ARCH_X64,\n"
-"    operating systems : OS_CE, OS_WIN, OS_LINUX, OS BD and OS_POSIX\n"
+"    operating systems : OS_CE, OS_WIN, OS_LINUX, OS_BSD and OS_POSIX\n"
 "\n"
 "The ARM processor is currently not supported. There are subtle\n"
 "differences between this module and the one in luajitTeX \n"

Modified: trunk/Build/source/texk/web2c/luatexdir/luaffi/ffi.h
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/luaffi/ffi.h	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/luaffi/ffi.h	2017-03-11 00:31:06 UTC (rev 43454)
@@ -151,7 +151,14 @@
 #if (defined ARCH_X86 || defined ARCH_X64) && (defined OS_CE || defined OS_WIN || defined OS_LINUX || defined OS_BSD || defined OS_POSIX || defined OS_OSX)
 #define FFI_ENABLE_LUATEX_INTERFACE
 #endif 
+/* for the moment */
+#if (defined __CYGWIN__)
+#undef FFI_ENABLE_LUATEX_INTERFACE
+#endif 
 
+  
+
+
 #ifdef _WIN32
 
 #   ifdef UNDER_CE

Modified: trunk/Build/source/texk/web2c/luatexdir/luafontloader/fontforge/fontforge/configure-pfaedit.h
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/luafontloader/fontforge/fontforge/configure-pfaedit.h	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/luafontloader/fontforge/fontforge/configure-pfaedit.h	2017-03-11 00:31:06 UTC (rev 43454)
@@ -291,11 +291,11 @@
 
 
 
-#ifdef __MINGW32__
+#ifdef _WIN32
 #include <stdio.h>
-extern FILE *_cairo_win32_tmpfile( void );
-#define tmpfile() _cairo_win32_tmpfile()
-#endif /* __MINGW32__ */
+extern FILE *_cairo_win_tmpfile( void );
+#define tmpfile() _cairo_win_tmpfile()
+#endif /* _WIN32 */
 
 
 #endif

Modified: trunk/Build/source/texk/web2c/luatexdir/luatex.c
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/luatex.c	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/luatex.c	2017-03-11 00:31:06 UTC (rev 43454)
@@ -29,9 +29,9 @@
 #define TeX
 
 int luatex_version = 100;        /* \.{\\luatexversion}  */
-int luatex_revision = '3';      /* \.{\\luatexrevision}  */
+int luatex_revision = '4';      /* \.{\\luatexrevision}  */
 int luatex_date_info = 2017021514;     /* the compile date is now hardwired :YEAR MONTH DAY HOUR*/
-const char *luatex_version_string = "1.0.3";
+const char *luatex_version_string = "1.0.4";
 const char *engine_name = my_name;     /* the name of this engine */
 
 #include <kpathsea/c-ctype.h>

Modified: trunk/Build/source/texk/web2c/luatexdir/ptexlib.h
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/ptexlib.h	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/ptexlib.h	2017-03-11 00:31:06 UTC (rev 43454)
@@ -291,7 +291,7 @@
 /* lua/ltexiolib.c */
 void flush_loggable_info(void);
 
-/* lua/luastuff.w and lua/luajitstuff.w */
+/* lua/luastuff.w  */
 void luafunctioncall(int slot);
 
 /* lua/luastuff.c */

Modified: trunk/Build/source/texk/web2c/luatexdir/tex/commands.w
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/tex/commands.w	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/tex/commands.w	2017-03-11 00:31:06 UTC (rev 43454)
@@ -1,879 +1,883 @@
-% commands.w
-%
-% Copyright 2009-2010 Taco Hoekwater <taco@@luatex.org>
-%
-% 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 <http://www.gnu.org/licenses/>.
-
-\def\eTeX{e-\TeX}
-
-@ @c
-
-
-#include "ptexlib.h"
-
-@ The symbolic names for glue parameters are put into \TeX's hash table
-by using the routine called |primitive|, defined below. Let us enter them
-now, so that we don't have to list all those parameter names anywhere else.
-
- at c
-void initialize_commands(void)
-{
-
-    primitive_tex("lineskip", assign_glue_cmd, glue_base + line_skip_code, glue_base);
-    primitive_tex("baselineskip", assign_glue_cmd, glue_base + baseline_skip_code, glue_base);
-    primitive_tex("parskip", assign_glue_cmd, glue_base + par_skip_code, glue_base);
-    primitive_tex("abovedisplayskip", assign_glue_cmd, glue_base + above_display_skip_code, glue_base);
-    primitive_tex("belowdisplayskip", assign_glue_cmd, glue_base + below_display_skip_code, glue_base);
-    primitive_tex("abovedisplayshortskip", assign_glue_cmd, glue_base + above_display_short_skip_code, glue_base);
-    primitive_tex("belowdisplayshortskip", assign_glue_cmd, glue_base + below_display_short_skip_code, glue_base);
-    primitive_tex("leftskip", assign_glue_cmd, glue_base + left_skip_code, glue_base);
-    primitive_tex("rightskip", assign_glue_cmd, glue_base + right_skip_code, glue_base);
-    primitive_tex("topskip", assign_glue_cmd, glue_base + top_skip_code, glue_base);
-    primitive_tex("splittopskip", assign_glue_cmd, glue_base + split_top_skip_code, glue_base);
-    primitive_tex("tabskip", assign_glue_cmd, glue_base + tab_skip_code, glue_base);
-    primitive_tex("spaceskip", assign_glue_cmd, glue_base + space_skip_code, glue_base);
-    primitive_tex("xspaceskip", assign_glue_cmd, glue_base + xspace_skip_code, glue_base);
-    primitive_tex("parfillskip", assign_glue_cmd, glue_base + par_fill_skip_code, glue_base);
-    primitive_tex("thinmuskip", assign_mu_glue_cmd, glue_base + thin_mu_skip_code, glue_base + thin_mu_skip_code);
-    primitive_tex("medmuskip", assign_mu_glue_cmd, glue_base + med_mu_skip_code, glue_base + thin_mu_skip_code);
-    primitive_tex("thickmuskip", assign_mu_glue_cmd, glue_base + thick_mu_skip_code, glue_base + thin_mu_skip_code);
-    primitive_luatex("mathsurroundskip", assign_glue_cmd, glue_base + math_skip_code, glue_base);
-    primitive_luatex("mathsurroundmode", assign_int_cmd, int_base + math_skip_mode_code, int_base);
-    primitive_tex("output", assign_toks_cmd, output_routine_loc, local_base);
-    primitive_tex("everypar", assign_toks_cmd, every_par_loc, local_base);
-    primitive_tex("everymath", assign_toks_cmd, every_math_loc, local_base);
-    primitive_tex("everydisplay", assign_toks_cmd, every_display_loc, local_base);
-    primitive_tex("everyhbox", assign_toks_cmd, every_hbox_loc, local_base);
-    primitive_tex("everyvbox", assign_toks_cmd, every_vbox_loc, local_base);
-    primitive_tex("everyjob", assign_toks_cmd, every_job_loc, local_base);
-    primitive_tex("everycr", assign_toks_cmd, every_cr_loc, local_base);
-    primitive_tex("errhelp", assign_toks_cmd, err_help_loc, local_base);
-
-    /* The integer parameter names must be entered into the hash table */
-
-    primitive_tex("pretolerance", assign_int_cmd, int_base + pretolerance_code, int_base);
-    primitive_tex("tolerance", assign_int_cmd, int_base + tolerance_code, int_base);
-    primitive_tex("linepenalty", assign_int_cmd, int_base + line_penalty_code, int_base);
-    primitive_tex("hyphenpenalty", assign_int_cmd, int_base + hyphen_penalty_code, int_base);
-    primitive_tex("exhyphenpenalty", assign_int_cmd, int_base + ex_hyphen_penalty_code, int_base);
-    primitive_tex("clubpenalty", assign_int_cmd, int_base + club_penalty_code, int_base);
-    primitive_tex("widowpenalty", assign_int_cmd, int_base + widow_penalty_code, int_base);
-    primitive_tex("displaywidowpenalty", assign_int_cmd, int_base + display_widow_penalty_code, int_base);
-    primitive_tex("brokenpenalty", assign_int_cmd, int_base + broken_penalty_code, int_base);
-    primitive_tex("binoppenalty", assign_int_cmd, int_base + bin_op_penalty_code, int_base);
-    primitive_tex("relpenalty", assign_int_cmd, int_base + rel_penalty_code, int_base);
-    primitive_tex("predisplaypenalty", assign_int_cmd, int_base + pre_display_penalty_code, int_base);
-    primitive_tex("postdisplaypenalty", assign_int_cmd, int_base + post_display_penalty_code, int_base);
-    primitive_tex("interlinepenalty", assign_int_cmd, int_base + inter_line_penalty_code, int_base);
-    primitive_tex("doublehyphendemerits", assign_int_cmd, int_base + double_hyphen_demerits_code, int_base);
-    primitive_tex("finalhyphendemerits", assign_int_cmd, int_base + final_hyphen_demerits_code, int_base);
-    primitive_tex("adjdemerits", assign_int_cmd, int_base + adj_demerits_code, int_base);
-    primitive_tex("mag", assign_int_cmd, int_base + mag_code, int_base);
-    primitive_tex("delimiterfactor", assign_int_cmd, int_base + delimiter_factor_code, int_base);
-    primitive_tex("looseness", assign_int_cmd, int_base + looseness_code, int_base);
-    primitive_tex("time", assign_int_cmd, int_base + time_code, int_base);
-    primitive_tex("day", assign_int_cmd, int_base + day_code, int_base);
-    primitive_tex("month", assign_int_cmd, int_base + month_code, int_base);
-    primitive_tex("year", assign_int_cmd, int_base + year_code, int_base);
-    primitive_tex("showboxbreadth", assign_int_cmd, int_base + show_box_breadth_code, int_base);
-    primitive_tex("showboxdepth", assign_int_cmd, int_base + show_box_depth_code, int_base);
-    primitive_tex("hbadness", assign_int_cmd, int_base + hbadness_code, int_base);
-    primitive_tex("vbadness", assign_int_cmd, int_base + vbadness_code, int_base);
-    primitive_tex("pausing", assign_int_cmd, int_base + pausing_code, int_base);
-    primitive_tex("tracingonline", assign_int_cmd, int_base + tracing_online_code, int_base);
-    primitive_tex("tracingmacros", assign_int_cmd, int_base + tracing_macros_code, int_base);
-    primitive_tex("tracingstats", assign_int_cmd, int_base + tracing_stats_code, int_base);
-    primitive_tex("tracingparagraphs", assign_int_cmd, int_base + tracing_paragraphs_code, int_base);
-    primitive_tex("tracingpages", assign_int_cmd, int_base + tracing_pages_code, int_base);
-    primitive_tex("tracingoutput", assign_int_cmd, int_base + tracing_output_code, int_base);
-    primitive_tex("tracinglostchars", assign_int_cmd, int_base + tracing_lost_chars_code, int_base);
-    primitive_tex("tracingcommands", assign_int_cmd, int_base + tracing_commands_code, int_base);
-    primitive_tex("tracingrestores", assign_int_cmd, int_base + tracing_restores_code, int_base);
-    primitive_tex("uchyph", assign_int_cmd, int_base + uc_hyph_code, int_base);
-    primitive_tex("outputpenalty", assign_int_cmd, int_base + output_penalty_code, int_base);
-    primitive_tex("maxdeadcycles", assign_int_cmd, int_base + max_dead_cycles_code, int_base);
-    primitive_tex("hangafter", assign_int_cmd, int_base + hang_after_code, int_base);
-    primitive_tex("floatingpenalty", assign_int_cmd, int_base + floating_penalty_code, int_base);
-    primitive_tex("globaldefs", assign_int_cmd, int_base + global_defs_code, int_base);
-    primitive_tex("fam", assign_int_cmd, int_base + cur_fam_code, int_base);
-    primitive_tex("escapechar", assign_int_cmd, int_base + escape_char_code, int_base);
-    primitive_tex("defaulthyphenchar", assign_int_cmd, int_base + default_hyphen_char_code, int_base);
-    primitive_tex("defaultskewchar", assign_int_cmd, int_base + default_skew_char_code, int_base);
-    primitive_tex("endlinechar", assign_int_cmd, int_base + end_line_char_code, int_base);
-    primitive_tex("newlinechar", assign_int_cmd, int_base + new_line_char_code, int_base);
-    primitive_tex("language", assign_int_cmd, int_base + language_code, int_base);
-    primitive_tex("setlanguage", assign_int_cmd, int_base + cur_lang_code, int_base);
-    primitive_tex("firstvalidlanguage", assign_int_cmd, int_base + first_valid_language_code, int_base);
-    primitive_tex("exhyphenchar", assign_int_cmd, int_base + ex_hyphen_char_code, int_base);
-    primitive_tex("lefthyphenmin", assign_int_cmd, int_base + left_hyphen_min_code, int_base);
-    primitive_tex("righthyphenmin", assign_int_cmd, int_base + right_hyphen_min_code, int_base);
-    primitive_tex("holdinginserts", assign_int_cmd, int_base + holding_inserts_code, int_base);
-    primitive_tex("errorcontextlines", assign_int_cmd, int_base + error_context_lines_code, int_base);
-    primitive_luatex("nokerns", assign_int_cmd, int_base + disable_kern_code, int_base);
-    primitive_luatex("noligs", assign_int_cmd, int_base + disable_lig_code, int_base);
-    primitive_luatex("nospaces", assign_int_cmd, int_base + disable_space_code, int_base);
-    primitive_luatex("catcodetable", assign_int_cmd, int_base + cat_code_table_code, int_base);
-    primitive_luatex("outputbox", assign_int_cmd, int_base + output_box_code, int_base);
-    primitive_luatex("outputmode", assign_int_cmd, int_base + output_mode_code, int_base);
-    primitive_luatex("adjustspacing", assign_int_cmd, int_base + adjust_spacing_code, int_base);
-    primitive_luatex("protrudechars", assign_int_cmd, int_base + protrude_chars_code, int_base);
-    primitive_luatex("tracingfonts", assign_int_cmd, int_base + tracing_fonts_code, int_base);
-    primitive_luatex("draftmode", assign_int_cmd, int_base + draft_mode_code, int_base);
-    primitive_tex("parindent", assign_dimen_cmd, dimen_base + par_indent_code, dimen_base);
-    primitive_tex("mathsurround", assign_dimen_cmd, dimen_base + math_surround_code, dimen_base);
-    primitive_tex("lineskiplimit", assign_dimen_cmd, dimen_base + line_skip_limit_code, dimen_base);
-    primitive_tex("hsize", assign_dimen_cmd, dimen_base + hsize_code, dimen_base);
-    primitive_tex("vsize", assign_dimen_cmd, dimen_base + vsize_code, dimen_base);
-    primitive_tex("maxdepth", assign_dimen_cmd, dimen_base + max_depth_code, dimen_base);
-    primitive_tex("splitmaxdepth", assign_dimen_cmd, dimen_base + split_max_depth_code, dimen_base);
-    primitive_tex("boxmaxdepth", assign_dimen_cmd, dimen_base + box_max_depth_code, dimen_base);
-    primitive_tex("hfuzz", assign_dimen_cmd, dimen_base + hfuzz_code, dimen_base);
-    primitive_tex("vfuzz", assign_dimen_cmd, dimen_base + vfuzz_code, dimen_base);
-    primitive_tex("delimitershortfall", assign_dimen_cmd, dimen_base + delimiter_shortfall_code, dimen_base);
-    primitive_tex("nulldelimiterspace", assign_dimen_cmd, dimen_base + null_delimiter_space_code, dimen_base);
-    primitive_tex("scriptspace", assign_dimen_cmd, dimen_base + script_space_code, dimen_base);
-    primitive_tex("predisplaysize", assign_dimen_cmd, dimen_base + pre_display_size_code, dimen_base);
-    primitive_tex("displaywidth", assign_dimen_cmd, dimen_base + display_width_code, dimen_base);
-    primitive_tex("displayindent", assign_dimen_cmd, dimen_base + display_indent_code, dimen_base);
-    primitive_tex("overfullrule", assign_dimen_cmd, dimen_base + overfull_rule_code, dimen_base);
-    primitive_tex("hangindent", assign_dimen_cmd, dimen_base + hang_indent_code, dimen_base);
-    primitive_tex("hoffset", assign_dimen_cmd, dimen_base + h_offset_code, dimen_base);
-    primitive_tex("voffset", assign_dimen_cmd, dimen_base + v_offset_code, dimen_base);
-    primitive_tex("emergencystretch", assign_dimen_cmd, dimen_base + emergency_stretch_code, dimen_base);
-    primitive_luatex("pagewidth", assign_dimen_cmd, dimen_base + page_width_code, dimen_base);
-    primitive_luatex("pageheight", assign_dimen_cmd, dimen_base + page_height_code, dimen_base);
-    primitive_luatex("pxdimen", assign_dimen_cmd, dimen_base + px_dimen_code, dimen_base);
-    primitive_luatex("predisplaygapfactor", assign_int_cmd, int_base + math_pre_display_gap_factor_code, int_base);
-
-    /* Many of \TeX's primitives need no |equiv|, since they are identifiable
-       by their |eq_type| alone. These primitives are loaded into the hash table
-       as follows: */
-
-    primitive_tex(" ", ex_space_cmd, 0, 0);
-    primitive_tex("/", ital_corr_cmd, 0, 0);
-    primitive_tex("accent", accent_cmd, 0, 0);
-    primitive_tex("advance", advance_cmd, 0, 0);
-    primitive_tex("afterassignment", after_assignment_cmd, 0, 0);
-    primitive_tex("aftergroup", after_group_cmd, 0, 0);
-    primitive_tex("begingroup", begin_group_cmd, 0, 0);
-    primitive_tex("char", char_num_cmd, 0, 0);
-    primitive_tex("csname", cs_name_cmd, 0, 0);
-    primitive_luatex("lastnamedcs", cs_name_cmd, 1, 0);
-    primitive_luatex("begincsname", cs_name_cmd, 2, 0);
-    primitive_tex("delimiter", delim_num_cmd, 0, 0);
-    primitive_luatex("Udelimiter", delim_num_cmd, 1, 0);
-    primitive_tex("divide", divide_cmd, 0, 0);
-    primitive_tex("endcsname", end_cs_name_cmd, 0, 0);
-    primitive_tex("endgroup", end_group_cmd, 0, 0);
-    cs_text(frozen_end_group) = maketexstring("endgroup");
-    eqtb[frozen_end_group] = eqtb[cur_val];
-    primitive_tex("expandafter", expand_after_cmd, 0, 0);
-    primitive_tex("font", def_font_cmd, 0, 0);
-    primitive_luatex("letterspacefont", letterspace_font_cmd, 0, 0);
-    primitive_luatex("expandglyphsinfont", normal_cmd, expand_font_code, 0);
-    primitive_luatex("copyfont", copy_font_cmd, 0, 0);
-    primitive_luatex("setfontid", set_font_id_cmd, 0, 0);
-    primitive_tex("fontdimen", assign_font_dimen_cmd, 0, 0);
-    primitive_tex("halign", halign_cmd, 0, 0);
-    primitive_tex("hrule", hrule_cmd, 0, 0);
-    primitive_luatex("nohrule", no_hrule_cmd, 0, 0);
-    primitive_tex("ignorespaces", ignore_spaces_cmd, 0, 0);
-    primitive_tex("insert", insert_cmd, 0, 0);
-    primitive_luatex("leftghost", char_ghost_cmd, 0, 0);
-    primitive_tex("mark", mark_cmd, 0, 0);
-    primitive_tex("mathaccent", math_accent_cmd, 0, 0);
-    primitive_luatex("Umathaccent", math_accent_cmd, 1, 0);
-    primitive_tex("mathchar", math_char_num_cmd, 0, 0);
-    primitive_luatex("Umathchar", math_char_num_cmd, 1, 0);
-    primitive_luatex("Umathcharnum", math_char_num_cmd, 2, 0);
-    primitive_tex("mathchoice", math_choice_cmd, 0, 0);
-    primitive_luatex("Ustack", math_choice_cmd, 1, 0);
-    primitive_tex("multiply", multiply_cmd, 0, 0);
-    primitive_tex("noalign", no_align_cmd, 0, 0);
-    primitive_tex("noboundary", boundary_cmd, 0, 0);
-    primitive_tex("boundary", boundary_cmd, 1, 0);
-    primitive_tex("protrusionboundary", boundary_cmd, 2, 0);
-    primitive_tex("wordboundary", boundary_cmd, 3, 0);
-    primitive_tex("noexpand", no_expand_cmd, 0, 0);
-    primitive_luatex("primitive", no_expand_cmd, 1, 0);
-    primitive_tex("nonscript", non_script_cmd, 0, 0);
-    primitive_tex("omit", omit_cmd, 0, 0);
-    primitive_tex("parshape", set_tex_shape_cmd, par_shape_loc, par_shape_loc);
-    primitive_tex("penalty", break_penalty_cmd, 0, 0);
-    primitive_tex("prevgraf", set_prev_graf_cmd, 0, 0);
-    primitive_tex("radical", radical_cmd, 0, 0);
-    primitive_luatex("Uradical", radical_cmd, 1, 0);
-    primitive_luatex("Uroot", radical_cmd, 2, 0);
-    primitive_luatex("Uunderdelimiter", radical_cmd, 3, 0);
-    primitive_luatex("Uoverdelimiter", radical_cmd, 4, 0);
-    primitive_luatex("Udelimiterunder", radical_cmd, 5, 0);
-    primitive_luatex("Udelimiterover", radical_cmd, 6, 0);
-    primitive_luatex("Uhextensible", radical_cmd, 7, 0);
-    primitive_tex("read", read_to_cs_cmd, 0, 0);
-    primitive_tex("relax", relax_cmd, too_big_char, too_big_char);
-    cs_text(frozen_relax) = maketexstring("relax");
-    eqtb[frozen_relax] = eqtb[cur_val];
-    primitive_luatex("rightghost", char_ghost_cmd, 1, 0);
-    primitive_tex("setbox", set_box_cmd, 0, 0);
-    primitive_tex("the", the_cmd, 0, 0);
-    primitive_luatex("toksapp", combine_toks_cmd, 0, 0);
-    primitive_luatex("tokspre", combine_toks_cmd, 1, 0);
-    primitive_luatex("etoksapp", combine_toks_cmd, 2, 0);
-    primitive_luatex("etokspre", combine_toks_cmd, 3, 0);
-    primitive_tex("toks", toks_register_cmd, 0, 0);
-    primitive_tex("vadjust", vadjust_cmd, 0, 0);
-    primitive_tex("valign", valign_cmd, 0, 0);
-    primitive_tex("vcenter", vcenter_cmd, 0, 0);
-    primitive_tex("vrule", vrule_cmd, 0, 0);
-    primitive_luatex("novrule", no_vrule_cmd, 0, 0);
-    primitive_tex("par", par_end_cmd, too_big_char, too_big_char);      /* cf.\ |scan_file_name| */
-    par_loc = cur_val;
-    par_token = cs_token_flag + par_loc;
-    @<Create a bunch of primitives@>;
-    @<Create the math param primitives@>;
-    @<Create another bunch of primitives@>;
-}
-
-
-@ These are in a separate module due to a CWEAVE limitation.
-
-@<Create a bunch of primitives@>=
-
-    /*
-        The processing of \.{\\input} involves the |start_input| subroutine,
-        which will be declared later; the processing of \.{\\endinput} is trivial.
-    */
-
-    primitive_tex("input", input_cmd, 0, 0);
-    primitive_tex("endinput", input_cmd, 1, 0);
-    primitive_tex("topmark", top_bot_mark_cmd, top_mark_code, 0);
-    primitive_tex("firstmark", top_bot_mark_cmd, first_mark_code, 0);
-    primitive_tex("botmark", top_bot_mark_cmd, bot_mark_code, 0);
-    primitive_tex("splitfirstmark", top_bot_mark_cmd, split_first_mark_code, 0);
-    primitive_tex("splitbotmark", top_bot_mark_cmd, split_bot_mark_code, 0);
-    primitive_luatex("clearmarks", mark_cmd, clear_marks_code, 0);
-    primitive_etex("marks", mark_cmd, marks_code, 0);
-    primitive_etex("topmarks", top_bot_mark_cmd, top_mark_code + marks_code, 0);
-    primitive_etex("firstmarks", top_bot_mark_cmd, first_mark_code + marks_code, 0);
-    primitive_etex("botmarks", top_bot_mark_cmd, bot_mark_code + marks_code, 0);
-    primitive_etex("splitfirstmarks", top_bot_mark_cmd, split_first_mark_code + marks_code, 0);
-    primitive_etex("splitbotmarks", top_bot_mark_cmd, split_bot_mark_code + marks_code, 0);
-
-    /*
-        The hash table is initialized with `\.{\\count}', `\.{\\attribute}',
-        `\.{\\dimen}', `\.{\\skip}', and `\.{\\muskip}' all having |register|
-        as their command code; they are distinguished by the |chr_code|, which
-        is either |int_val|, |attr_val|, |dimen_val|, |glue_val|, or |mu_val|.
-    */
-
-    primitive_tex("count", register_cmd, int_val_level, 0);
-    primitive_luatex("attribute", register_cmd, attr_val_level, 0);
-    primitive_tex("dimen", register_cmd, dimen_val_level, 0);
-    primitive_tex("skip", register_cmd, glue_val_level, 0);
-    primitive_tex("muskip", register_cmd, mu_val_level, 0);
-
-    primitive_tex("spacefactor", set_aux_cmd, hmode, 0);
-    primitive_tex("prevdepth", set_aux_cmd, vmode, 0);
-    primitive_tex("deadcycles", set_page_int_cmd, 0, 0);
-    primitive_tex("insertpenalties", set_page_int_cmd, 1, 0);
-    primitive_tex("wd", set_box_dimen_cmd, width_offset, 0);
-    primitive_tex("ht", set_box_dimen_cmd, height_offset, 0);
-    primitive_tex("dp", set_box_dimen_cmd, depth_offset, 0);
-    primitive_tex("lastpenalty", last_item_cmd, lastpenalty_code, 0);
-    primitive_tex("lastkern", last_item_cmd, lastkern_code, 0);
-    primitive_tex("lastskip", last_item_cmd, lastskip_code, 0);
-    primitive_tex("inputlineno", last_item_cmd, input_line_no_code, 0);
-    primitive_tex("badness", last_item_cmd, badness_code, 0);
-    primitive_luatex("luatexversion", last_item_cmd, luatex_version_code, 0);
-    primitive_luatex("lastsavedboxresourceindex", last_item_cmd, last_saved_box_resource_index_code, 0);
-    primitive_luatex("lastsavedimageresourceindex", last_item_cmd, last_saved_image_resource_index_code, 0);
-    primitive_luatex("lastsavedimageresourcepages", last_item_cmd, last_saved_image_resource_pages_code, 0);
-    primitive_luatex("lastxpos", last_item_cmd, last_x_pos_code, 0);
-    primitive_luatex("lastypos", last_item_cmd, last_y_pos_code, 0);
-    primitive_luatex("randomseed", last_item_cmd, random_seed_code, 0);
-
-    primitive_tex("number", convert_cmd, number_code, 0);
-    primitive_tex("romannumeral", convert_cmd, roman_numeral_code, 0);
-    primitive_tex("string", convert_cmd, string_code, 0);
-    primitive_tex("csstring", convert_cmd, cs_string_code, 0);
-    primitive_tex("meaning", convert_cmd, meaning_code, 0);
-    primitive_etex("eTeXVersion", convert_cmd, etex_code, 0);
-    primitive_tex("fontname", convert_cmd, font_name_code, 0);
-    primitive_luatex("fontid", convert_cmd, font_id_code, 0);
-    primitive_luatex("luatexrevision", convert_cmd, luatex_revision_code, 0);
-    primitive_luatex("luatexdatestamp", convert_cmd, luatex_date_code, 0);
-    primitive_luatex("luatexbanner", convert_cmd, luatex_banner_code, 0);
-    primitive_luatex("leftmarginkern", convert_cmd, left_margin_kern_code, 0);
-    primitive_luatex("rightmarginkern", convert_cmd, right_margin_kern_code, 0);
-    primitive_luatex("uniformdeviate", convert_cmd, uniform_deviate_code, 0);
-    primitive_luatex("normaldeviate", convert_cmd, normal_deviate_code, 0);
-    primitive_core("directlua", convert_cmd, lua_code, 0);
-    primitive_luatex("luafunction", convert_cmd, lua_function_code, 0);
-    primitive_luatex("luaescapestring", convert_cmd, lua_escape_string_code, 0);
-    primitive_luatex("mathstyle", convert_cmd, math_style_code, 0);
-    primitive_luatex("expanded", convert_cmd, expanded_code, 0);
-    primitive_tex("jobname", convert_cmd, job_name_code, 0);
-    primitive_luatex("formatname", convert_cmd, format_name_code, 0);
-    primitive_luatex("Uchar", convert_cmd, uchar_code, 0);
-
-    primitive_luatex("Umathcharclass", convert_cmd, math_char_class_code, 0);
-    primitive_luatex("Umathcharfam", convert_cmd, math_char_fam_code, 0);
-    primitive_luatex("Umathcharslot", convert_cmd, math_char_slot_code, 0);
-
-    primitive_tex("if", if_test_cmd, if_char_code, 0);
-    primitive_tex("ifcat", if_test_cmd, if_cat_code, 0);
-    primitive_tex("ifnum", if_test_cmd, if_int_code, 0);
-    primitive_tex("ifdim", if_test_cmd, if_dim_code, 0);
-    primitive_tex("ifodd", if_test_cmd, if_odd_code, 0);
-    primitive_tex("ifvmode", if_test_cmd, if_vmode_code, 0);
-    primitive_tex("ifhmode", if_test_cmd, if_hmode_code, 0);
-    primitive_tex("ifmmode", if_test_cmd, if_mmode_code, 0);
-    primitive_tex("ifinner", if_test_cmd, if_inner_code, 0);
-    primitive_tex("ifvoid", if_test_cmd, if_void_code, 0);
-
-    primitive_tex("ifhbox", if_test_cmd, if_hbox_code, 0);
-    primitive_tex("ifvbox", if_test_cmd, if_vbox_code, 0);
-    primitive_tex("ifx", if_test_cmd, ifx_code, 0);
-    primitive_tex("ifeof", if_test_cmd, if_eof_code, 0);
-    primitive_tex("iftrue", if_test_cmd, if_true_code, 0);
-    primitive_tex("iffalse", if_test_cmd, if_false_code, 0);
-    primitive_tex("ifcase", if_test_cmd, if_case_code, 0);
-    primitive_luatex("ifprimitive", if_test_cmd, if_primitive_code, 0);
-    primitive_tex("fi", fi_or_else_cmd, fi_code, 0);
-    cs_text(frozen_fi) = maketexstring("fi");
-    eqtb[frozen_fi] = eqtb[cur_val];
-    primitive_tex("or", fi_or_else_cmd, or_code, 0);
-    primitive_tex("else", fi_or_else_cmd, else_code, 0);
-
-    /*
-        \TeX\ always knows at least one font, namely the null font. It has no
-        characters, and its seven parameters are all equal to zero.
-    */
-
-    primitive_tex("nullfont", set_font_cmd, null_font, 0);
-    cs_text(frozen_null_font) = maketexstring("nullfont");
-    eqtb[frozen_null_font] = eqtb[cur_val];
-
-    primitive_tex("span", tab_mark_cmd, span_code, tab_mark_cmd_code);
-    primitive_luatex("aligntab", tab_mark_cmd, tab_mark_cmd_code, tab_mark_cmd_code);
-    primitive_luatex("alignmark", mac_param_cmd, tab_mark_cmd_code, tab_mark_cmd_code);
-    primitive_tex("cr", car_ret_cmd, cr_code, cr_code);
-    cs_text(frozen_cr) = maketexstring("cr");
-    eqtb[frozen_cr] = eqtb[cur_val];
-    primitive_tex("crcr", car_ret_cmd, cr_cr_code, cr_code);
-    cs_text(frozen_end_template) = maketexstring("endtemplate");
-    cs_text(frozen_endv) = maketexstring("endtemplate");
-    set_eq_type(frozen_endv, endv_cmd);
-    set_equiv(frozen_endv, null_list);
-    set_eq_level(frozen_endv, level_one);
-    eqtb[frozen_end_template] = eqtb[frozen_endv];
-    set_eq_type(frozen_end_template, end_template_cmd);
-
-    primitive_tex("pagegoal", set_page_dimen_cmd, 0, 0);
-    primitive_tex("pagetotal", set_page_dimen_cmd, 1, 0);
-    primitive_tex("pagestretch", set_page_dimen_cmd, 2, 0);
-    primitive_tex("pagefilstretch", set_page_dimen_cmd, 3, 0);
-    primitive_tex("pagefillstretch", set_page_dimen_cmd, 4, 0);
-    primitive_tex("pagefilllstretch", set_page_dimen_cmd, 5, 0);
-    primitive_tex("pageshrink", set_page_dimen_cmd, 6, 0);
-    primitive_tex("pagedepth", set_page_dimen_cmd, 7, 0);
-
-    /*
-        Either \.{\\dump} or \.{\\end} will cause |main_control| to enter the
-        endgame, since both of them have `|stop|' as their command code.
-    */
-
-    primitive_tex("end", stop_cmd, 0, 0);
-    primitive_tex("dump", stop_cmd, 1, 0);
-
-    primitive_tex("hskip", hskip_cmd, skip_code, 0);
-    primitive_tex("hfil", hskip_cmd, fil_code, 0);
-    primitive_tex("hfill", hskip_cmd, fill_code, 0);
-    primitive_tex("hss", hskip_cmd, ss_code, 0);
-    primitive_tex("hfilneg", hskip_cmd, fil_neg_code, 0);
-    primitive_tex("vskip", vskip_cmd, skip_code, 0);
-    primitive_tex("vfil", vskip_cmd, fil_code, 0);
-    primitive_tex("vfill", vskip_cmd, fill_code, 0);
-    primitive_tex("vss", vskip_cmd, ss_code, 0);
-    primitive_tex("vfilneg", vskip_cmd, fil_neg_code, 0);
-    primitive_tex("mskip", mskip_cmd, mskip_code, 0);
-    primitive_tex("kern", kern_cmd, explicit_kern, 0);
-    primitive_tex("mkern", mkern_cmd, mu_glue, 0);
-    primitive_tex("moveleft", hmove_cmd, 1, 0);
-    primitive_tex("moveright", hmove_cmd, 0, 0);
-    primitive_tex("raise", vmove_cmd, 1, 0);
-    primitive_tex("lower", vmove_cmd, 0, 0);
-    primitive_tex("box", make_box_cmd, box_code, 0);
-    primitive_tex("copy", make_box_cmd, copy_code, 0);
-    primitive_tex("lastbox", make_box_cmd, last_box_code, 0);
-    primitive_tex("vsplit", make_box_cmd, vsplit_code, 0);
-    primitive_tex("tpack", make_box_cmd, tpack_code, 0);
-    primitive_tex("vpack", make_box_cmd, vpack_code, 0);
-    primitive_tex("hpack", make_box_cmd, hpack_code, 0);
-    primitive_tex("vtop", make_box_cmd, vtop_code, 0);
-    primitive_tex("vbox", make_box_cmd, vtop_code + vmode, 0);
-    primitive_tex("hbox", make_box_cmd, vtop_code + hmode, 0);
-    primitive_tex("shipout", leader_ship_cmd, a_leaders - 1, 0);        /* |ship_out_flag=leader_flag-1| */
-    primitive_tex("leaders", leader_ship_cmd, a_leaders, 0);
-    primitive_tex("cleaders", leader_ship_cmd, c_leaders, 0);
-    primitive_tex("xleaders", leader_ship_cmd, x_leaders, 0);
-    primitive_luatex("gleaders", leader_ship_cmd, g_leaders, 0);
-    primitive_luatex("boxdir", assign_box_dir_cmd, 0, 0);
-    primitive_tex("indent", start_par_cmd, 1, 0);
-    primitive_tex("noindent", start_par_cmd, 0, 0);
-    primitive_luatex("quitvmode", start_par_cmd, 2, 0);
-    primitive_tex("unpenalty", remove_item_cmd, penalty_node, 0);
-    primitive_tex("unkern", remove_item_cmd, kern_node, 0);
-    primitive_tex("unskip", remove_item_cmd, glue_node, 0);
-    primitive_tex("unhbox", un_hbox_cmd, box_code, 0);
-    primitive_tex("unhcopy", un_hbox_cmd, copy_code, 0);
-    primitive_tex("unvbox", un_vbox_cmd, box_code, 0);
-    primitive_tex("unvcopy", un_vbox_cmd, copy_code, 0);
-    primitive_tex("-", discretionary_cmd, explicit_disc, 0);
-    primitive_tex("discretionary", discretionary_cmd, discretionary_disc, 0);
-    primitive_luatex("localleftbox", assign_local_box_cmd, 0, 0);
-    primitive_luatex("localrightbox", assign_local_box_cmd, 1, 0);
-
-    primitive_luatex("Ustartmath", math_shift_cs_cmd, text_style, 0);
-    primitive_luatex("Ustopmath", math_shift_cs_cmd, cramped_text_style, 0);
-    primitive_luatex("Ustartdisplaymath", math_shift_cs_cmd, display_style, 0);
-    primitive_luatex("Ustopdisplaymath", math_shift_cs_cmd, cramped_display_style, 0);
-    primitive_tex("eqno", eq_no_cmd, 0, 0);
-    primitive_tex("leqno", eq_no_cmd, 1, 0);
-    primitive_tex("mathord", math_comp_cmd, ord_noad_type, 0);
-    primitive_tex("mathop", math_comp_cmd, op_noad_type_normal, 0);
-    primitive_tex("mathbin", math_comp_cmd, bin_noad_type, 0);
-    primitive_tex("mathrel", math_comp_cmd, rel_noad_type, 0);
-    primitive_tex("mathopen", math_comp_cmd, open_noad_type, 0);
-    primitive_tex("mathclose", math_comp_cmd, close_noad_type, 0);
-    primitive_tex("mathpunct", math_comp_cmd, punct_noad_type, 0);
-    primitive_tex("mathinner", math_comp_cmd, inner_noad_type, 0);
-    primitive_tex("underline", math_comp_cmd, under_noad_type, 0);
-    primitive_tex("overline", math_comp_cmd, over_noad_type, 0);
-    primitive_tex("displaylimits", limit_switch_cmd, op_noad_type_normal, 0);
-    primitive_tex("limits", limit_switch_cmd, op_noad_type_limits, 0);
-    primitive_tex("nolimits", limit_switch_cmd, op_noad_type_no_limits, 0);
-    primitive_tex("displaystyle", math_style_cmd, display_style, 0);
-    primitive_tex("textstyle", math_style_cmd, text_style, 0);
-    primitive_tex("scriptstyle", math_style_cmd, script_style, 0);
-    primitive_tex("scriptscriptstyle", math_style_cmd, script_script_style, 0);
-    primitive_luatex("crampeddisplaystyle", math_style_cmd, cramped_display_style, 0);
-    primitive_luatex("crampedtextstyle", math_style_cmd, cramped_text_style, 0);
-    primitive_luatex("crampedscriptstyle", math_style_cmd, cramped_script_style, 0);
-    primitive_luatex("crampedscriptscriptstyle", math_style_cmd, cramped_script_script_style, 0);
-    primitive_luatex("Usuperscript", super_sub_script_cmd, sup_mark_cmd, sup_mark_cmd);
-    primitive_luatex("Usubscript", super_sub_script_cmd, sub_mark_cmd, sup_mark_cmd);
-    primitive_tex("above", above_cmd, above_code, 0);
-    primitive_tex("over", above_cmd, over_code, 0);
-    primitive_tex("atop", above_cmd, atop_code, 0);
-    primitive_luatex("Uskewed", above_cmd, skewed_code, 0);
-    primitive_tex("abovewithdelims", above_cmd, delimited_code + above_code, 0);
-    primitive_tex("overwithdelims", above_cmd, delimited_code + over_code, 0);
-    primitive_tex("atopwithdelims", above_cmd, delimited_code + atop_code, 0);
-    primitive_luatex("Uskewedwithdelims", above_cmd, delimited_code + skewed_code, 0);
-    primitive_tex("left", left_right_cmd, left_noad_side, 0);
-    primitive_tex("right", left_right_cmd, right_noad_side, 0);
-    primitive_tex("middle", left_right_cmd, middle_noad_side, 0);
-    primitive_tex("Uleft", left_right_cmd, 10+left_noad_side, 0);
-    primitive_tex("Uright", left_right_cmd, 10+right_noad_side, 0);
-    primitive_tex("Umiddle", left_right_cmd, 10+middle_noad_side, 0);
-    primitive_luatex("Uvextensible", left_right_cmd, 10+no_noad_side, 0);
-    cs_text(frozen_right) = maketexstring("right");
-    eqtb[frozen_right] = eqtb[cur_val];
-
-    primitive_tex("long", prefix_cmd, 1, 0);
-    primitive_tex("outer", prefix_cmd, 2, 0);
-    primitive_tex("global", prefix_cmd, 4, 0);
-    primitive_tex("def", def_cmd, 0, 0);
-    primitive_tex("gdef", def_cmd, 1, 0);
-    primitive_tex("edef", def_cmd, 2, 0);
-    primitive_tex("xdef", def_cmd, 3, 0);
-    primitive_tex("let", let_cmd, normal, 0);
-    primitive_tex("futurelet", let_cmd, normal + 1, 0);
-    primitive_luatex("letcharcode", let_cmd, normal + 2, 0);
-    primitive_tex("chardef", shorthand_def_cmd, char_def_code, 0);
-    primitive_tex("mathchardef", shorthand_def_cmd, math_char_def_code, 0);
-    primitive_luatex("Umathchardef", shorthand_def_cmd, xmath_char_def_code, 0);
-    primitive_luatex("Umathcharnumdef", shorthand_def_cmd, umath_char_def_code, 0);
-    primitive_tex("countdef", shorthand_def_cmd, count_def_code, 0);
-    primitive_luatex("attributedef", shorthand_def_cmd, attribute_def_code, 0);
-    primitive_tex("dimendef", shorthand_def_cmd, dimen_def_code, 0);
-    primitive_tex("skipdef", shorthand_def_cmd, skip_def_code, 0);
-    primitive_tex("muskipdef", shorthand_def_cmd, mu_skip_def_code, 0);
-    primitive_tex("toksdef", shorthand_def_cmd, toks_def_code, 0);
-    primitive_tex("catcode", def_char_code_cmd, cat_code_base, cat_code_base);
-    primitive_tex("mathcode", def_char_code_cmd, math_code_base, cat_code_base);
-    primitive_tex("lccode", def_char_code_cmd, lc_code_base, cat_code_base);
-    primitive_tex("uccode", def_char_code_cmd, uc_code_base, cat_code_base);
-    primitive_tex("sfcode", def_char_code_cmd, sf_code_base, cat_code_base);
-    primitive_tex("delcode", def_del_code_cmd, del_code_base, del_code_base);
-    primitive_tex("textfont", def_family_cmd, text_size, 0);
-    primitive_tex("scriptfont", def_family_cmd, script_size, 0);
-    primitive_tex("scriptscriptfont", def_family_cmd, script_script_size, 0);
-    primitive_luatex("Umathquad", set_math_param_cmd, math_param_quad, 0);
-    primitive_luatex("Umathaxis", set_math_param_cmd, math_param_axis, 0);
-
-@ These are in a separate module due to a CWEAVE limitation.
-
-@<Create the math param primitives@>=
-    primitive_luatex("Umathoperatorsize", set_math_param_cmd, math_param_operator_size, 0);
-    primitive_luatex("Umathoverbarkern", set_math_param_cmd, math_param_overbar_kern, 0);
-    primitive_luatex("Umathoverbarrule", set_math_param_cmd, math_param_overbar_rule, 0);
-    primitive_luatex("Umathoverbarvgap", set_math_param_cmd, math_param_overbar_vgap, 0);
-    primitive_luatex("Umathunderbarkern", set_math_param_cmd, math_param_underbar_kern, 0);
-    primitive_luatex("Umathunderbarrule", set_math_param_cmd, math_param_underbar_rule, 0);
-    primitive_luatex("Umathunderbarvgap", set_math_param_cmd, math_param_underbar_vgap, 0);
-    primitive_luatex("Umathradicalkern", set_math_param_cmd, math_param_radical_kern, 0);
-    primitive_luatex("Umathradicalrule", set_math_param_cmd, math_param_radical_rule, 0);
-    primitive_luatex("Umathradicalvgap", set_math_param_cmd, math_param_radical_vgap, 0);
-    primitive_luatex("Umathradicaldegreebefore", set_math_param_cmd, math_param_radical_degree_before, 0);
-    primitive_luatex("Umathradicaldegreeafter", set_math_param_cmd, math_param_radical_degree_after, 0);
-    primitive_luatex("Umathradicaldegreeraise", set_math_param_cmd, math_param_radical_degree_raise, 0);
-    primitive_luatex("Umathstackvgap", set_math_param_cmd, math_param_stack_vgap, 0);
-    primitive_luatex("Umathstacknumup", set_math_param_cmd, math_param_stack_num_up, 0);
-    primitive_luatex("Umathstackdenomdown", set_math_param_cmd, math_param_stack_denom_down, 0);
-    primitive_luatex("Umathfractionrule", set_math_param_cmd, math_param_fraction_rule, 0);
-    primitive_luatex("Umathfractionnumvgap", set_math_param_cmd, math_param_fraction_num_vgap, 0);
-    primitive_luatex("Umathfractionnumup", set_math_param_cmd, math_param_fraction_num_up, 0);
-    primitive_luatex("Umathfractiondenomvgap", set_math_param_cmd, math_param_fraction_denom_vgap, 0);
-    primitive_luatex("Umathfractiondenomdown", set_math_param_cmd, math_param_fraction_denom_down, 0);
-    primitive_luatex("Umathfractiondelsize", set_math_param_cmd, math_param_fraction_del_size, 0);
-    primitive_luatex("Umathskewedfractionvgap", set_math_param_cmd, math_param_skewed_fraction_vgap, 0);
-    primitive_luatex("Umathskewedfractionhgap", set_math_param_cmd, math_param_skewed_fraction_hgap, 0);
-    primitive_luatex("Umathlimitabovevgap", set_math_param_cmd, math_param_limit_above_vgap, 0);
-    primitive_luatex("Umathlimitabovebgap", set_math_param_cmd, math_param_limit_above_bgap, 0);
-    primitive_luatex("Umathlimitabovekern", set_math_param_cmd, math_param_limit_above_kern, 0);
-    primitive_luatex("Umathlimitbelowvgap", set_math_param_cmd, math_param_limit_below_vgap, 0);
-    primitive_luatex("Umathlimitbelowbgap", set_math_param_cmd, math_param_limit_below_bgap, 0);
-    primitive_luatex("Umathlimitbelowkern", set_math_param_cmd, math_param_limit_below_kern, 0);
-    primitive_luatex("Umathnolimitsubfactor", set_math_param_cmd, math_param_nolimit_sub_factor, 0); /* bonus */
-    primitive_luatex("Umathnolimitsupfactor", set_math_param_cmd, math_param_nolimit_sup_factor, 0); /* bonus */
-    primitive_luatex("Umathunderdelimitervgap", set_math_param_cmd, math_param_under_delimiter_vgap, 0);
-    primitive_luatex("Umathunderdelimiterbgap", set_math_param_cmd, math_param_under_delimiter_bgap, 0);
-    primitive_luatex("Umathoverdelimitervgap", set_math_param_cmd, math_param_over_delimiter_vgap, 0);
-    primitive_luatex("Umathoverdelimiterbgap", set_math_param_cmd, math_param_over_delimiter_bgap, 0);
-    primitive_luatex("Umathsubshiftdrop", set_math_param_cmd, math_param_sub_shift_drop, 0);
-    primitive_luatex("Umathsupshiftdrop", set_math_param_cmd, math_param_sup_shift_drop, 0);
-    primitive_luatex("Umathsubshiftdown", set_math_param_cmd, math_param_sub_shift_down, 0);
-    primitive_luatex("Umathsubsupshiftdown", set_math_param_cmd, math_param_sub_sup_shift_down, 0);
-    primitive_luatex("Umathsubtopmax", set_math_param_cmd, math_param_sub_top_max, 0);
-    primitive_luatex("Umathsupshiftup", set_math_param_cmd, math_param_sup_shift_up, 0);
-    primitive_luatex("Umathsupbottommin", set_math_param_cmd, math_param_sup_bottom_min, 0);
-    primitive_luatex("Umathsupsubbottommax", set_math_param_cmd, math_param_sup_sub_bottom_max, 0);
-    primitive_luatex("Umathsubsupvgap", set_math_param_cmd, math_param_subsup_vgap, 0);
-    primitive_luatex("Umathspaceafterscript", set_math_param_cmd, math_param_space_after_script, 0);
-    primitive_luatex("Umathconnectoroverlapmin", set_math_param_cmd, math_param_connector_overlap_min, 0);
-    primitive_luatex("Umathordordspacing", set_math_param_cmd, math_param_ord_ord_spacing, 0);
-    primitive_luatex("Umathordopspacing", set_math_param_cmd, math_param_ord_op_spacing, 0);
-    primitive_luatex("Umathordbinspacing", set_math_param_cmd, math_param_ord_bin_spacing, 0);
-    primitive_luatex("Umathordrelspacing", set_math_param_cmd, math_param_ord_rel_spacing, 0);
-    primitive_luatex("Umathordopenspacing", set_math_param_cmd, math_param_ord_open_spacing, 0);
-    primitive_luatex("Umathordclosespacing", set_math_param_cmd, math_param_ord_close_spacing, 0);
-    primitive_luatex("Umathordpunctspacing", set_math_param_cmd, math_param_ord_punct_spacing, 0);
-    primitive_luatex("Umathordinnerspacing", set_math_param_cmd, math_param_ord_inner_spacing, 0);
-    primitive_luatex("Umathopordspacing", set_math_param_cmd, math_param_op_ord_spacing, 0);
-    primitive_luatex("Umathopopspacing", set_math_param_cmd, math_param_op_op_spacing, 0);
-    primitive_luatex("Umathopbinspacing", set_math_param_cmd, math_param_op_bin_spacing, 0);
-    primitive_luatex("Umathoprelspacing", set_math_param_cmd, math_param_op_rel_spacing, 0);
-    primitive_luatex("Umathopopenspacing", set_math_param_cmd, math_param_op_open_spacing, 0);
-    primitive_luatex("Umathopclosespacing", set_math_param_cmd, math_param_op_close_spacing, 0);
-    primitive_luatex("Umathoppunctspacing", set_math_param_cmd, math_param_op_punct_spacing, 0);
-    primitive_luatex("Umathopinnerspacing", set_math_param_cmd, math_param_op_inner_spacing, 0);
-    primitive_luatex("Umathbinordspacing", set_math_param_cmd, math_param_bin_ord_spacing, 0);
-    primitive_luatex("Umathbinopspacing", set_math_param_cmd, math_param_bin_op_spacing, 0);
-    primitive_luatex("Umathbinbinspacing", set_math_param_cmd, math_param_bin_bin_spacing, 0);
-    primitive_luatex("Umathbinrelspacing", set_math_param_cmd, math_param_bin_rel_spacing, 0);
-    primitive_luatex("Umathbinopenspacing", set_math_param_cmd, math_param_bin_open_spacing, 0);
-    primitive_luatex("Umathbinclosespacing", set_math_param_cmd, math_param_bin_close_spacing, 0);
-    primitive_luatex("Umathbinpunctspacing", set_math_param_cmd, math_param_bin_punct_spacing, 0);
-    primitive_luatex("Umathbininnerspacing", set_math_param_cmd, math_param_bin_inner_spacing, 0);
-    primitive_luatex("Umathrelordspacing", set_math_param_cmd, math_param_rel_ord_spacing, 0);
-    primitive_luatex("Umathrelopspacing", set_math_param_cmd, math_param_rel_op_spacing, 0);
-    primitive_luatex("Umathrelbinspacing", set_math_param_cmd, math_param_rel_bin_spacing, 0);
-    primitive_luatex("Umathrelrelspacing", set_math_param_cmd, math_param_rel_rel_spacing, 0);
-    primitive_luatex("Umathrelopenspacing", set_math_param_cmd, math_param_rel_open_spacing, 0);
-    primitive_luatex("Umathrelclosespacing", set_math_param_cmd, math_param_rel_close_spacing, 0);
-    primitive_luatex("Umathrelpunctspacing", set_math_param_cmd, math_param_rel_punct_spacing, 0);
-    primitive_luatex("Umathrelinnerspacing", set_math_param_cmd, math_param_rel_inner_spacing, 0);
-    primitive_luatex("Umathopenordspacing", set_math_param_cmd, math_param_open_ord_spacing, 0);
-    primitive_luatex("Umathopenopspacing", set_math_param_cmd, math_param_open_op_spacing, 0);
-    primitive_luatex("Umathopenbinspacing", set_math_param_cmd, math_param_open_bin_spacing, 0);
-    primitive_luatex("Umathopenrelspacing", set_math_param_cmd, math_param_open_rel_spacing, 0);
-    primitive_luatex("Umathopenopenspacing", set_math_param_cmd, math_param_open_open_spacing, 0);
-    primitive_luatex("Umathopenclosespacing", set_math_param_cmd, math_param_open_close_spacing, 0);
-    primitive_luatex("Umathopenpunctspacing", set_math_param_cmd, math_param_open_punct_spacing, 0);
-    primitive_luatex("Umathopeninnerspacing", set_math_param_cmd, math_param_open_inner_spacing, 0);
-    primitive_luatex("Umathcloseordspacing", set_math_param_cmd, math_param_close_ord_spacing, 0);
-    primitive_luatex("Umathcloseopspacing", set_math_param_cmd, math_param_close_op_spacing, 0);
-    primitive_luatex("Umathclosebinspacing", set_math_param_cmd, math_param_close_bin_spacing, 0);
-    primitive_luatex("Umathcloserelspacing", set_math_param_cmd, math_param_close_rel_spacing, 0);
-    primitive_luatex("Umathcloseopenspacing", set_math_param_cmd, math_param_close_open_spacing, 0);
-    primitive_luatex("Umathcloseclosespacing", set_math_param_cmd, math_param_close_close_spacing, 0);
-    primitive_luatex("Umathclosepunctspacing", set_math_param_cmd, math_param_close_punct_spacing, 0);
-    primitive_luatex("Umathcloseinnerspacing", set_math_param_cmd, math_param_close_inner_spacing, 0);
-    primitive_luatex("Umathpunctordspacing", set_math_param_cmd, math_param_punct_ord_spacing, 0);
-    primitive_luatex("Umathpunctopspacing", set_math_param_cmd, math_param_punct_op_spacing, 0);
-    primitive_luatex("Umathpunctbinspacing", set_math_param_cmd, math_param_punct_bin_spacing, 0);
-    primitive_luatex("Umathpunctrelspacing", set_math_param_cmd, math_param_punct_rel_spacing, 0);
-    primitive_luatex("Umathpunctopenspacing", set_math_param_cmd, math_param_punct_open_spacing, 0);
-    primitive_luatex("Umathpunctclosespacing", set_math_param_cmd, math_param_punct_close_spacing, 0);
-    primitive_luatex("Umathpunctpunctspacing", set_math_param_cmd, math_param_punct_punct_spacing, 0);
-    primitive_luatex("Umathpunctinnerspacing", set_math_param_cmd, math_param_punct_inner_spacing, 0);
-    primitive_luatex("Umathinnerordspacing", set_math_param_cmd, math_param_inner_ord_spacing, 0);
-    primitive_luatex("Umathinneropspacing", set_math_param_cmd, math_param_inner_op_spacing, 0);
-    primitive_luatex("Umathinnerbinspacing", set_math_param_cmd, math_param_inner_bin_spacing, 0);
-    primitive_luatex("Umathinnerrelspacing", set_math_param_cmd, math_param_inner_rel_spacing, 0);
-    primitive_luatex("Umathinneropenspacing", set_math_param_cmd, math_param_inner_open_spacing, 0);
-    primitive_luatex("Umathinnerclosespacing", set_math_param_cmd, math_param_inner_close_spacing, 0);
-    primitive_luatex("Umathinnerpunctspacing", set_math_param_cmd, math_param_inner_punct_spacing, 0);
-    primitive_luatex("Umathinnerinnerspacing", set_math_param_cmd, math_param_inner_inner_spacing, 0);
-
-@ These are in a separate module due to a CWEAVE limitation.
-
-@<Create another bunch of primitives@>=
-    primitive_luatex("Umathcode", extdef_math_code_cmd, math_code_base, math_code_base);
-    primitive_luatex("Udelcode", extdef_del_code_cmd, del_code_base, del_code_base);
-    primitive_luatex("Umathcodenum", extdef_math_code_cmd, math_code_base + 1, math_code_base);
-    primitive_luatex("Udelcodenum", extdef_del_code_cmd, del_code_base + 1, del_code_base);
-    primitive_tex("hyphenation", hyph_data_cmd, 0, 0);
-    primitive_tex("patterns", hyph_data_cmd, 1, 0);
-    primitive_luatex("prehyphenchar", hyph_data_cmd, 2, 0);
-    primitive_luatex("posthyphenchar", hyph_data_cmd, 3, 0);
-    primitive_luatex("preexhyphenchar", hyph_data_cmd, 4, 0);
-    primitive_luatex("postexhyphenchar", hyph_data_cmd, 5, 0);
-    primitive_luatex("hyphenationmin", hyph_data_cmd, 6, 0);
-    primitive_luatex("hjcode", hyph_data_cmd, 7, 0);
-    primitive_tex("hyphenchar", assign_font_int_cmd, 0, 0);
-    primitive_tex("skewchar", assign_font_int_cmd, 1, 0);
-    primitive_luatex("lpcode", assign_font_int_cmd, lp_code_base, 0);
-    primitive_luatex("rpcode", assign_font_int_cmd, rp_code_base, 0);
-    primitive_luatex("efcode", assign_font_int_cmd, ef_code_base, 0);
-    primitive_luatex("tagcode", assign_font_int_cmd, tag_code, 0);
-    primitive_luatex("ignoreligaturesinfont", assign_font_int_cmd, no_lig_code, 0);
-    primitive_tex("batchmode", set_interaction_cmd, batch_mode, 0);
-    primitive_tex("nonstopmode", set_interaction_cmd, nonstop_mode, 0);
-    primitive_tex("scrollmode", set_interaction_cmd, scroll_mode, 0);
-    primitive_tex("errorstopmode", set_interaction_cmd, error_stop_mode, 0);
-    primitive_tex("openin", in_stream_cmd, 1, 0);
-    primitive_tex("closein", in_stream_cmd, 0, 0);
-    primitive_tex("message", message_cmd, 0, 0);
-    primitive_tex("errmessage", message_cmd, 1, 0);
-    primitive_tex("lowercase", case_shift_cmd, lc_code_base, lc_code_base);
-    primitive_tex("uppercase", case_shift_cmd, uc_code_base, lc_code_base);
-    primitive_tex("show", xray_cmd, show_code, 0);
-    primitive_tex("showbox", xray_cmd, show_box_code, 0);
-    primitive_tex("showthe", xray_cmd, show_the_code, 0);
-    primitive_tex("showlists", xray_cmd, show_lists, 0);
-
-    primitive_tex("openout", extension_cmd, open_code, 0);
-    primitive_tex("write", extension_cmd, write_code, 0);
-    write_loc = cur_val;
-    primitive_tex("closeout", extension_cmd, close_code, 0);
-    primitive_tex("special", extension_cmd, special_code, 0);
-    cs_text(frozen_special) = maketexstring("special");
-    eqtb[frozen_special] = eqtb[cur_val];
-    primitive_tex("immediate", extension_cmd, immediate_code, 0);
-    primitive_luatex("localinterlinepenalty", assign_int_cmd, int_base + local_inter_line_penalty_code, int_base);
-    primitive_luatex("localbrokenpenalty", assign_int_cmd, int_base + local_broken_penalty_code, int_base);
-    primitive_luatex("pagedir", assign_dir_cmd, int_base + page_direction_code, dir_base);
-    primitive_luatex("bodydir", assign_dir_cmd, int_base + body_direction_code, dir_base);
-    primitive_luatex("pardir", assign_dir_cmd, int_base + par_direction_code, dir_base);
-    primitive_luatex("textdir", assign_dir_cmd, int_base + text_direction_code, dir_base);
-    primitive_luatex("mathdir", assign_dir_cmd, int_base + math_direction_code, dir_base);
-    primitive_luatex("linedir", assign_dir_cmd, int_base + line_direction_code, dir_base);
-    primitive_luatex("pageleftoffset", assign_dimen_cmd, dimen_base + page_left_offset_code, dimen_base);
-    primitive_luatex("pagetopoffset", assign_dimen_cmd, dimen_base + page_top_offset_code, dimen_base);
-    primitive_luatex("pagerightoffset", assign_dimen_cmd, dimen_base + page_right_offset_code, dimen_base);
-    primitive_luatex("pagebottomoffset", assign_dimen_cmd, dimen_base + page_bottom_offset_code, dimen_base);
-    primitive_luatex("saveboxresource", extension_cmd, save_box_resource_code, 0);
-    primitive_luatex("useboxresource", extension_cmd, use_box_resource_code, 0);
-    primitive_luatex("saveimageresource", extension_cmd, save_image_resource_code, 0);
-    primitive_luatex("useimageresource", extension_cmd, use_image_resource_code, 0);
-    primitive_luatex("savepos", normal_cmd, save_pos_code, 0);
-    primitive_luatex("savecatcodetable", normal_cmd, save_cat_code_table_code, 0);
-    primitive_luatex("initcatcodetable", normal_cmd, init_cat_code_table_code, 0);
-    primitive_luatex("setrandomseed", normal_cmd, set_random_seed_code, 0);
-    primitive_luatex("latelua", normal_cmd, late_lua_code, 0);
-    primitive_luatex("insertht", convert_cmd, insert_ht_code, 0);
-    primitive_luatex("dviextension", extension_cmd, dvi_extension_code, 0);
-    primitive_luatex("dvifeedback", feedback_cmd, dvi_feedback_code, 0);
-    primitive_luatex("dvivariable", variable_cmd, dvi_variable_code, 0);
-    primitive_luatex("pdfextension", extension_cmd, pdf_extension_code, 0);
-    primitive_luatex("pdffeedback", feedback_cmd, pdf_feedback_code, 0);
-    primitive_luatex("pdfvariable", variable_cmd, pdf_variable_code, 0);
-    primitive_luatex("mathoption", option_cmd, math_option_code, 0);
-
-    /*
-        some of the internal integer parameters are not associated with actual
-        primitives at all.
-    */
-
-    primitive_no("nolocalwhatsits", assign_int_cmd, int_base + no_local_whatsits_code, int_base);
-    primitive_no("nolocaldirs", assign_int_cmd, int_base + no_local_dirs_code, int_base);
-
-
-@ @c
-void initialize_etex_commands(void)
-{
-    primitive_etex("lastnodetype", last_item_cmd, last_node_type_code, 0);
-    primitive_etex("eTeXversion", last_item_cmd, eTeX_version_code, 0);
-    primitive_etex("eTeXminorversion", last_item_cmd, eTeX_minor_version_code, 0);
-    primitive_etex("eTeXrevision", convert_cmd, eTeX_revision_code, 0);
-
-    /*
-        First we implement the additional \eTeX\ parameters in the table of equivalents.
-    */
-
-    primitive_etex("everyeof", assign_toks_cmd, every_eof_loc, local_base);
-    primitive_etex("tracingassigns", assign_int_cmd, int_base + tracing_assigns_code, int_base);
-    primitive_etex("tracinggroups", assign_int_cmd, int_base + tracing_groups_code, int_base);
-    primitive_etex("tracingifs", assign_int_cmd, int_base + tracing_ifs_code, int_base);
-    primitive_etex("tracingscantokens", assign_int_cmd, int_base + tracing_scan_tokens_code, int_base);
-    primitive_etex("tracingnesting", assign_int_cmd, int_base + tracing_nesting_code, int_base);
-    primitive_etex("predisplaydirection", assign_int_cmd, int_base + pre_display_direction_code, int_base);
-    primitive_etex("lastlinefit", assign_int_cmd, int_base + last_line_fit_code, int_base);
-    primitive_etex("savingvdiscards", assign_int_cmd, int_base + saving_vdiscards_code, int_base);
-    primitive_etex("savinghyphcodes", assign_int_cmd, int_base + saving_hyph_codes_code, int_base);
-    primitive_luatex("suppressfontnotfounderror", assign_int_cmd, int_base + suppress_fontnotfound_error_code, int_base);
-    primitive_luatex("suppresslongerror", assign_int_cmd, int_base + suppress_long_error_code, int_base);
-    primitive_luatex("suppressmathparerror", assign_int_cmd, int_base + suppress_mathpar_error_code, int_base);
-    primitive_luatex("suppressifcsnameerror", assign_int_cmd, int_base + suppress_ifcsname_error_code, int_base);
-    primitive_luatex("suppressoutererror", assign_int_cmd, int_base + suppress_outer_error_code, int_base);
-    primitive_luatex("matheqnogapstep", assign_int_cmd, int_base + math_eqno_gap_step_code, int_base);
-    primitive_luatex("mathdisplayskipmode", assign_int_cmd, int_base + math_display_skip_mode_code, int_base);
-    primitive_luatex("mathscriptsmode", assign_int_cmd, int_base + math_scripts_mode_code, int_base);
-    primitive_luatex("mathnolimitsmode", assign_int_cmd, int_base + math_nolimits_mode_code, int_base);
-    primitive_luatex("mathrulesmode", assign_int_cmd, int_base + math_rules_mode_code, int_base);
-    primitive_luatex("mathrulesfam", assign_int_cmd, int_base + math_rules_fam_code, int_base);
-    primitive_luatex("synctex", assign_int_cmd, int_base + synctex_code, int_base);
-
-    primitive_etex("currentgrouplevel", last_item_cmd, current_group_level_code, 0);
-    primitive_etex("currentgrouptype", last_item_cmd, current_group_type_code, 0);
-
-    primitive_etex("currentiflevel", last_item_cmd, current_if_level_code, 0);
-    primitive_etex("currentiftype", last_item_cmd, current_if_type_code, 0);
-    primitive_etex("currentifbranch", last_item_cmd, current_if_branch_code, 0);
-    primitive_etex("fontcharwd", last_item_cmd, font_char_wd_code, 0);
-    primitive_etex("fontcharht", last_item_cmd, font_char_ht_code, 0);
-    primitive_etex("fontchardp", last_item_cmd, font_char_dp_code, 0);
-    primitive_etex("fontcharic", last_item_cmd, font_char_ic_code, 0);
-
-    primitive_etex("parshapelength", last_item_cmd, par_shape_length_code, 0);
-    primitive_etex("parshapeindent", last_item_cmd, par_shape_indent_code, 0);
-    primitive_etex("parshapedimen", last_item_cmd, par_shape_dimen_code, 0);
-
-    primitive_luatex("shapemode", assign_int_cmd, int_base + shape_mode_code, int_base);
-    primitive_luatex("hyphenationbounds", assign_int_cmd, int_base + hyphenation_bounds_code, int_base);
-
-    primitive_etex("showgroups", xray_cmd, show_groups, 0);
-
-    /*
-        The \.{\\showtokens} command displays a token list.
-    */
-
-    primitive_etex("showtokens", xray_cmd, show_tokens, 0);
-
-    /*
-        The \.{\\unexpanded} primitive prevents expansion of tokens much as
-        the result from \.{\\the} applied to a token variable.  The
-        \.{\\detokenize} primitive converts a token list into a list of
-        character tokens much as if the token list were written to a file.  We
-        use the fact that the command modifiers for \.{\\unexpanded} and
-        \.{\\detokenize} are odd whereas those for \.{\\the} and \.{\\showthe}
-        are even.
-    */
-
-    primitive_etex("unexpanded", the_cmd, 1, 0);
-    primitive_etex("detokenize", the_cmd, show_tokens, 0);
-
-    /*
-        The \.{\\showifs} command displays all currently active conditionals.
-    */
-
-    primitive_etex("showifs", xray_cmd, show_ifs, 0);
-
-    /*
-        The \.{\\interactionmode} primitive allows to query and set the interaction mode.
-    */
-
-    primitive_etex("interactionmode", set_page_int_cmd, 2, 0);
-
-    /*
-        The |scan_tokens| feature of \eTeX\ defines the \.{\\scantokens} primitive.
-    */
-
-    primitive_etex("scantokens", input_cmd, 2, 0);
-    primitive_luatex("scantextokens", input_cmd, 3, 0);
-
-    primitive_etex("readline", read_to_cs_cmd, 1, 0);
-
-    primitive_etex("unless", expand_after_cmd, 1, 0);
-    primitive_etex("ifdefined", if_test_cmd, if_def_code, 0);
-    primitive_etex("ifcsname", if_test_cmd, if_cs_code, 0);
-    primitive_etex("iffontchar", if_test_cmd, if_font_char_code, 0);
-    primitive_luatex("ifincsname", if_test_cmd, if_in_csname_code, 0);
-    primitive_luatex("ifabsnum", if_test_cmd, if_abs_num_code, 0);
-    primitive_luatex("ifabsdim", if_test_cmd, if_abs_dim_code, 0);
-
-    /*
-        The |protected| feature of \eTeX\ defines the \.{\\protected} prefix
-        command for macro definitions.  Such macros are protected against
-        expansions when lists of expanded tokens are built, e.g., for \.{\\edef}
-        or during \.{\\write}.
-    */
-
-    primitive_etex("protected", prefix_cmd, 8, 0);
-
-    /*
-        Here are the additional \eTeX\ primitives for expressions.
-    */
-
-    primitive_etex("numexpr", last_item_cmd, eTeX_expr - int_val_level + int_val_level, 0);
-    primitive_etex("dimexpr", last_item_cmd, eTeX_expr - int_val_level + dimen_val_level, 0);
-    primitive_etex("glueexpr", last_item_cmd, eTeX_expr - int_val_level + glue_val_level, 0);
-    primitive_etex("muexpr", last_item_cmd, eTeX_expr - int_val_level + mu_val_level, 0);
-
-    primitive_etex("gluestretchorder", last_item_cmd, glue_stretch_order_code, 0);
-    primitive_etex("glueshrinkorder", last_item_cmd, glue_shrink_order_code, 0);
-    primitive_etex("gluestretch", last_item_cmd, glue_stretch_code, 0);
-    primitive_etex("glueshrink", last_item_cmd, glue_shrink_code, 0);
-
-    primitive_etex("mutoglue", last_item_cmd, mu_to_glue_code, 0);
-    primitive_etex("gluetomu", last_item_cmd, glue_to_mu_code, 0);
-
-    /*
-        The \.{\\pagediscards} and \.{\\splitdiscards} commands share the
-        command code |un_vbox| with \.{\\unvbox} and \.{\\unvcopy}, they are
-        distinguished by their |chr_code| values |last_box_code| and
-        |vsplit_code|.  These |chr_code| values are larger than |box_code| and
-        |copy_code|.
-    */
-
-    primitive_etex("pagediscards", un_vbox_cmd, last_box_code, 0);
-    primitive_etex("splitdiscards", un_vbox_cmd, vsplit_code, 0);
-
-    /*
-        The \.{\\interlinepenalties}, \.{\\clubpenalties}, \.{\\widowpenalties},
-        and \.{\\displaywidowpenalties} commands allow to define arrays of
-        penalty values to be used instead of the corresponding single values.
-    */
-
-    primitive_etex("interlinepenalties", set_etex_shape_cmd, inter_line_penalties_loc, etex_pen_base);
-    primitive_etex("clubpenalties", set_etex_shape_cmd, club_penalties_loc, etex_pen_base);
-    primitive_etex("widowpenalties", set_etex_shape_cmd, widow_penalties_loc, etex_pen_base);
-    primitive_etex("displaywidowpenalties", set_etex_shape_cmd, display_widow_penalties_loc, etex_pen_base);
-
-}
+% commands.w
+%
+% Copyright 2009-2010 Taco Hoekwater <taco@@luatex.org>
+%
+% 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 <http://www.gnu.org/licenses/>.
+
+\def\eTeX{e-\TeX}
+
+@ @c
+
+
+#include "ptexlib.h"
+
+@ The symbolic names for glue parameters are put into \TeX's hash table
+by using the routine called |primitive|, defined below. Let us enter them
+now, so that we don't have to list all those parameter names anywhere else.
+
+ at c
+void initialize_commands(void)
+{
+
+    primitive_tex("lineskip", assign_glue_cmd, glue_base + line_skip_code, glue_base);
+    primitive_tex("baselineskip", assign_glue_cmd, glue_base + baseline_skip_code, glue_base);
+    primitive_tex("parskip", assign_glue_cmd, glue_base + par_skip_code, glue_base);
+    primitive_tex("abovedisplayskip", assign_glue_cmd, glue_base + above_display_skip_code, glue_base);
+    primitive_tex("belowdisplayskip", assign_glue_cmd, glue_base + below_display_skip_code, glue_base);
+    primitive_tex("abovedisplayshortskip", assign_glue_cmd, glue_base + above_display_short_skip_code, glue_base);
+    primitive_tex("belowdisplayshortskip", assign_glue_cmd, glue_base + below_display_short_skip_code, glue_base);
+    primitive_tex("leftskip", assign_glue_cmd, glue_base + left_skip_code, glue_base);
+    primitive_tex("rightskip", assign_glue_cmd, glue_base + right_skip_code, glue_base);
+    primitive_tex("topskip", assign_glue_cmd, glue_base + top_skip_code, glue_base);
+    primitive_tex("splittopskip", assign_glue_cmd, glue_base + split_top_skip_code, glue_base);
+    primitive_tex("tabskip", assign_glue_cmd, glue_base + tab_skip_code, glue_base);
+    primitive_tex("spaceskip", assign_glue_cmd, glue_base + space_skip_code, glue_base);
+    primitive_tex("xspaceskip", assign_glue_cmd, glue_base + xspace_skip_code, glue_base);
+    primitive_tex("parfillskip", assign_glue_cmd, glue_base + par_fill_skip_code, glue_base);
+    primitive_tex("thinmuskip", assign_mu_glue_cmd, glue_base + thin_mu_skip_code, glue_base + thin_mu_skip_code);
+    primitive_tex("medmuskip", assign_mu_glue_cmd, glue_base + med_mu_skip_code, glue_base + thin_mu_skip_code);
+    primitive_tex("thickmuskip", assign_mu_glue_cmd, glue_base + thick_mu_skip_code, glue_base + thin_mu_skip_code);
+    primitive_luatex("mathsurroundskip", assign_glue_cmd, glue_base + math_skip_code, glue_base);
+    primitive_luatex("mathsurroundmode", assign_int_cmd, int_base + math_skip_mode_code, int_base);
+    primitive_tex("output", assign_toks_cmd, output_routine_loc, local_base);
+    primitive_tex("everypar", assign_toks_cmd, every_par_loc, local_base);
+    primitive_tex("everymath", assign_toks_cmd, every_math_loc, local_base);
+    primitive_tex("everydisplay", assign_toks_cmd, every_display_loc, local_base);
+    primitive_tex("everyhbox", assign_toks_cmd, every_hbox_loc, local_base);
+    primitive_tex("everyvbox", assign_toks_cmd, every_vbox_loc, local_base);
+    primitive_tex("everyjob", assign_toks_cmd, every_job_loc, local_base);
+    primitive_tex("everycr", assign_toks_cmd, every_cr_loc, local_base);
+    primitive_tex("errhelp", assign_toks_cmd, err_help_loc, local_base);
+
+    /* The integer parameter names must be entered into the hash table */
+
+    primitive_tex("pretolerance", assign_int_cmd, int_base + pretolerance_code, int_base);
+    primitive_tex("tolerance", assign_int_cmd, int_base + tolerance_code, int_base);
+    primitive_tex("linepenalty", assign_int_cmd, int_base + line_penalty_code, int_base);
+    primitive_tex("hyphenpenalty", assign_int_cmd, int_base + hyphen_penalty_code, int_base);
+    primitive_tex("exhyphenpenalty", assign_int_cmd, int_base + ex_hyphen_penalty_code, int_base);
+    primitive_tex("clubpenalty", assign_int_cmd, int_base + club_penalty_code, int_base);
+    primitive_tex("widowpenalty", assign_int_cmd, int_base + widow_penalty_code, int_base);
+    primitive_tex("displaywidowpenalty", assign_int_cmd, int_base + display_widow_penalty_code, int_base);
+    primitive_tex("brokenpenalty", assign_int_cmd, int_base + broken_penalty_code, int_base);
+    primitive_tex("binoppenalty", assign_int_cmd, int_base + bin_op_penalty_code, int_base);
+    primitive_tex("relpenalty", assign_int_cmd, int_base + rel_penalty_code, int_base);
+    primitive_tex("predisplaypenalty", assign_int_cmd, int_base + pre_display_penalty_code, int_base);
+    primitive_tex("postdisplaypenalty", assign_int_cmd, int_base + post_display_penalty_code, int_base);
+    primitive_tex("interlinepenalty", assign_int_cmd, int_base + inter_line_penalty_code, int_base);
+    primitive_tex("doublehyphendemerits", assign_int_cmd, int_base + double_hyphen_demerits_code, int_base);
+    primitive_tex("finalhyphendemerits", assign_int_cmd, int_base + final_hyphen_demerits_code, int_base);
+    primitive_tex("adjdemerits", assign_int_cmd, int_base + adj_demerits_code, int_base);
+    primitive_tex("mag", assign_int_cmd, int_base + mag_code, int_base);
+    primitive_tex("delimiterfactor", assign_int_cmd, int_base + delimiter_factor_code, int_base);
+    primitive_tex("looseness", assign_int_cmd, int_base + looseness_code, int_base);
+    primitive_tex("time", assign_int_cmd, int_base + time_code, int_base);
+    primitive_tex("day", assign_int_cmd, int_base + day_code, int_base);
+    primitive_tex("month", assign_int_cmd, int_base + month_code, int_base);
+    primitive_tex("year", assign_int_cmd, int_base + year_code, int_base);
+    primitive_tex("showboxbreadth", assign_int_cmd, int_base + show_box_breadth_code, int_base);
+    primitive_tex("showboxdepth", assign_int_cmd, int_base + show_box_depth_code, int_base);
+    primitive_tex("hbadness", assign_int_cmd, int_base + hbadness_code, int_base);
+    primitive_tex("vbadness", assign_int_cmd, int_base + vbadness_code, int_base);
+    primitive_tex("pausing", assign_int_cmd, int_base + pausing_code, int_base);
+    primitive_tex("tracingonline", assign_int_cmd, int_base + tracing_online_code, int_base);
+    primitive_tex("tracingmacros", assign_int_cmd, int_base + tracing_macros_code, int_base);
+    primitive_tex("tracingstats", assign_int_cmd, int_base + tracing_stats_code, int_base);
+    primitive_tex("tracingparagraphs", assign_int_cmd, int_base + tracing_paragraphs_code, int_base);
+    primitive_tex("tracingpages", assign_int_cmd, int_base + tracing_pages_code, int_base);
+    primitive_tex("tracingoutput", assign_int_cmd, int_base + tracing_output_code, int_base);
+    primitive_tex("tracinglostchars", assign_int_cmd, int_base + tracing_lost_chars_code, int_base);
+    primitive_tex("tracingcommands", assign_int_cmd, int_base + tracing_commands_code, int_base);
+    primitive_tex("tracingrestores", assign_int_cmd, int_base + tracing_restores_code, int_base);
+    primitive_tex("uchyph", assign_int_cmd, int_base + uc_hyph_code, int_base);
+    primitive_tex("outputpenalty", assign_int_cmd, int_base + output_penalty_code, int_base);
+    primitive_tex("maxdeadcycles", assign_int_cmd, int_base + max_dead_cycles_code, int_base);
+    primitive_tex("hangafter", assign_int_cmd, int_base + hang_after_code, int_base);
+    primitive_tex("floatingpenalty", assign_int_cmd, int_base + floating_penalty_code, int_base);
+    primitive_tex("globaldefs", assign_int_cmd, int_base + global_defs_code, int_base);
+    primitive_tex("fam", assign_int_cmd, int_base + cur_fam_code, int_base);
+    primitive_tex("escapechar", assign_int_cmd, int_base + escape_char_code, int_base);
+    primitive_tex("defaulthyphenchar", assign_int_cmd, int_base + default_hyphen_char_code, int_base);
+    primitive_tex("defaultskewchar", assign_int_cmd, int_base + default_skew_char_code, int_base);
+    primitive_tex("endlinechar", assign_int_cmd, int_base + end_line_char_code, int_base);
+    primitive_tex("newlinechar", assign_int_cmd, int_base + new_line_char_code, int_base);
+    primitive_tex("language", assign_int_cmd, int_base + language_code, int_base);
+    primitive_tex("setlanguage", assign_int_cmd, int_base + cur_lang_code, int_base);
+    primitive_tex("firstvalidlanguage", assign_int_cmd, int_base + first_valid_language_code, int_base);
+    primitive_tex("exhyphenchar", assign_int_cmd, int_base + ex_hyphen_char_code, int_base);
+    primitive_tex("lefthyphenmin", assign_int_cmd, int_base + left_hyphen_min_code, int_base);
+    primitive_tex("righthyphenmin", assign_int_cmd, int_base + right_hyphen_min_code, int_base);
+    primitive_tex("holdinginserts", assign_int_cmd, int_base + holding_inserts_code, int_base);
+    primitive_tex("errorcontextlines", assign_int_cmd, int_base + error_context_lines_code, int_base);
+    primitive_luatex("nokerns", assign_int_cmd, int_base + disable_kern_code, int_base);
+    primitive_luatex("noligs", assign_int_cmd, int_base + disable_lig_code, int_base);
+    primitive_luatex("nospaces", assign_int_cmd, int_base + disable_space_code, int_base);
+    primitive_luatex("catcodetable", assign_int_cmd, int_base + cat_code_table_code, int_base);
+    primitive_luatex("outputbox", assign_int_cmd, int_base + output_box_code, int_base);
+    primitive_luatex("outputmode", assign_int_cmd, int_base + output_mode_code, int_base);
+    primitive_luatex("adjustspacing", assign_int_cmd, int_base + adjust_spacing_code, int_base);
+    primitive_luatex("protrudechars", assign_int_cmd, int_base + protrude_chars_code, int_base);
+    primitive_luatex("tracingfonts", assign_int_cmd, int_base + tracing_fonts_code, int_base);
+    primitive_luatex("draftmode", assign_int_cmd, int_base + draft_mode_code, int_base);
+    primitive_tex("parindent", assign_dimen_cmd, dimen_base + par_indent_code, dimen_base);
+    primitive_tex("mathsurround", assign_dimen_cmd, dimen_base + math_surround_code, dimen_base);
+    primitive_tex("lineskiplimit", assign_dimen_cmd, dimen_base + line_skip_limit_code, dimen_base);
+    primitive_tex("hsize", assign_dimen_cmd, dimen_base + hsize_code, dimen_base);
+    primitive_tex("vsize", assign_dimen_cmd, dimen_base + vsize_code, dimen_base);
+    primitive_tex("maxdepth", assign_dimen_cmd, dimen_base + max_depth_code, dimen_base);
+    primitive_tex("splitmaxdepth", assign_dimen_cmd, dimen_base + split_max_depth_code, dimen_base);
+    primitive_tex("boxmaxdepth", assign_dimen_cmd, dimen_base + box_max_depth_code, dimen_base);
+    primitive_tex("hfuzz", assign_dimen_cmd, dimen_base + hfuzz_code, dimen_base);
+    primitive_tex("vfuzz", assign_dimen_cmd, dimen_base + vfuzz_code, dimen_base);
+    primitive_tex("delimitershortfall", assign_dimen_cmd, dimen_base + delimiter_shortfall_code, dimen_base);
+    primitive_tex("nulldelimiterspace", assign_dimen_cmd, dimen_base + null_delimiter_space_code, dimen_base);
+    primitive_tex("scriptspace", assign_dimen_cmd, dimen_base + script_space_code, dimen_base);
+    primitive_tex("predisplaysize", assign_dimen_cmd, dimen_base + pre_display_size_code, dimen_base);
+    primitive_tex("displaywidth", assign_dimen_cmd, dimen_base + display_width_code, dimen_base);
+    primitive_tex("displayindent", assign_dimen_cmd, dimen_base + display_indent_code, dimen_base);
+    primitive_tex("overfullrule", assign_dimen_cmd, dimen_base + overfull_rule_code, dimen_base);
+    primitive_tex("hangindent", assign_dimen_cmd, dimen_base + hang_indent_code, dimen_base);
+    primitive_tex("hoffset", assign_dimen_cmd, dimen_base + h_offset_code, dimen_base);
+    primitive_tex("voffset", assign_dimen_cmd, dimen_base + v_offset_code, dimen_base);
+    primitive_tex("emergencystretch", assign_dimen_cmd, dimen_base + emergency_stretch_code, dimen_base);
+    primitive_luatex("pagewidth", assign_dimen_cmd, dimen_base + page_width_code, dimen_base);
+    primitive_luatex("pageheight", assign_dimen_cmd, dimen_base + page_height_code, dimen_base);
+    primitive_luatex("pxdimen", assign_dimen_cmd, dimen_base + px_dimen_code, dimen_base);
+    primitive_luatex("predisplaygapfactor", assign_int_cmd, int_base + math_pre_display_gap_factor_code, int_base);
+    primitive_luatex("hyphenpenaltymode", assign_int_cmd, int_base + hyphen_penalty_mode_code, int_base);
+    primitive_luatex("automatichyphenpenalty", assign_int_cmd, int_base + automatic_hyphen_penalty_code, int_base);
+    primitive_luatex("explicithyphenpenalty", assign_int_cmd, int_base + explicit_hyphen_penalty_code, int_base);
+
+    /* Many of \TeX's primitives need no |equiv|, since they are identifiable
+       by their |eq_type| alone. These primitives are loaded into the hash table
+       as follows: */
+
+    primitive_tex(" ", ex_space_cmd, 0, 0);
+    primitive_tex("/", ital_corr_cmd, 0, 0);
+    primitive_tex("accent", accent_cmd, 0, 0);
+    primitive_tex("advance", advance_cmd, 0, 0);
+    primitive_tex("afterassignment", after_assignment_cmd, 0, 0);
+    primitive_tex("aftergroup", after_group_cmd, 0, 0);
+    primitive_tex("begingroup", begin_group_cmd, 0, 0);
+    primitive_tex("char", char_num_cmd, 0, 0);
+    primitive_tex("csname", cs_name_cmd, 0, 0);
+    primitive_luatex("lastnamedcs", cs_name_cmd, 1, 0);
+    primitive_luatex("begincsname", cs_name_cmd, 2, 0);
+    primitive_tex("delimiter", delim_num_cmd, 0, 0);
+    primitive_luatex("Udelimiter", delim_num_cmd, 1, 0);
+    primitive_tex("divide", divide_cmd, 0, 0);
+    primitive_tex("endcsname", end_cs_name_cmd, 0, 0);
+    primitive_tex("endgroup", end_group_cmd, 0, 0);
+    cs_text(frozen_end_group) = maketexstring("endgroup");
+    eqtb[frozen_end_group] = eqtb[cur_val];
+    primitive_tex("expandafter", expand_after_cmd, 0, 0);
+    primitive_tex("font", def_font_cmd, 0, 0);
+    primitive_luatex("letterspacefont", letterspace_font_cmd, 0, 0);
+    primitive_luatex("expandglyphsinfont", normal_cmd, expand_font_code, 0);
+    primitive_luatex("copyfont", copy_font_cmd, 0, 0);
+    primitive_luatex("setfontid", set_font_id_cmd, 0, 0);
+    primitive_tex("fontdimen", assign_font_dimen_cmd, 0, 0);
+    primitive_tex("halign", halign_cmd, 0, 0);
+    primitive_tex("hrule", hrule_cmd, 0, 0);
+    primitive_luatex("nohrule", no_hrule_cmd, 0, 0);
+    primitive_tex("ignorespaces", ignore_spaces_cmd, 0, 0);
+    primitive_tex("insert", insert_cmd, 0, 0);
+    primitive_luatex("leftghost", char_ghost_cmd, 0, 0);
+    primitive_tex("mark", mark_cmd, 0, 0);
+    primitive_tex("mathaccent", math_accent_cmd, 0, 0);
+    primitive_luatex("Umathaccent", math_accent_cmd, 1, 0);
+    primitive_tex("mathchar", math_char_num_cmd, 0, 0);
+    primitive_luatex("Umathchar", math_char_num_cmd, 1, 0);
+    primitive_luatex("Umathcharnum", math_char_num_cmd, 2, 0);
+    primitive_tex("mathchoice", math_choice_cmd, 0, 0);
+    primitive_luatex("Ustack", math_choice_cmd, 1, 0);
+    primitive_tex("multiply", multiply_cmd, 0, 0);
+    primitive_tex("noalign", no_align_cmd, 0, 0);
+    primitive_tex("noboundary", boundary_cmd, 0, 0);
+    primitive_tex("boundary", boundary_cmd, 1, 0);
+    primitive_tex("protrusionboundary", boundary_cmd, 2, 0);
+    primitive_tex("wordboundary", boundary_cmd, 3, 0);
+    primitive_tex("noexpand", no_expand_cmd, 0, 0);
+    primitive_luatex("primitive", no_expand_cmd, 1, 0);
+    primitive_tex("nonscript", non_script_cmd, 0, 0);
+    primitive_tex("omit", omit_cmd, 0, 0);
+    primitive_tex("parshape", set_tex_shape_cmd, par_shape_loc, par_shape_loc);
+    primitive_tex("penalty", break_penalty_cmd, 0, 0);
+    primitive_tex("prevgraf", set_prev_graf_cmd, 0, 0);
+    primitive_tex("radical", radical_cmd, 0, 0);
+    primitive_luatex("Uradical", radical_cmd, 1, 0);
+    primitive_luatex("Uroot", radical_cmd, 2, 0);
+    primitive_luatex("Uunderdelimiter", radical_cmd, 3, 0);
+    primitive_luatex("Uoverdelimiter", radical_cmd, 4, 0);
+    primitive_luatex("Udelimiterunder", radical_cmd, 5, 0);
+    primitive_luatex("Udelimiterover", radical_cmd, 6, 0);
+    primitive_luatex("Uhextensible", radical_cmd, 7, 0);
+    primitive_tex("read", read_to_cs_cmd, 0, 0);
+    primitive_tex("relax", relax_cmd, too_big_char, too_big_char);
+    cs_text(frozen_relax) = maketexstring("relax");
+    eqtb[frozen_relax] = eqtb[cur_val];
+    primitive_luatex("rightghost", char_ghost_cmd, 1, 0);
+    primitive_tex("setbox", set_box_cmd, 0, 0);
+    primitive_tex("the", the_cmd, 0, 0);
+    primitive_luatex("toksapp", combine_toks_cmd, 0, 0);
+    primitive_luatex("tokspre", combine_toks_cmd, 1, 0);
+    primitive_luatex("etoksapp", combine_toks_cmd, 2, 0);
+    primitive_luatex("etokspre", combine_toks_cmd, 3, 0);
+    primitive_tex("toks", toks_register_cmd, 0, 0);
+    primitive_tex("vadjust", vadjust_cmd, 0, 0);
+    primitive_tex("valign", valign_cmd, 0, 0);
+    primitive_tex("vcenter", vcenter_cmd, 0, 0);
+    primitive_tex("vrule", vrule_cmd, 0, 0);
+    primitive_luatex("novrule", no_vrule_cmd, 0, 0);
+    primitive_tex("par", par_end_cmd, too_big_char, too_big_char);      /* cf.\ |scan_file_name| */
+    par_loc = cur_val;
+    par_token = cs_token_flag + par_loc;
+    @<Create a bunch of primitives@>;
+    @<Create the math param primitives@>;
+    @<Create another bunch of primitives@>;
+}
+
+
+@ These are in a separate module due to a CWEAVE limitation.
+
+@<Create a bunch of primitives@>=
+
+    /*
+        The processing of \.{\\input} involves the |start_input| subroutine,
+        which will be declared later; the processing of \.{\\endinput} is trivial.
+    */
+
+    primitive_tex("input", input_cmd, 0, 0);
+    primitive_tex("endinput", input_cmd, 1, 0);
+    primitive_tex("topmark", top_bot_mark_cmd, top_mark_code, 0);
+    primitive_tex("firstmark", top_bot_mark_cmd, first_mark_code, 0);
+    primitive_tex("botmark", top_bot_mark_cmd, bot_mark_code, 0);
+    primitive_tex("splitfirstmark", top_bot_mark_cmd, split_first_mark_code, 0);
+    primitive_tex("splitbotmark", top_bot_mark_cmd, split_bot_mark_code, 0);
+    primitive_luatex("clearmarks", mark_cmd, clear_marks_code, 0);
+    primitive_etex("marks", mark_cmd, marks_code, 0);
+    primitive_etex("topmarks", top_bot_mark_cmd, top_mark_code + marks_code, 0);
+    primitive_etex("firstmarks", top_bot_mark_cmd, first_mark_code + marks_code, 0);
+    primitive_etex("botmarks", top_bot_mark_cmd, bot_mark_code + marks_code, 0);
+    primitive_etex("splitfirstmarks", top_bot_mark_cmd, split_first_mark_code + marks_code, 0);
+    primitive_etex("splitbotmarks", top_bot_mark_cmd, split_bot_mark_code + marks_code, 0);
+
+    /*
+        The hash table is initialized with `\.{\\count}', `\.{\\attribute}',
+        `\.{\\dimen}', `\.{\\skip}', and `\.{\\muskip}' all having |register|
+        as their command code; they are distinguished by the |chr_code|, which
+        is either |int_val|, |attr_val|, |dimen_val|, |glue_val|, or |mu_val|.
+    */
+
+    primitive_tex("count", register_cmd, int_val_level, 0);
+    primitive_luatex("attribute", register_cmd, attr_val_level, 0);
+    primitive_tex("dimen", register_cmd, dimen_val_level, 0);
+    primitive_tex("skip", register_cmd, glue_val_level, 0);
+    primitive_tex("muskip", register_cmd, mu_val_level, 0);
+
+    primitive_tex("spacefactor", set_aux_cmd, hmode, 0);
+    primitive_tex("prevdepth", set_aux_cmd, vmode, 0);
+    primitive_tex("deadcycles", set_page_int_cmd, 0, 0);
+    primitive_tex("insertpenalties", set_page_int_cmd, 1, 0);
+    primitive_tex("wd", set_box_dimen_cmd, width_offset, 0);
+    primitive_tex("ht", set_box_dimen_cmd, height_offset, 0);
+    primitive_tex("dp", set_box_dimen_cmd, depth_offset, 0);
+    primitive_tex("lastpenalty", last_item_cmd, lastpenalty_code, 0);
+    primitive_tex("lastkern", last_item_cmd, lastkern_code, 0);
+    primitive_tex("lastskip", last_item_cmd, lastskip_code, 0);
+    primitive_tex("inputlineno", last_item_cmd, input_line_no_code, 0);
+    primitive_tex("badness", last_item_cmd, badness_code, 0);
+    primitive_luatex("luatexversion", last_item_cmd, luatex_version_code, 0);
+    primitive_luatex("lastsavedboxresourceindex", last_item_cmd, last_saved_box_resource_index_code, 0);
+    primitive_luatex("lastsavedimageresourceindex", last_item_cmd, last_saved_image_resource_index_code, 0);
+    primitive_luatex("lastsavedimageresourcepages", last_item_cmd, last_saved_image_resource_pages_code, 0);
+    primitive_luatex("lastxpos", last_item_cmd, last_x_pos_code, 0);
+    primitive_luatex("lastypos", last_item_cmd, last_y_pos_code, 0);
+    primitive_luatex("randomseed", last_item_cmd, random_seed_code, 0);
+
+    primitive_tex("number", convert_cmd, number_code, 0);
+    primitive_tex("romannumeral", convert_cmd, roman_numeral_code, 0);
+    primitive_tex("string", convert_cmd, string_code, 0);
+    primitive_tex("csstring", convert_cmd, cs_string_code, 0);
+    primitive_tex("meaning", convert_cmd, meaning_code, 0);
+    primitive_etex("eTeXVersion", convert_cmd, etex_code, 0);
+    primitive_tex("fontname", convert_cmd, font_name_code, 0);
+    primitive_luatex("fontid", convert_cmd, font_id_code, 0);
+    primitive_luatex("luatexrevision", convert_cmd, luatex_revision_code, 0);
+    primitive_luatex("luatexdatestamp", convert_cmd, luatex_date_code, 0);
+    primitive_luatex("luatexbanner", convert_cmd, luatex_banner_code, 0);
+    primitive_luatex("leftmarginkern", convert_cmd, left_margin_kern_code, 0);
+    primitive_luatex("rightmarginkern", convert_cmd, right_margin_kern_code, 0);
+    primitive_luatex("uniformdeviate", convert_cmd, uniform_deviate_code, 0);
+    primitive_luatex("normaldeviate", convert_cmd, normal_deviate_code, 0);
+    primitive_core("directlua", convert_cmd, lua_code, 0);
+    primitive_luatex("luafunction", convert_cmd, lua_function_code, 0);
+    primitive_luatex("luaescapestring", convert_cmd, lua_escape_string_code, 0);
+    primitive_luatex("mathstyle", convert_cmd, math_style_code, 0);
+    primitive_luatex("expanded", convert_cmd, expanded_code, 0);
+    primitive_tex("jobname", convert_cmd, job_name_code, 0);
+    primitive_luatex("formatname", convert_cmd, format_name_code, 0);
+    primitive_luatex("Uchar", convert_cmd, uchar_code, 0);
+
+    primitive_luatex("Umathcharclass", convert_cmd, math_char_class_code, 0);
+    primitive_luatex("Umathcharfam", convert_cmd, math_char_fam_code, 0);
+    primitive_luatex("Umathcharslot", convert_cmd, math_char_slot_code, 0);
+
+    primitive_tex("if", if_test_cmd, if_char_code, 0);
+    primitive_tex("ifcat", if_test_cmd, if_cat_code, 0);
+    primitive_tex("ifnum", if_test_cmd, if_int_code, 0);
+    primitive_tex("ifdim", if_test_cmd, if_dim_code, 0);
+    primitive_tex("ifodd", if_test_cmd, if_odd_code, 0);
+    primitive_tex("ifvmode", if_test_cmd, if_vmode_code, 0);
+    primitive_tex("ifhmode", if_test_cmd, if_hmode_code, 0);
+    primitive_tex("ifmmode", if_test_cmd, if_mmode_code, 0);
+    primitive_tex("ifinner", if_test_cmd, if_inner_code, 0);
+    primitive_tex("ifvoid", if_test_cmd, if_void_code, 0);
+
+    primitive_tex("ifhbox", if_test_cmd, if_hbox_code, 0);
+    primitive_tex("ifvbox", if_test_cmd, if_vbox_code, 0);
+    primitive_tex("ifx", if_test_cmd, ifx_code, 0);
+    primitive_tex("ifeof", if_test_cmd, if_eof_code, 0);
+    primitive_tex("iftrue", if_test_cmd, if_true_code, 0);
+    primitive_tex("iffalse", if_test_cmd, if_false_code, 0);
+    primitive_tex("ifcase", if_test_cmd, if_case_code, 0);
+    primitive_luatex("ifprimitive", if_test_cmd, if_primitive_code, 0);
+    primitive_tex("fi", fi_or_else_cmd, fi_code, 0);
+    cs_text(frozen_fi) = maketexstring("fi");
+    eqtb[frozen_fi] = eqtb[cur_val];
+    primitive_tex("or", fi_or_else_cmd, or_code, 0);
+    primitive_tex("else", fi_or_else_cmd, else_code, 0);
+
+    /*
+        \TeX\ always knows at least one font, namely the null font. It has no
+        characters, and its seven parameters are all equal to zero.
+    */
+
+    primitive_tex("nullfont", set_font_cmd, null_font, 0);
+    cs_text(frozen_null_font) = maketexstring("nullfont");
+    eqtb[frozen_null_font] = eqtb[cur_val];
+
+    primitive_tex("span", tab_mark_cmd, span_code, tab_mark_cmd_code);
+    primitive_luatex("aligntab", tab_mark_cmd, tab_mark_cmd_code, tab_mark_cmd_code);
+    primitive_luatex("alignmark", mac_param_cmd, tab_mark_cmd_code, tab_mark_cmd_code);
+    primitive_tex("cr", car_ret_cmd, cr_code, cr_code);
+    cs_text(frozen_cr) = maketexstring("cr");
+    eqtb[frozen_cr] = eqtb[cur_val];
+    primitive_tex("crcr", car_ret_cmd, cr_cr_code, cr_code);
+    cs_text(frozen_end_template) = maketexstring("endtemplate");
+    cs_text(frozen_endv) = maketexstring("endtemplate");
+    set_eq_type(frozen_endv, endv_cmd);
+    set_equiv(frozen_endv, null_list);
+    set_eq_level(frozen_endv, level_one);
+    eqtb[frozen_end_template] = eqtb[frozen_endv];
+    set_eq_type(frozen_end_template, end_template_cmd);
+
+    primitive_tex("pagegoal", set_page_dimen_cmd, 0, 0);
+    primitive_tex("pagetotal", set_page_dimen_cmd, 1, 0);
+    primitive_tex("pagestretch", set_page_dimen_cmd, 2, 0);
+    primitive_tex("pagefilstretch", set_page_dimen_cmd, 3, 0);
+    primitive_tex("pagefillstretch", set_page_dimen_cmd, 4, 0);
+    primitive_tex("pagefilllstretch", set_page_dimen_cmd, 5, 0);
+    primitive_tex("pageshrink", set_page_dimen_cmd, 6, 0);
+    primitive_tex("pagedepth", set_page_dimen_cmd, 7, 0);
+
+    /*
+        Either \.{\\dump} or \.{\\end} will cause |main_control| to enter the
+        endgame, since both of them have `|stop|' as their command code.
+    */
+
+    primitive_tex("end", stop_cmd, 0, 0);
+    primitive_tex("dump", stop_cmd, 1, 0);
+
+    primitive_tex("hskip", hskip_cmd, skip_code, 0);
+    primitive_tex("hfil", hskip_cmd, fil_code, 0);
+    primitive_tex("hfill", hskip_cmd, fill_code, 0);
+    primitive_tex("hss", hskip_cmd, ss_code, 0);
+    primitive_tex("hfilneg", hskip_cmd, fil_neg_code, 0);
+    primitive_tex("vskip", vskip_cmd, skip_code, 0);
+    primitive_tex("vfil", vskip_cmd, fil_code, 0);
+    primitive_tex("vfill", vskip_cmd, fill_code, 0);
+    primitive_tex("vss", vskip_cmd, ss_code, 0);
+    primitive_tex("vfilneg", vskip_cmd, fil_neg_code, 0);
+    primitive_tex("mskip", mskip_cmd, mskip_code, 0);
+    primitive_tex("kern", kern_cmd, explicit_kern, 0);
+    primitive_tex("mkern", mkern_cmd, mu_glue, 0);
+    primitive_tex("moveleft", hmove_cmd, 1, 0);
+    primitive_tex("moveright", hmove_cmd, 0, 0);
+    primitive_tex("raise", vmove_cmd, 1, 0);
+    primitive_tex("lower", vmove_cmd, 0, 0);
+    primitive_tex("box", make_box_cmd, box_code, 0);
+    primitive_tex("copy", make_box_cmd, copy_code, 0);
+    primitive_tex("lastbox", make_box_cmd, last_box_code, 0);
+    primitive_tex("vsplit", make_box_cmd, vsplit_code, 0);
+    primitive_tex("tpack", make_box_cmd, tpack_code, 0);
+    primitive_tex("vpack", make_box_cmd, vpack_code, 0);
+    primitive_tex("hpack", make_box_cmd, hpack_code, 0);
+    primitive_tex("vtop", make_box_cmd, vtop_code, 0);
+    primitive_tex("vbox", make_box_cmd, vtop_code + vmode, 0);
+    primitive_tex("hbox", make_box_cmd, vtop_code + hmode, 0);
+    primitive_tex("shipout", leader_ship_cmd, a_leaders - 1, 0);        /* |ship_out_flag=leader_flag-1| */
+    primitive_tex("leaders", leader_ship_cmd, a_leaders, 0);
+    primitive_tex("cleaders", leader_ship_cmd, c_leaders, 0);
+    primitive_tex("xleaders", leader_ship_cmd, x_leaders, 0);
+    primitive_luatex("gleaders", leader_ship_cmd, g_leaders, 0);
+    primitive_luatex("boxdir", assign_box_dir_cmd, 0, 0);
+    primitive_tex("indent", start_par_cmd, 1, 0);
+    primitive_tex("noindent", start_par_cmd, 0, 0);
+    primitive_luatex("quitvmode", start_par_cmd, 2, 0);
+    primitive_tex("unpenalty", remove_item_cmd, penalty_node, 0);
+    primitive_tex("unkern", remove_item_cmd, kern_node, 0);
+    primitive_tex("unskip", remove_item_cmd, glue_node, 0);
+    primitive_tex("unhbox", un_hbox_cmd, box_code, 0);
+    primitive_tex("unhcopy", un_hbox_cmd, copy_code, 0);
+    primitive_tex("unvbox", un_vbox_cmd, box_code, 0);
+    primitive_tex("unvcopy", un_vbox_cmd, copy_code, 0);
+    primitive_tex("-", discretionary_cmd, explicit_disc, 0);
+    primitive_tex("discretionary", discretionary_cmd, discretionary_disc, 0);
+    primitive_luatex("localleftbox", assign_local_box_cmd, 0, 0);
+    primitive_luatex("localrightbox", assign_local_box_cmd, 1, 0);
+
+    primitive_luatex("Ustartmath", math_shift_cs_cmd, text_style, 0);
+    primitive_luatex("Ustopmath", math_shift_cs_cmd, cramped_text_style, 0);
+    primitive_luatex("Ustartdisplaymath", math_shift_cs_cmd, display_style, 0);
+    primitive_luatex("Ustopdisplaymath", math_shift_cs_cmd, cramped_display_style, 0);
+    primitive_tex("eqno", eq_no_cmd, 0, 0);
+    primitive_tex("leqno", eq_no_cmd, 1, 0);
+    primitive_tex("mathord", math_comp_cmd, ord_noad_type, 0);
+    primitive_tex("mathop", math_comp_cmd, op_noad_type_normal, 0);
+    primitive_tex("mathbin", math_comp_cmd, bin_noad_type, 0);
+    primitive_tex("mathrel", math_comp_cmd, rel_noad_type, 0);
+    primitive_tex("mathopen", math_comp_cmd, open_noad_type, 0);
+    primitive_tex("mathclose", math_comp_cmd, close_noad_type, 0);
+    primitive_tex("mathpunct", math_comp_cmd, punct_noad_type, 0);
+    primitive_tex("mathinner", math_comp_cmd, inner_noad_type, 0);
+    primitive_tex("underline", math_comp_cmd, under_noad_type, 0);
+    primitive_tex("overline", math_comp_cmd, over_noad_type, 0);
+    primitive_tex("displaylimits", limit_switch_cmd, op_noad_type_normal, 0);
+    primitive_tex("limits", limit_switch_cmd, op_noad_type_limits, 0);
+    primitive_tex("nolimits", limit_switch_cmd, op_noad_type_no_limits, 0);
+    primitive_tex("displaystyle", math_style_cmd, display_style, 0);
+    primitive_tex("textstyle", math_style_cmd, text_style, 0);
+    primitive_tex("scriptstyle", math_style_cmd, script_style, 0);
+    primitive_tex("scriptscriptstyle", math_style_cmd, script_script_style, 0);
+    primitive_luatex("crampeddisplaystyle", math_style_cmd, cramped_display_style, 0);
+    primitive_luatex("crampedtextstyle", math_style_cmd, cramped_text_style, 0);
+    primitive_luatex("crampedscriptstyle", math_style_cmd, cramped_script_style, 0);
+    primitive_luatex("crampedscriptscriptstyle", math_style_cmd, cramped_script_script_style, 0);
+    primitive_luatex("Usuperscript", super_sub_script_cmd, sup_mark_cmd, sup_mark_cmd);
+    primitive_luatex("Usubscript", super_sub_script_cmd, sub_mark_cmd, sup_mark_cmd);
+    primitive_tex("above", above_cmd, above_code, 0);
+    primitive_tex("over", above_cmd, over_code, 0);
+    primitive_tex("atop", above_cmd, atop_code, 0);
+    primitive_luatex("Uskewed", above_cmd, skewed_code, 0);
+    primitive_tex("abovewithdelims", above_cmd, delimited_code + above_code, 0);
+    primitive_tex("overwithdelims", above_cmd, delimited_code + over_code, 0);
+    primitive_tex("atopwithdelims", above_cmd, delimited_code + atop_code, 0);
+    primitive_luatex("Uskewedwithdelims", above_cmd, delimited_code + skewed_code, 0);
+    primitive_tex("left", left_right_cmd, left_noad_side, 0);
+    primitive_tex("right", left_right_cmd, right_noad_side, 0);
+    primitive_tex("middle", left_right_cmd, middle_noad_side, 0);
+    primitive_tex("Uleft", left_right_cmd, 10+left_noad_side, 0);
+    primitive_tex("Uright", left_right_cmd, 10+right_noad_side, 0);
+    primitive_tex("Umiddle", left_right_cmd, 10+middle_noad_side, 0);
+    primitive_luatex("Uvextensible", left_right_cmd, 10+no_noad_side, 0);
+    cs_text(frozen_right) = maketexstring("right");
+    eqtb[frozen_right] = eqtb[cur_val];
+
+    primitive_tex("long", prefix_cmd, 1, 0);
+    primitive_tex("outer", prefix_cmd, 2, 0);
+    primitive_tex("global", prefix_cmd, 4, 0);
+    primitive_tex("def", def_cmd, 0, 0);
+    primitive_tex("gdef", def_cmd, 1, 0);
+    primitive_tex("edef", def_cmd, 2, 0);
+    primitive_tex("xdef", def_cmd, 3, 0);
+    primitive_tex("let", let_cmd, normal, 0);
+    primitive_tex("futurelet", let_cmd, normal + 1, 0);
+    primitive_luatex("letcharcode", let_cmd, normal + 2, 0);
+    primitive_tex("chardef", shorthand_def_cmd, char_def_code, 0);
+    primitive_tex("mathchardef", shorthand_def_cmd, math_char_def_code, 0);
+    primitive_luatex("Umathchardef", shorthand_def_cmd, xmath_char_def_code, 0);
+    primitive_luatex("Umathcharnumdef", shorthand_def_cmd, umath_char_def_code, 0);
+    primitive_tex("countdef", shorthand_def_cmd, count_def_code, 0);
+    primitive_luatex("attributedef", shorthand_def_cmd, attribute_def_code, 0);
+    primitive_tex("dimendef", shorthand_def_cmd, dimen_def_code, 0);
+    primitive_tex("skipdef", shorthand_def_cmd, skip_def_code, 0);
+    primitive_tex("muskipdef", shorthand_def_cmd, mu_skip_def_code, 0);
+    primitive_tex("toksdef", shorthand_def_cmd, toks_def_code, 0);
+    primitive_tex("catcode", def_char_code_cmd, cat_code_base, cat_code_base);
+    primitive_tex("mathcode", def_char_code_cmd, math_code_base, cat_code_base);
+    primitive_tex("lccode", def_char_code_cmd, lc_code_base, cat_code_base);
+    primitive_tex("uccode", def_char_code_cmd, uc_code_base, cat_code_base);
+    primitive_tex("sfcode", def_char_code_cmd, sf_code_base, cat_code_base);
+    primitive_tex("delcode", def_del_code_cmd, del_code_base, del_code_base);
+    primitive_tex("textfont", def_family_cmd, text_size, 0);
+    primitive_tex("scriptfont", def_family_cmd, script_size, 0);
+    primitive_tex("scriptscriptfont", def_family_cmd, script_script_size, 0);
+    primitive_luatex("Umathquad", set_math_param_cmd, math_param_quad, 0);
+    primitive_luatex("Umathaxis", set_math_param_cmd, math_param_axis, 0);
+
+@ These are in a separate module due to a CWEAVE limitation.
+
+@<Create the math param primitives@>=
+    primitive_luatex("Umathoperatorsize", set_math_param_cmd, math_param_operator_size, 0);
+    primitive_luatex("Umathoverbarkern", set_math_param_cmd, math_param_overbar_kern, 0);
+    primitive_luatex("Umathoverbarrule", set_math_param_cmd, math_param_overbar_rule, 0);
+    primitive_luatex("Umathoverbarvgap", set_math_param_cmd, math_param_overbar_vgap, 0);
+    primitive_luatex("Umathunderbarkern", set_math_param_cmd, math_param_underbar_kern, 0);
+    primitive_luatex("Umathunderbarrule", set_math_param_cmd, math_param_underbar_rule, 0);
+    primitive_luatex("Umathunderbarvgap", set_math_param_cmd, math_param_underbar_vgap, 0);
+    primitive_luatex("Umathradicalkern", set_math_param_cmd, math_param_radical_kern, 0);
+    primitive_luatex("Umathradicalrule", set_math_param_cmd, math_param_radical_rule, 0);
+    primitive_luatex("Umathradicalvgap", set_math_param_cmd, math_param_radical_vgap, 0);
+    primitive_luatex("Umathradicaldegreebefore", set_math_param_cmd, math_param_radical_degree_before, 0);
+    primitive_luatex("Umathradicaldegreeafter", set_math_param_cmd, math_param_radical_degree_after, 0);
+    primitive_luatex("Umathradicaldegreeraise", set_math_param_cmd, math_param_radical_degree_raise, 0);
+    primitive_luatex("Umathstackvgap", set_math_param_cmd, math_param_stack_vgap, 0);
+    primitive_luatex("Umathstacknumup", set_math_param_cmd, math_param_stack_num_up, 0);
+    primitive_luatex("Umathstackdenomdown", set_math_param_cmd, math_param_stack_denom_down, 0);
+    primitive_luatex("Umathfractionrule", set_math_param_cmd, math_param_fraction_rule, 0);
+    primitive_luatex("Umathfractionnumvgap", set_math_param_cmd, math_param_fraction_num_vgap, 0);
+    primitive_luatex("Umathfractionnumup", set_math_param_cmd, math_param_fraction_num_up, 0);
+    primitive_luatex("Umathfractiondenomvgap", set_math_param_cmd, math_param_fraction_denom_vgap, 0);
+    primitive_luatex("Umathfractiondenomdown", set_math_param_cmd, math_param_fraction_denom_down, 0);
+    primitive_luatex("Umathfractiondelsize", set_math_param_cmd, math_param_fraction_del_size, 0);
+    primitive_luatex("Umathskewedfractionvgap", set_math_param_cmd, math_param_skewed_fraction_vgap, 0);
+    primitive_luatex("Umathskewedfractionhgap", set_math_param_cmd, math_param_skewed_fraction_hgap, 0);
+    primitive_luatex("Umathlimitabovevgap", set_math_param_cmd, math_param_limit_above_vgap, 0);
+    primitive_luatex("Umathlimitabovebgap", set_math_param_cmd, math_param_limit_above_bgap, 0);
+    primitive_luatex("Umathlimitabovekern", set_math_param_cmd, math_param_limit_above_kern, 0);
+    primitive_luatex("Umathlimitbelowvgap", set_math_param_cmd, math_param_limit_below_vgap, 0);
+    primitive_luatex("Umathlimitbelowbgap", set_math_param_cmd, math_param_limit_below_bgap, 0);
+    primitive_luatex("Umathlimitbelowkern", set_math_param_cmd, math_param_limit_below_kern, 0);
+    primitive_luatex("Umathnolimitsubfactor", set_math_param_cmd, math_param_nolimit_sub_factor, 0); /* bonus */
+    primitive_luatex("Umathnolimitsupfactor", set_math_param_cmd, math_param_nolimit_sup_factor, 0); /* bonus */
+    primitive_luatex("Umathunderdelimitervgap", set_math_param_cmd, math_param_under_delimiter_vgap, 0);
+    primitive_luatex("Umathunderdelimiterbgap", set_math_param_cmd, math_param_under_delimiter_bgap, 0);
+    primitive_luatex("Umathoverdelimitervgap", set_math_param_cmd, math_param_over_delimiter_vgap, 0);
+    primitive_luatex("Umathoverdelimiterbgap", set_math_param_cmd, math_param_over_delimiter_bgap, 0);
+    primitive_luatex("Umathsubshiftdrop", set_math_param_cmd, math_param_sub_shift_drop, 0);
+    primitive_luatex("Umathsupshiftdrop", set_math_param_cmd, math_param_sup_shift_drop, 0);
+    primitive_luatex("Umathsubshiftdown", set_math_param_cmd, math_param_sub_shift_down, 0);
+    primitive_luatex("Umathsubsupshiftdown", set_math_param_cmd, math_param_sub_sup_shift_down, 0);
+    primitive_luatex("Umathsubtopmax", set_math_param_cmd, math_param_sub_top_max, 0);
+    primitive_luatex("Umathsupshiftup", set_math_param_cmd, math_param_sup_shift_up, 0);
+    primitive_luatex("Umathsupbottommin", set_math_param_cmd, math_param_sup_bottom_min, 0);
+    primitive_luatex("Umathsupsubbottommax", set_math_param_cmd, math_param_sup_sub_bottom_max, 0);
+    primitive_luatex("Umathsubsupvgap", set_math_param_cmd, math_param_subsup_vgap, 0);
+    primitive_luatex("Umathspaceafterscript", set_math_param_cmd, math_param_space_after_script, 0);
+    primitive_luatex("Umathconnectoroverlapmin", set_math_param_cmd, math_param_connector_overlap_min, 0);
+    primitive_luatex("Umathordordspacing", set_math_param_cmd, math_param_ord_ord_spacing, 0);
+    primitive_luatex("Umathordopspacing", set_math_param_cmd, math_param_ord_op_spacing, 0);
+    primitive_luatex("Umathordbinspacing", set_math_param_cmd, math_param_ord_bin_spacing, 0);
+    primitive_luatex("Umathordrelspacing", set_math_param_cmd, math_param_ord_rel_spacing, 0);
+    primitive_luatex("Umathordopenspacing", set_math_param_cmd, math_param_ord_open_spacing, 0);
+    primitive_luatex("Umathordclosespacing", set_math_param_cmd, math_param_ord_close_spacing, 0);
+    primitive_luatex("Umathordpunctspacing", set_math_param_cmd, math_param_ord_punct_spacing, 0);
+    primitive_luatex("Umathordinnerspacing", set_math_param_cmd, math_param_ord_inner_spacing, 0);
+    primitive_luatex("Umathopordspacing", set_math_param_cmd, math_param_op_ord_spacing, 0);
+    primitive_luatex("Umathopopspacing", set_math_param_cmd, math_param_op_op_spacing, 0);
+    primitive_luatex("Umathopbinspacing", set_math_param_cmd, math_param_op_bin_spacing, 0);
+    primitive_luatex("Umathoprelspacing", set_math_param_cmd, math_param_op_rel_spacing, 0);
+    primitive_luatex("Umathopopenspacing", set_math_param_cmd, math_param_op_open_spacing, 0);
+    primitive_luatex("Umathopclosespacing", set_math_param_cmd, math_param_op_close_spacing, 0);
+    primitive_luatex("Umathoppunctspacing", set_math_param_cmd, math_param_op_punct_spacing, 0);
+    primitive_luatex("Umathopinnerspacing", set_math_param_cmd, math_param_op_inner_spacing, 0);
+    primitive_luatex("Umathbinordspacing", set_math_param_cmd, math_param_bin_ord_spacing, 0);
+    primitive_luatex("Umathbinopspacing", set_math_param_cmd, math_param_bin_op_spacing, 0);
+    primitive_luatex("Umathbinbinspacing", set_math_param_cmd, math_param_bin_bin_spacing, 0);
+    primitive_luatex("Umathbinrelspacing", set_math_param_cmd, math_param_bin_rel_spacing, 0);
+    primitive_luatex("Umathbinopenspacing", set_math_param_cmd, math_param_bin_open_spacing, 0);
+    primitive_luatex("Umathbinclosespacing", set_math_param_cmd, math_param_bin_close_spacing, 0);
+    primitive_luatex("Umathbinpunctspacing", set_math_param_cmd, math_param_bin_punct_spacing, 0);
+    primitive_luatex("Umathbininnerspacing", set_math_param_cmd, math_param_bin_inner_spacing, 0);
+    primitive_luatex("Umathrelordspacing", set_math_param_cmd, math_param_rel_ord_spacing, 0);
+    primitive_luatex("Umathrelopspacing", set_math_param_cmd, math_param_rel_op_spacing, 0);
+    primitive_luatex("Umathrelbinspacing", set_math_param_cmd, math_param_rel_bin_spacing, 0);
+    primitive_luatex("Umathrelrelspacing", set_math_param_cmd, math_param_rel_rel_spacing, 0);
+    primitive_luatex("Umathrelopenspacing", set_math_param_cmd, math_param_rel_open_spacing, 0);
+    primitive_luatex("Umathrelclosespacing", set_math_param_cmd, math_param_rel_close_spacing, 0);
+    primitive_luatex("Umathrelpunctspacing", set_math_param_cmd, math_param_rel_punct_spacing, 0);
+    primitive_luatex("Umathrelinnerspacing", set_math_param_cmd, math_param_rel_inner_spacing, 0);
+    primitive_luatex("Umathopenordspacing", set_math_param_cmd, math_param_open_ord_spacing, 0);
+    primitive_luatex("Umathopenopspacing", set_math_param_cmd, math_param_open_op_spacing, 0);
+    primitive_luatex("Umathopenbinspacing", set_math_param_cmd, math_param_open_bin_spacing, 0);
+    primitive_luatex("Umathopenrelspacing", set_math_param_cmd, math_param_open_rel_spacing, 0);
+    primitive_luatex("Umathopenopenspacing", set_math_param_cmd, math_param_open_open_spacing, 0);
+    primitive_luatex("Umathopenclosespacing", set_math_param_cmd, math_param_open_close_spacing, 0);
+    primitive_luatex("Umathopenpunctspacing", set_math_param_cmd, math_param_open_punct_spacing, 0);
+    primitive_luatex("Umathopeninnerspacing", set_math_param_cmd, math_param_open_inner_spacing, 0);
+    primitive_luatex("Umathcloseordspacing", set_math_param_cmd, math_param_close_ord_spacing, 0);
+    primitive_luatex("Umathcloseopspacing", set_math_param_cmd, math_param_close_op_spacing, 0);
+    primitive_luatex("Umathclosebinspacing", set_math_param_cmd, math_param_close_bin_spacing, 0);
+    primitive_luatex("Umathcloserelspacing", set_math_param_cmd, math_param_close_rel_spacing, 0);
+    primitive_luatex("Umathcloseopenspacing", set_math_param_cmd, math_param_close_open_spacing, 0);
+    primitive_luatex("Umathcloseclosespacing", set_math_param_cmd, math_param_close_close_spacing, 0);
+    primitive_luatex("Umathclosepunctspacing", set_math_param_cmd, math_param_close_punct_spacing, 0);
+    primitive_luatex("Umathcloseinnerspacing", set_math_param_cmd, math_param_close_inner_spacing, 0);
+    primitive_luatex("Umathpunctordspacing", set_math_param_cmd, math_param_punct_ord_spacing, 0);
+    primitive_luatex("Umathpunctopspacing", set_math_param_cmd, math_param_punct_op_spacing, 0);
+    primitive_luatex("Umathpunctbinspacing", set_math_param_cmd, math_param_punct_bin_spacing, 0);
+    primitive_luatex("Umathpunctrelspacing", set_math_param_cmd, math_param_punct_rel_spacing, 0);
+    primitive_luatex("Umathpunctopenspacing", set_math_param_cmd, math_param_punct_open_spacing, 0);
+    primitive_luatex("Umathpunctclosespacing", set_math_param_cmd, math_param_punct_close_spacing, 0);
+    primitive_luatex("Umathpunctpunctspacing", set_math_param_cmd, math_param_punct_punct_spacing, 0);
+    primitive_luatex("Umathpunctinnerspacing", set_math_param_cmd, math_param_punct_inner_spacing, 0);
+    primitive_luatex("Umathinnerordspacing", set_math_param_cmd, math_param_inner_ord_spacing, 0);
+    primitive_luatex("Umathinneropspacing", set_math_param_cmd, math_param_inner_op_spacing, 0);
+    primitive_luatex("Umathinnerbinspacing", set_math_param_cmd, math_param_inner_bin_spacing, 0);
+    primitive_luatex("Umathinnerrelspacing", set_math_param_cmd, math_param_inner_rel_spacing, 0);
+    primitive_luatex("Umathinneropenspacing", set_math_param_cmd, math_param_inner_open_spacing, 0);
+    primitive_luatex("Umathinnerclosespacing", set_math_param_cmd, math_param_inner_close_spacing, 0);
+    primitive_luatex("Umathinnerpunctspacing", set_math_param_cmd, math_param_inner_punct_spacing, 0);
+    primitive_luatex("Umathinnerinnerspacing", set_math_param_cmd, math_param_inner_inner_spacing, 0);
+
+@ These are in a separate module due to a CWEAVE limitation.
+
+@<Create another bunch of primitives@>=
+    primitive_luatex("Umathcode", extdef_math_code_cmd, math_code_base, math_code_base);
+    primitive_luatex("Udelcode", extdef_del_code_cmd, del_code_base, del_code_base);
+    primitive_luatex("Umathcodenum", extdef_math_code_cmd, math_code_base + 1, math_code_base);
+    primitive_luatex("Udelcodenum", extdef_del_code_cmd, del_code_base + 1, del_code_base);
+    primitive_tex("hyphenation", hyph_data_cmd, 0, 0);
+    primitive_tex("patterns", hyph_data_cmd, 1, 0);
+    primitive_luatex("prehyphenchar", hyph_data_cmd, 2, 0);
+    primitive_luatex("posthyphenchar", hyph_data_cmd, 3, 0);
+    primitive_luatex("preexhyphenchar", hyph_data_cmd, 4, 0);
+    primitive_luatex("postexhyphenchar", hyph_data_cmd, 5, 0);
+    primitive_luatex("hyphenationmin", hyph_data_cmd, 6, 0);
+    primitive_luatex("hjcode", hyph_data_cmd, 7, 0);
+    primitive_tex("hyphenchar", assign_font_int_cmd, 0, 0);
+    primitive_tex("skewchar", assign_font_int_cmd, 1, 0);
+    primitive_luatex("lpcode", assign_font_int_cmd, lp_code_base, 0);
+    primitive_luatex("rpcode", assign_font_int_cmd, rp_code_base, 0);
+    primitive_luatex("efcode", assign_font_int_cmd, ef_code_base, 0);
+    primitive_luatex("tagcode", assign_font_int_cmd, tag_code, 0);
+    primitive_luatex("ignoreligaturesinfont", assign_font_int_cmd, no_lig_code, 0);
+    primitive_tex("batchmode", set_interaction_cmd, batch_mode, 0);
+    primitive_tex("nonstopmode", set_interaction_cmd, nonstop_mode, 0);
+    primitive_tex("scrollmode", set_interaction_cmd, scroll_mode, 0);
+    primitive_tex("errorstopmode", set_interaction_cmd, error_stop_mode, 0);
+    primitive_tex("openin", in_stream_cmd, 1, 0);
+    primitive_tex("closein", in_stream_cmd, 0, 0);
+    primitive_tex("message", message_cmd, 0, 0);
+    primitive_tex("errmessage", message_cmd, 1, 0);
+    primitive_tex("lowercase", case_shift_cmd, lc_code_base, lc_code_base);
+    primitive_tex("uppercase", case_shift_cmd, uc_code_base, lc_code_base);
+    primitive_tex("show", xray_cmd, show_code, 0);
+    primitive_tex("showbox", xray_cmd, show_box_code, 0);
+    primitive_tex("showthe", xray_cmd, show_the_code, 0);
+    primitive_tex("showlists", xray_cmd, show_lists, 0);
+
+    primitive_tex("openout", extension_cmd, open_code, 0);
+    primitive_tex("write", extension_cmd, write_code, 0);
+    write_loc = cur_val;
+    primitive_tex("closeout", extension_cmd, close_code, 0);
+    primitive_tex("special", extension_cmd, special_code, 0);
+    cs_text(frozen_special) = maketexstring("special");
+    eqtb[frozen_special] = eqtb[cur_val];
+    primitive_tex("immediate", extension_cmd, immediate_code, 0);
+    primitive_luatex("localinterlinepenalty", assign_int_cmd, int_base + local_inter_line_penalty_code, int_base);
+    primitive_luatex("localbrokenpenalty", assign_int_cmd, int_base + local_broken_penalty_code, int_base);
+    primitive_luatex("pagedir", assign_dir_cmd, int_base + page_direction_code, dir_base);
+    primitive_luatex("bodydir", assign_dir_cmd, int_base + body_direction_code, dir_base);
+    primitive_luatex("pardir", assign_dir_cmd, int_base + par_direction_code, dir_base);
+    primitive_luatex("textdir", assign_dir_cmd, int_base + text_direction_code, dir_base);
+    primitive_luatex("mathdir", assign_dir_cmd, int_base + math_direction_code, dir_base);
+    primitive_luatex("linedir", assign_dir_cmd, int_base + line_direction_code, dir_base);
+    primitive_luatex("pageleftoffset", assign_dimen_cmd, dimen_base + page_left_offset_code, dimen_base);
+    primitive_luatex("pagetopoffset", assign_dimen_cmd, dimen_base + page_top_offset_code, dimen_base);
+    primitive_luatex("pagerightoffset", assign_dimen_cmd, dimen_base + page_right_offset_code, dimen_base);
+    primitive_luatex("pagebottomoffset", assign_dimen_cmd, dimen_base + page_bottom_offset_code, dimen_base);
+    primitive_luatex("saveboxresource", extension_cmd, save_box_resource_code, 0);
+    primitive_luatex("useboxresource", extension_cmd, use_box_resource_code, 0);
+    primitive_luatex("saveimageresource", extension_cmd, save_image_resource_code, 0);
+    primitive_luatex("useimageresource", extension_cmd, use_image_resource_code, 0);
+    primitive_luatex("savepos", normal_cmd, save_pos_code, 0);
+    primitive_luatex("savecatcodetable", normal_cmd, save_cat_code_table_code, 0);
+    primitive_luatex("initcatcodetable", normal_cmd, init_cat_code_table_code, 0);
+    primitive_luatex("setrandomseed", normal_cmd, set_random_seed_code, 0);
+    primitive_luatex("latelua", normal_cmd, late_lua_code, 0);
+    primitive_luatex("insertht", convert_cmd, insert_ht_code, 0);
+    primitive_luatex("dviextension", extension_cmd, dvi_extension_code, 0);
+    primitive_luatex("dvifeedback", feedback_cmd, dvi_feedback_code, 0);
+    primitive_luatex("dvivariable", variable_cmd, dvi_variable_code, 0);
+    primitive_luatex("pdfextension", extension_cmd, pdf_extension_code, 0);
+    primitive_luatex("pdffeedback", feedback_cmd, pdf_feedback_code, 0);
+    primitive_luatex("pdfvariable", variable_cmd, pdf_variable_code, 0);
+    primitive_luatex("mathoption", option_cmd, math_option_code, 0);
+
+    /*
+        some of the internal integer parameters are not associated with actual
+        primitives at all.
+    */
+
+    primitive_no("nolocalwhatsits", assign_int_cmd, int_base + no_local_whatsits_code, int_base);
+    primitive_no("nolocaldirs", assign_int_cmd, int_base + no_local_dirs_code, int_base);
+
+
+@ @c
+void initialize_etex_commands(void)
+{
+    primitive_etex("lastnodetype", last_item_cmd, last_node_type_code, 0);
+    primitive_etex("eTeXversion", last_item_cmd, eTeX_version_code, 0);
+    primitive_etex("eTeXminorversion", last_item_cmd, eTeX_minor_version_code, 0);
+    primitive_etex("eTeXrevision", convert_cmd, eTeX_revision_code, 0);
+
+    /*
+        First we implement the additional \eTeX\ parameters in the table of equivalents.
+    */
+
+    primitive_etex("everyeof", assign_toks_cmd, every_eof_loc, local_base);
+    primitive_etex("tracingassigns", assign_int_cmd, int_base + tracing_assigns_code, int_base);
+    primitive_etex("tracinggroups", assign_int_cmd, int_base + tracing_groups_code, int_base);
+    primitive_etex("tracingifs", assign_int_cmd, int_base + tracing_ifs_code, int_base);
+    primitive_etex("tracingscantokens", assign_int_cmd, int_base + tracing_scan_tokens_code, int_base);
+    primitive_etex("tracingnesting", assign_int_cmd, int_base + tracing_nesting_code, int_base);
+    primitive_etex("predisplaydirection", assign_int_cmd, int_base + pre_display_direction_code, int_base);
+    primitive_etex("lastlinefit", assign_int_cmd, int_base + last_line_fit_code, int_base);
+    primitive_etex("savingvdiscards", assign_int_cmd, int_base + saving_vdiscards_code, int_base);
+    primitive_etex("savinghyphcodes", assign_int_cmd, int_base + saving_hyph_codes_code, int_base);
+    primitive_luatex("suppressfontnotfounderror", assign_int_cmd, int_base + suppress_fontnotfound_error_code, int_base);
+    primitive_luatex("suppresslongerror", assign_int_cmd, int_base + suppress_long_error_code, int_base);
+    primitive_luatex("suppressmathparerror", assign_int_cmd, int_base + suppress_mathpar_error_code, int_base);
+    primitive_luatex("suppressifcsnameerror", assign_int_cmd, int_base + suppress_ifcsname_error_code, int_base);
+    primitive_luatex("suppressoutererror", assign_int_cmd, int_base + suppress_outer_error_code, int_base);
+    primitive_luatex("matheqnogapstep", assign_int_cmd, int_base + math_eqno_gap_step_code, int_base);
+    primitive_luatex("mathdisplayskipmode", assign_int_cmd, int_base + math_display_skip_mode_code, int_base);
+    primitive_luatex("mathscriptsmode", assign_int_cmd, int_base + math_scripts_mode_code, int_base);
+    primitive_luatex("mathnolimitsmode", assign_int_cmd, int_base + math_nolimits_mode_code, int_base);
+    primitive_luatex("mathitalicsmode", assign_int_cmd, int_base + math_italics_mode_code, int_base);
+    primitive_luatex("mathrulesmode", assign_int_cmd, int_base + math_rules_mode_code, int_base);
+    primitive_luatex("mathrulesfam", assign_int_cmd, int_base + math_rules_fam_code, int_base);
+    primitive_luatex("synctex", assign_int_cmd, int_base + synctex_code, int_base);
+
+    primitive_etex("currentgrouplevel", last_item_cmd, current_group_level_code, 0);
+    primitive_etex("currentgrouptype", last_item_cmd, current_group_type_code, 0);
+
+    primitive_etex("currentiflevel", last_item_cmd, current_if_level_code, 0);
+    primitive_etex("currentiftype", last_item_cmd, current_if_type_code, 0);
+    primitive_etex("currentifbranch", last_item_cmd, current_if_branch_code, 0);
+    primitive_etex("fontcharwd", last_item_cmd, font_char_wd_code, 0);
+    primitive_etex("fontcharht", last_item_cmd, font_char_ht_code, 0);
+    primitive_etex("fontchardp", last_item_cmd, font_char_dp_code, 0);
+    primitive_etex("fontcharic", last_item_cmd, font_char_ic_code, 0);
+
+    primitive_etex("parshapelength", last_item_cmd, par_shape_length_code, 0);
+    primitive_etex("parshapeindent", last_item_cmd, par_shape_indent_code, 0);
+    primitive_etex("parshapedimen", last_item_cmd, par_shape_dimen_code, 0);
+
+    primitive_luatex("shapemode", assign_int_cmd, int_base + shape_mode_code, int_base);
+    primitive_luatex("hyphenationbounds", assign_int_cmd, int_base + hyphenation_bounds_code, int_base);
+
+    primitive_etex("showgroups", xray_cmd, show_groups, 0);
+
+    /*
+        The \.{\\showtokens} command displays a token list.
+    */
+
+    primitive_etex("showtokens", xray_cmd, show_tokens, 0);
+
+    /*
+        The \.{\\unexpanded} primitive prevents expansion of tokens much as
+        the result from \.{\\the} applied to a token variable.  The
+        \.{\\detokenize} primitive converts a token list into a list of
+        character tokens much as if the token list were written to a file.  We
+        use the fact that the command modifiers for \.{\\unexpanded} and
+        \.{\\detokenize} are odd whereas those for \.{\\the} and \.{\\showthe}
+        are even.
+    */
+
+    primitive_etex("unexpanded", the_cmd, 1, 0);
+    primitive_etex("detokenize", the_cmd, show_tokens, 0);
+
+    /*
+        The \.{\\showifs} command displays all currently active conditionals.
+    */
+
+    primitive_etex("showifs", xray_cmd, show_ifs, 0);
+
+    /*
+        The \.{\\interactionmode} primitive allows to query and set the interaction mode.
+    */
+
+    primitive_etex("interactionmode", set_page_int_cmd, 2, 0);
+
+    /*
+        The |scan_tokens| feature of \eTeX\ defines the \.{\\scantokens} primitive.
+    */
+
+    primitive_etex("scantokens", input_cmd, 2, 0);
+    primitive_luatex("scantextokens", input_cmd, 3, 0);
+
+    primitive_etex("readline", read_to_cs_cmd, 1, 0);
+
+    primitive_etex("unless", expand_after_cmd, 1, 0);
+    primitive_etex("ifdefined", if_test_cmd, if_def_code, 0);
+    primitive_etex("ifcsname", if_test_cmd, if_cs_code, 0);
+    primitive_etex("iffontchar", if_test_cmd, if_font_char_code, 0);
+    primitive_luatex("ifincsname", if_test_cmd, if_in_csname_code, 0);
+    primitive_luatex("ifabsnum", if_test_cmd, if_abs_num_code, 0);
+    primitive_luatex("ifabsdim", if_test_cmd, if_abs_dim_code, 0);
+
+    /*
+        The |protected| feature of \eTeX\ defines the \.{\\protected} prefix
+        command for macro definitions.  Such macros are protected against
+        expansions when lists of expanded tokens are built, e.g., for \.{\\edef}
+        or during \.{\\write}.
+    */
+
+    primitive_etex("protected", prefix_cmd, 8, 0);
+
+    /*
+        Here are the additional \eTeX\ primitives for expressions.
+    */
+
+    primitive_etex("numexpr", last_item_cmd, eTeX_expr - int_val_level + int_val_level, 0);
+    primitive_etex("dimexpr", last_item_cmd, eTeX_expr - int_val_level + dimen_val_level, 0);
+    primitive_etex("glueexpr", last_item_cmd, eTeX_expr - int_val_level + glue_val_level, 0);
+    primitive_etex("muexpr", last_item_cmd, eTeX_expr - int_val_level + mu_val_level, 0);
+
+    primitive_etex("gluestretchorder", last_item_cmd, glue_stretch_order_code, 0);
+    primitive_etex("glueshrinkorder", last_item_cmd, glue_shrink_order_code, 0);
+    primitive_etex("gluestretch", last_item_cmd, glue_stretch_code, 0);
+    primitive_etex("glueshrink", last_item_cmd, glue_shrink_code, 0);
+
+    primitive_etex("mutoglue", last_item_cmd, mu_to_glue_code, 0);
+    primitive_etex("gluetomu", last_item_cmd, glue_to_mu_code, 0);
+
+    /*
+        The \.{\\pagediscards} and \.{\\splitdiscards} commands share the
+        command code |un_vbox| with \.{\\unvbox} and \.{\\unvcopy}, they are
+        distinguished by their |chr_code| values |last_box_code| and
+        |vsplit_code|.  These |chr_code| values are larger than |box_code| and
+        |copy_code|.
+    */
+
+    primitive_etex("pagediscards", un_vbox_cmd, last_box_code, 0);
+    primitive_etex("splitdiscards", un_vbox_cmd, vsplit_code, 0);
+
+    /*
+        The \.{\\interlinepenalties}, \.{\\clubpenalties}, \.{\\widowpenalties},
+        and \.{\\displaywidowpenalties} commands allow to define arrays of
+        penalty values to be used instead of the corresponding single values.
+    */
+
+    primitive_etex("interlinepenalties", set_etex_shape_cmd, inter_line_penalties_loc, etex_pen_base);
+    primitive_etex("clubpenalties", set_etex_shape_cmd, club_penalties_loc, etex_pen_base);
+    primitive_etex("widowpenalties", set_etex_shape_cmd, widow_penalties_loc, etex_pen_base);
+    primitive_etex("displaywidowpenalties", set_etex_shape_cmd, display_widow_penalties_loc, etex_pen_base);
+
+}

Modified: trunk/Build/source/texk/web2c/luatexdir/tex/dumpdata.w
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/tex/dumpdata.w	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/tex/dumpdata.w	2017-03-11 00:31:06 UTC (rev 43454)
@@ -1,526 +1,526 @@
-% dumpdata.w
-%
-% Copyright 2009-2010 Taco Hoekwater <taco@@luatex.org>
-%
-% 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 <http://www.gnu.org/licenses/>.
-
-@ @c
-
-#include "ptexlib.h"
-
-/* we start with 907: the sum of the values of the bytes of "don knuth" */
-
-#define FORMAT_ID (907+25)
-#if ((FORMAT_ID>=0) && (FORMAT_ID<=256))
-#error Wrong value for FORMAT_ID.
-#endif
-
-
-@ After \.{INITEX} has seen a collection of fonts and macros, it
-can write all the necessary information on an auxiliary file so
-that production versions of \TeX\ are able to initialize their
-memory at high speed. The present section of the program takes
-care of such output and input. We shall consider simultaneously
-the processes of storing and restoring,
-so that the inverse relation between them is clear.
- at .INITEX@>
-
-The global variable |format_ident| is a string that is printed right
-after the |banner| line when \TeX\ is ready to start. For \.{INITEX} this
-string says simply `\.{(INITEX)}'; for other versions of \TeX\ it says,
-for example, `\.{(preloaded format=plain 1982.11.19)}', showing the year,
-month, and day that the format file was created. We have |format_ident=0|
-before \TeX's tables are loaded. |FORMAT_ID| is a new field of type int
-suitable for the identification of a format: values between 0 and 256
-(included) can not be used because in the previous format they are used
-for the length of  the name of the engine.
- at c
-str_number format_ident;
-str_number format_name;         /* principal file name */
-
-
-@ Format files consist of |memory_word| items, and we use the following
-macros to dump words of different types:
-
- at c
-FILE *fmt_file;                 /* for input or output of format information */
-
-@ @c
-void store_fmt_file(void)
-{
-    int j, k, l;                /* all-purpose indices */
-    halfword p;                 /* all-purpose pointer */
-    int x;                      /* something to dump */
-    char *format_engine;
-    int callback_id;            /* |pre_dump| callback */
-    char *fmtname = NULL;
-    /* If dumping is not allowed, abort */
-    /* The user is not allowed to dump a format file unless |save_ptr=0|.
-       This condition implies that |cur_level=level_one|, hence
-       the |xeq_level| array is constant and it need not be dumped. */
-    if (save_ptr != 0) {
-        print_err("You can't dump inside a group");
-        help1("`{...\\dump}' is a no-no.");
-        succumb();
-    }
-
-    /* Create the |format_ident|, open the format file, and inform the user
-       that dumping has begun */
-    callback_id = callback_defined(pre_dump_callback);
-    if (callback_id > 0) {
-        (void) run_callback(callback_id, "->");
-    }
-    selector = new_string;
-    tprint(" (format=");
-    print(job_name);
-    print_char(' ');
-    print_int(year_par);
-    print_char('.');
-    print_int(month_par);
-    print_char('.');
-    print_int(day_par);
-    print_char(')');
-    str_room(2);
-    format_ident = make_string();
-    print(job_name);
-    format_name = make_string();
-    if (interaction == batch_mode)
-        selector = log_only;
-    else
-        selector = term_and_log;
-
-    fmtname = pack_job_name(format_extension);
-    while (!zopen_w_output(&fmt_file, fmtname, FOPEN_WBIN_MODE)) {
-        fmtname = prompt_file_name("format file name", format_extension);
-    }
-    tprint_nl("Beginning to dump on file ");
-    tprint(fmtname);
-    free(fmtname);
-    tprint_nl("");
-    print(format_ident);
-
-    /* Dump constants for consistency check */
-    /* The next few sections of the program should make it clear how we use the
-       dump/undump macros. */
-
-    dump_int(0x57325458);       /* Web2C \TeX's magic constant: "W2TX" */
-    dump_int(FORMAT_ID);
-
-    /* Align engine to 4 bytes with one or more trailing NUL */
-    x = (int) strlen(engine_name);
-    format_engine = xmalloc((unsigned) (x + 4));
-    strcpy(format_engine, engine_name);
-    for (k = x; k <= x + 3; k++)
-        format_engine[k] = 0;
-    x = x + 4 - (x % 4);
-    dump_int(x);
-    dump_things(format_engine[0], x);
-    xfree(format_engine);
-    dump_int(0x57325458);       /* TODO HM, what checksum would make sense? */
-    dump_int(max_halfword);
-    dump_int(hash_high);
-    dump_int(eqtb_size);
-    dump_int(hash_prime);
-
-    /* Dump the string pool */
-    k = dump_string_pool();
-    print_ln();
-    print_int(k);
-    tprint(" strings using ");
-    print_int((longinteger) pool_size);
-    tprint(" bytes");
-
-    /* Dump the dynamic memory */
-    /* By sorting the list of available spaces in the variable-size portion of
-       |mem|, we are usually able to get by without having to dump very much
-       of the dynamic memory.
-
-       We recompute |var_used| and |dyn_used|, so that \.{INITEX} dumps valid
-       information even when it has not been gathering statistics.
-     */
-    dump_node_mem();
-    dump_int(temp_token_head);
-    dump_int(hold_token_head);
-    dump_int(omit_template);
-    dump_int(null_list);
-    dump_int(backup_head);
-    dump_int(garbage);
-    x = (int) fix_mem_min;
-    dump_int(x);
-    x = (int) fix_mem_max;
-    dump_int(x);
-    x = (int) fix_mem_end;
-    dump_int(x);
-    dump_int(avail);
-    dyn_used = (int) fix_mem_end + 1;
-    dump_things(fixmem[fix_mem_min], fix_mem_end - fix_mem_min + 1);
-    x = x + (int) (fix_mem_end + 1 - fix_mem_min);
-    p = avail;
-    while (p != null) {
-        decr(dyn_used);
-        p = token_link(p);
-    }
-    dump_int(dyn_used);
-    print_ln();
-    print_int(x);
-    tprint(" memory locations dumped; current usage is ");
-    print_int(var_used);
-    print_char('&');
-    print_int(dyn_used);
-
-    /* Dump the table of equivalents */
-    /* Dump regions 1 to 4 of |eqtb| */
-    /*The table of equivalents usually contains repeated information, so we dump it
-       in compressed form: The sequence of $n+2$ values $(n,x_1,\ldots,x_n,m)$ in the
-       format file represents $n+m$ consecutive entries of |eqtb|, with |m| extra
-       copies of $x_n$, namely $(x_1,\ldots,x_n,x_n,\ldots,x_n)$.
-     */
-    k = null_cs;
-    do {
-        j = k;
-        while (j < int_base - 1) {
-            if ((equiv(j) == equiv(j + 1)) && (eq_type(j) == eq_type(j + 1)) &&
-                (eq_level(j) == eq_level(j + 1)))
-                goto FOUND1;
-            incr(j);
-        }
-        l = int_base;
-        goto DONE1;             /* |j=int_base-1| */
-      FOUND1:
-        incr(j);
-        l = j;
-        while (j < int_base - 1) {
-            if ((equiv(j) != equiv(j + 1)) || (eq_type(j) != eq_type(j + 1)) ||
-                (eq_level(j) != eq_level(j + 1)))
-                goto DONE1;
-            incr(j);
-        }
-      DONE1:
-        dump_int(l - k);
-        dump_things(eqtb[k], l - k);
-        k = j + 1;
-        dump_int(k - l);
-    } while (k != int_base);
-
-    /* Dump regions 5 and 6 of |eqtb| */
-    do {
-        j = k;
-        while (j < eqtb_size) {
-            if (eqtb[j].cint == eqtb[j + 1].cint)
-                goto FOUND2;
-            incr(j);
-        }
-        l = eqtb_size + 1;
-        goto DONE2;             /* |j=eqtb_size| */
-      FOUND2:
-        incr(j);
-        l = j;
-        while (j < eqtb_size) {
-            if (eqtb[j].cint != eqtb[j + 1].cint)
-                goto DONE2;
-            incr(j);
-        }
-      DONE2:
-        dump_int(l - k);
-        dump_things(eqtb[k], l - k);
-        k = j + 1;
-        dump_int(k - l);
-    } while (k <= eqtb_size);
-    if (hash_high > 0)
-        dump_things(eqtb[eqtb_size + 1], hash_high);    /* dump |hash_extra| part */
-
-    dump_int(par_loc);
-    dump_int(write_loc);
-    dump_math_codes();
-    dump_text_codes();
-    /* Dump the hash table */
-    /* A different scheme is used to compress the hash table, since its lower
-       region is usually sparse. When |text(p)<>0| for |p<=hash_used|, we output
-       two words, |p| and |hash[p]|. The hash table is, of course, densely packed
-       for |p>=hash_used|, so the remaining entries are output in a~block.
-     */
-    dump_primitives();
-    dump_int(hash_used);
-    cs_count = frozen_control_sequence - 1 - hash_used + hash_high;
-    for (p = hash_base; p <= hash_used; p++) {
-        if (cs_text(p) != 0) {
-            dump_int(p);
-            dump_hh(hash[p]);
-            incr(cs_count);
-        }
-    }
-    dump_things(hash[hash_used + 1],
-                undefined_control_sequence - 1 - hash_used);
-    if (hash_high > 0)
-        dump_things(hash[eqtb_size + 1], hash_high);
-    dump_int(cs_count);
-    print_ln();
-    print_int(cs_count);
-    tprint(" multiletter control sequences");
-
-    /* Dump the font information */
-    dump_int(max_font_id());
-    for (k = 0; k <= max_font_id(); k++) {
-        /* Dump the array info for internal font number |k| */
-        dump_font(k);
-        tprint_nl("\\font");
-        print_esc(font_id_text(k));
-        print_char('=');
-        tprint_file_name((unsigned char *) font_name(k),
-                         (unsigned char *) font_area(k), NULL);
-        if (font_size(k) != font_dsize(k)) {
-            tprint(" at ");
-            print_scaled(font_size(k));
-            tprint("pt");
-        }
-    }
-    print_ln();
-    print_int(max_font_id());
-    tprint(" preloaded font");
-    if (max_font_id() != 1)
-        print_char('s');
-    dump_math_data();
-
-    /* Dump the hyphenation tables */
-    dump_language_data();
-
-    /* Dump a couple more things and the closing check word */
-    dump_int(interaction);
-    dump_int(format_ident);
-    dump_int(format_name);
-    dump_int(69069);
-    /* We have already printed a lot of statistics, so we set |tracing_stats:=0|
-       to prevent them from appearing again. */
-    tracing_stats_par = 0;
-
-    /* Dump the lua bytecodes */
-    dump_luac_registers();
-
-    /* Close the format file */
-    zwclose(fmt_file);
-}
-
-@ Corresponding to the procedure that dumps a format file, we have a function
-that reads one in. The function returns |false| if the dumped format is
-incompatible with the present \TeX\ table sizes, etc.
-
- at c
-#define too_small(A) do {					\
-	wake_up_terminal();					\
-	wterm_cr();						\
-	fprintf(term_out,"---! Must increase the %s",(A));	\
-	goto BAD_FMT;						\
-    } while (0)
-
-@ The inverse macros are slightly more complicated, since we need to check
-the range of the values we are reading in. We say `|undump(a)(b)(x)|' to
-read an integer value |x| that is supposed to be in the range |a<=x<=b|.
-
- at c
-#define undump(A,B,C) do {						\
-	undump_int(x);							\
-	if (x<(A) || x>(B)) goto BAD_FMT;				\
-	else (C) = x;							\
-    } while (0)
-
-
-#define format_debug(A,B) do {					\
-	if (debug_format_file) {				\
-	    fprintf (stderr, "fmtdebug: %s=%d", (A), (int)(B));	\
-	}							\
-    } while (0)
-
-#define undump_size(A,B,C,D) do {					\
-	undump_int(x);							\
-	if (x<(A))  goto BAD_FMT;					\
-	if (x>(B))  too_small(C);					\
-	else format_debug (C,x);					\
-	(D) = x;							\
-    } while (0)
-
-
-@ @c
-boolean load_fmt_file(const char *fmtname)
-{
-    int j, k;                   /* all-purpose indices */
-    halfword p;                 /* all-purpose pointer */
-    int x;                      /* something undumped */
-    char *format_engine;
-    /* Undump constants for consistency check */
-    if (ini_version) {
-        libcfree(hash);
-        libcfree(eqtb);
-        libcfree(fixmem);
-        libcfree(varmem);
-    }
-    undump_int(x);
-    format_debug("format magic number", x);
-    if (x != 0x57325458)
-        goto BAD_FMT;           /* not a format file */
-
-    undump_int(x);
-    format_debug("format id", x);
-    if (x != FORMAT_ID)
-        goto BAD_FMT;           /* FORMAT_ID mismatch */
-
-    undump_int(x);
-    format_debug("engine name size", x);
-    if ((x < 0) || (x > 256))
-        goto BAD_FMT;           /* corrupted format file */
-
-    format_engine = xmalloc((unsigned) x);
-    undump_things(format_engine[0], x);
-    format_engine[x - 1] = 0;   /* force string termination, just in case */
-    if (strcmp(engine_name, format_engine)) {
-        wake_up_terminal();
-        wterm_cr();
-        fprintf(term_out, "---! %s was written by %s", fmtname, format_engine);
-        xfree(format_engine);
-        goto BAD_FMT;
-    }
-    xfree(format_engine);
-    undump_int(x);
-    format_debug("string pool checksum", x);
-    if (x != 0x57325458) {      /* todo: @@\$ *//* check that strings are the same */
-        wake_up_terminal();
-        wterm_cr();
-        fprintf(term_out, "---! %s was written by a different version",
-                fmtname);
-        goto BAD_FMT;
-    }
-    undump_int(x);
-    if (x != max_halfword)
-        goto BAD_FMT;           /* check |max_halfword| */
-    undump_int(hash_high);
-    if ((hash_high < 0) || (hash_high > sup_hash_extra))
-        goto BAD_FMT;
-    if (hash_extra < hash_high)
-        hash_extra = hash_high;
-    eqtb_top = eqtb_size + hash_extra;
-    if (hash_extra == 0)
-        hash_top = undefined_control_sequence;
-    else
-        hash_top = eqtb_top;
-    hash = xmallocarray(two_halves, (unsigned) (1 + hash_top));
-    memset(hash, 0, sizeof(two_halves) * (unsigned) (hash_top + 1));
-    eqtb = xmallocarray(memory_word, (unsigned) (eqtb_top + 1));
-    set_eq_type(undefined_control_sequence, undefined_cs_cmd);
-    set_equiv(undefined_control_sequence, null);
-    set_eq_level(undefined_control_sequence, level_zero);
-    for (x = eqtb_size + 1; x <= eqtb_top; x++)
-        eqtb[x] = eqtb[undefined_control_sequence];
-    undump_int(x);
-    if (x != eqtb_size)
-        goto BAD_FMT;
-    undump_int(x);
-    if (x != hash_prime)
-        goto BAD_FMT;
-
-    /* Undump the string pool */
-    str_ptr = undump_string_pool();
-    /* Undump the dynamic memory */
-    undump_node_mem();
-    undump_int(temp_token_head);
-    undump_int(hold_token_head);
-    undump_int(omit_template);
-    undump_int(null_list);
-    undump_int(backup_head);
-    undump_int(garbage);
-    undump_int(fix_mem_min);
-    undump_int(fix_mem_max);
-    fixmem = xmallocarray(smemory_word, fix_mem_max + 1);
-    memset(voidcast(fixmem), 0, (fix_mem_max + 1) * sizeof(smemory_word));
-    undump_int(fix_mem_end);
-    undump_int(avail);
-    undump_things(fixmem[fix_mem_min], fix_mem_end - fix_mem_min + 1);
-    undump_int(dyn_used);
-
-    /* Undump the table of equivalents */
-    /* Undump regions 1 to 6 of |eqtb| */
-    k = null_cs;
-    do {
-        undump_int(x);
-        if ((x < 1) || (k + x > eqtb_size + 1))
-            goto BAD_FMT;
-        undump_things(eqtb[k], x);
-        k = k + x;
-        undump_int(x);
-        if ((x < 0) || (k + x > eqtb_size + 1))
-            goto BAD_FMT;
-        for (j = k; j <= k + x - 1; j++)
-            eqtb[j] = eqtb[k - 1];
-        k = k + x;
-    } while (k <= eqtb_size);
-    if (hash_high > 0)          /* undump |hash_extra| part */
-        undump_things(eqtb[eqtb_size + 1], hash_high);
-
-    undump(hash_base, hash_top, par_loc);
-    par_token = cs_token_flag + par_loc;
-    undump(hash_base, hash_top, write_loc);
-    undump_math_codes();
-    undump_text_codes();
-    /* Undump the hash table */
-    undump_primitives();
-    undump(hash_base, frozen_control_sequence, hash_used);
-    p = hash_base - 1;
-    do {
-        undump(p + 1, hash_used, p);
-        undump_hh(hash[p]);
-    } while (p != hash_used);
-    undump_things(hash[hash_used + 1],
-                  undefined_control_sequence - 1 - hash_used);
-    if (debug_format_file)
-        print_csnames(hash_base, undefined_control_sequence - 1);
-    if (hash_high > 0) {
-        undump_things(hash[eqtb_size + 1], hash_high);
-        if (debug_format_file)
-            print_csnames(eqtb_size + 1, hash_high - (eqtb_size + 1));
-    }
-    undump_int(cs_count);
-
-    /* Undump the font information */
-    undump_int(x);
-    set_max_font_id(x);
-    for (k = 0; k <= max_font_id(); k++) {
-        /* Undump the array info for internal font number |k| */
-        undump_font(k);
-    }
-    undump_math_data();
-
-    /* Undump the hyphenation tables */
-    undump_language_data();
-
-    /* Undump a couple more things and the closing check word */
-    undump(batch_mode, error_stop_mode, interaction);
-    if (interactionoption != unspecified_mode)
-        interaction = interactionoption;
-    undump(0, str_ptr, format_ident);
-    undump(0, str_ptr, format_name);
-    undump_int(x);
-    if (x != 69069)
-        goto BAD_FMT;
-
-    /* Undump the lua bytecodes */
-    undump_luac_registers();
-
-    prev_depth_par = ignore_depth;
-    return true;                /* it worked! */
-  BAD_FMT:
-    wake_up_terminal();
-    wterm_cr();
-    fprintf(term_out, "(Fatal format file error; I'm stymied)");
-    return false;
-}
+% dumpdata.w
+%
+% Copyright 2009-2010 Taco Hoekwater <taco@@luatex.org>
+%
+% 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 <http://www.gnu.org/licenses/>.
+
+@ @c
+
+#include "ptexlib.h"
+
+/* we start with 907: the sum of the values of the bytes of "don knuth" */
+
+#define FORMAT_ID (907+27)
+#if ((FORMAT_ID>=0) && (FORMAT_ID<=256))
+#error Wrong value for FORMAT_ID.
+#endif
+
+
+@ After \.{INITEX} has seen a collection of fonts and macros, it
+can write all the necessary information on an auxiliary file so
+that production versions of \TeX\ are able to initialize their
+memory at high speed. The present section of the program takes
+care of such output and input. We shall consider simultaneously
+the processes of storing and restoring,
+so that the inverse relation between them is clear.
+ at .INITEX@>
+
+The global variable |format_ident| is a string that is printed right
+after the |banner| line when \TeX\ is ready to start. For \.{INITEX} this
+string says simply `\.{(INITEX)}'; for other versions of \TeX\ it says,
+for example, `\.{(preloaded format=plain 1982.11.19)}', showing the year,
+month, and day that the format file was created. We have |format_ident=0|
+before \TeX's tables are loaded. |FORMAT_ID| is a new field of type int
+suitable for the identification of a format: values between 0 and 256
+(included) can not be used because in the previous format they are used
+for the length of  the name of the engine.
+ at c
+str_number format_ident;
+str_number format_name;         /* principal file name */
+
+
+@ Format files consist of |memory_word| items, and we use the following
+macros to dump words of different types:
+
+ at c
+FILE *fmt_file;                 /* for input or output of format information */
+
+@ @c
+void store_fmt_file(void)
+{
+    int j, k, l;                /* all-purpose indices */
+    halfword p;                 /* all-purpose pointer */
+    int x;                      /* something to dump */
+    char *format_engine;
+    int callback_id;            /* |pre_dump| callback */
+    char *fmtname = NULL;
+    /* If dumping is not allowed, abort */
+    /* The user is not allowed to dump a format file unless |save_ptr=0|.
+       This condition implies that |cur_level=level_one|, hence
+       the |xeq_level| array is constant and it need not be dumped. */
+    if (save_ptr != 0) {
+        print_err("You can't dump inside a group");
+        help1("`{...\\dump}' is a no-no.");
+        succumb();
+    }
+
+    /* Create the |format_ident|, open the format file, and inform the user
+       that dumping has begun */
+    callback_id = callback_defined(pre_dump_callback);
+    if (callback_id > 0) {
+        (void) run_callback(callback_id, "->");
+    }
+    selector = new_string;
+    tprint(" (format=");
+    print(job_name);
+    print_char(' ');
+    print_int(year_par);
+    print_char('.');
+    print_int(month_par);
+    print_char('.');
+    print_int(day_par);
+    print_char(')');
+    str_room(2);
+    format_ident = make_string();
+    print(job_name);
+    format_name = make_string();
+    if (interaction == batch_mode)
+        selector = log_only;
+    else
+        selector = term_and_log;
+
+    fmtname = pack_job_name(format_extension);
+    while (!zopen_w_output(&fmt_file, fmtname, FOPEN_WBIN_MODE)) {
+        fmtname = prompt_file_name("format file name", format_extension);
+    }
+    tprint_nl("Beginning to dump on file ");
+    tprint(fmtname);
+    free(fmtname);
+    tprint_nl("");
+    print(format_ident);
+
+    /* Dump constants for consistency check */
+    /* The next few sections of the program should make it clear how we use the
+       dump/undump macros. */
+
+    dump_int(0x57325458);       /* Web2C \TeX's magic constant: "W2TX" */
+    dump_int(FORMAT_ID);
+
+    /* Align engine to 4 bytes with one or more trailing NUL */
+    x = (int) strlen(engine_name);
+    format_engine = xmalloc((unsigned) (x + 4));
+    strcpy(format_engine, engine_name);
+    for (k = x; k <= x + 3; k++)
+        format_engine[k] = 0;
+    x = x + 4 - (x % 4);
+    dump_int(x);
+    dump_things(format_engine[0], x);
+    xfree(format_engine);
+    dump_int(0x57325458);       /* TODO HM, what checksum would make sense? */
+    dump_int(max_halfword);
+    dump_int(hash_high);
+    dump_int(eqtb_size);
+    dump_int(hash_prime);
+
+    /* Dump the string pool */
+    k = dump_string_pool();
+    print_ln();
+    print_int(k);
+    tprint(" strings using ");
+    print_int((longinteger) pool_size);
+    tprint(" bytes");
+
+    /* Dump the dynamic memory */
+    /* By sorting the list of available spaces in the variable-size portion of
+       |mem|, we are usually able to get by without having to dump very much
+       of the dynamic memory.
+
+       We recompute |var_used| and |dyn_used|, so that \.{INITEX} dumps valid
+       information even when it has not been gathering statistics.
+     */
+    dump_node_mem();
+    dump_int(temp_token_head);
+    dump_int(hold_token_head);
+    dump_int(omit_template);
+    dump_int(null_list);
+    dump_int(backup_head);
+    dump_int(garbage);
+    x = (int) fix_mem_min;
+    dump_int(x);
+    x = (int) fix_mem_max;
+    dump_int(x);
+    x = (int) fix_mem_end;
+    dump_int(x);
+    dump_int(avail);
+    dyn_used = (int) fix_mem_end + 1;
+    dump_things(fixmem[fix_mem_min], fix_mem_end - fix_mem_min + 1);
+    x = x + (int) (fix_mem_end + 1 - fix_mem_min);
+    p = avail;
+    while (p != null) {
+        decr(dyn_used);
+        p = token_link(p);
+    }
+    dump_int(dyn_used);
+    print_ln();
+    print_int(x);
+    tprint(" memory locations dumped; current usage is ");
+    print_int(var_used);
+    print_char('&');
+    print_int(dyn_used);
+
+    /* Dump the table of equivalents */
+    /* Dump regions 1 to 4 of |eqtb| */
+    /*The table of equivalents usually contains repeated information, so we dump it
+       in compressed form: The sequence of $n+2$ values $(n,x_1,\ldots,x_n,m)$ in the
+       format file represents $n+m$ consecutive entries of |eqtb|, with |m| extra
+       copies of $x_n$, namely $(x_1,\ldots,x_n,x_n,\ldots,x_n)$.
+     */
+    k = null_cs;
+    do {
+        j = k;
+        while (j < int_base - 1) {
+            if ((equiv(j) == equiv(j + 1)) && (eq_type(j) == eq_type(j + 1)) &&
+                (eq_level(j) == eq_level(j + 1)))
+                goto FOUND1;
+            incr(j);
+        }
+        l = int_base;
+        goto DONE1;             /* |j=int_base-1| */
+      FOUND1:
+        incr(j);
+        l = j;
+        while (j < int_base - 1) {
+            if ((equiv(j) != equiv(j + 1)) || (eq_type(j) != eq_type(j + 1)) ||
+                (eq_level(j) != eq_level(j + 1)))
+                goto DONE1;
+            incr(j);
+        }
+      DONE1:
+        dump_int(l - k);
+        dump_things(eqtb[k], l - k);
+        k = j + 1;
+        dump_int(k - l);
+    } while (k != int_base);
+
+    /* Dump regions 5 and 6 of |eqtb| */
+    do {
+        j = k;
+        while (j < eqtb_size) {
+            if (eqtb[j].cint == eqtb[j + 1].cint)
+                goto FOUND2;
+            incr(j);
+        }
+        l = eqtb_size + 1;
+        goto DONE2;             /* |j=eqtb_size| */
+      FOUND2:
+        incr(j);
+        l = j;
+        while (j < eqtb_size) {
+            if (eqtb[j].cint != eqtb[j + 1].cint)
+                goto DONE2;
+            incr(j);
+        }
+      DONE2:
+        dump_int(l - k);
+        dump_things(eqtb[k], l - k);
+        k = j + 1;
+        dump_int(k - l);
+    } while (k <= eqtb_size);
+    if (hash_high > 0)
+        dump_things(eqtb[eqtb_size + 1], hash_high);    /* dump |hash_extra| part */
+
+    dump_int(par_loc);
+    dump_int(write_loc);
+    dump_math_codes();
+    dump_text_codes();
+    /* Dump the hash table */
+    /* A different scheme is used to compress the hash table, since its lower
+       region is usually sparse. When |text(p)<>0| for |p<=hash_used|, we output
+       two words, |p| and |hash[p]|. The hash table is, of course, densely packed
+       for |p>=hash_used|, so the remaining entries are output in a~block.
+     */
+    dump_primitives();
+    dump_int(hash_used);
+    cs_count = frozen_control_sequence - 1 - hash_used + hash_high;
+    for (p = hash_base; p <= hash_used; p++) {
+        if (cs_text(p) != 0) {
+            dump_int(p);
+            dump_hh(hash[p]);
+            incr(cs_count);
+        }
+    }
+    dump_things(hash[hash_used + 1],
+                undefined_control_sequence - 1 - hash_used);
+    if (hash_high > 0)
+        dump_things(hash[eqtb_size + 1], hash_high);
+    dump_int(cs_count);
+    print_ln();
+    print_int(cs_count);
+    tprint(" multiletter control sequences");
+
+    /* Dump the font information */
+    dump_int(max_font_id());
+    for (k = 0; k <= max_font_id(); k++) {
+        /* Dump the array info for internal font number |k| */
+        dump_font(k);
+        tprint_nl("\\font");
+        print_esc(font_id_text(k));
+        print_char('=');
+        tprint_file_name((unsigned char *) font_name(k),
+                         (unsigned char *) font_area(k), NULL);
+        if (font_size(k) != font_dsize(k)) {
+            tprint(" at ");
+            print_scaled(font_size(k));
+            tprint("pt");
+        }
+    }
+    print_ln();
+    print_int(max_font_id());
+    tprint(" preloaded font");
+    if (max_font_id() != 1)
+        print_char('s');
+    dump_math_data();
+
+    /* Dump the hyphenation tables */
+    dump_language_data();
+
+    /* Dump a couple more things and the closing check word */
+    dump_int(interaction);
+    dump_int(format_ident);
+    dump_int(format_name);
+    dump_int(69069);
+    /* We have already printed a lot of statistics, so we set |tracing_stats:=0|
+       to prevent them from appearing again. */
+    tracing_stats_par = 0;
+
+    /* Dump the lua bytecodes */
+    dump_luac_registers();
+
+    /* Close the format file */
+    zwclose(fmt_file);
+}
+
+@ Corresponding to the procedure that dumps a format file, we have a function
+that reads one in. The function returns |false| if the dumped format is
+incompatible with the present \TeX\ table sizes, etc.
+
+ at c
+#define too_small(A) do {					\
+	wake_up_terminal();					\
+	wterm_cr();						\
+	fprintf(term_out,"---! Must increase the %s",(A));	\
+	goto BAD_FMT;						\
+    } while (0)
+
+@ The inverse macros are slightly more complicated, since we need to check
+the range of the values we are reading in. We say `|undump(a)(b)(x)|' to
+read an integer value |x| that is supposed to be in the range |a<=x<=b|.
+
+ at c
+#define undump(A,B,C) do {						\
+	undump_int(x);							\
+	if (x<(A) || x>(B)) goto BAD_FMT;				\
+	else (C) = x;							\
+    } while (0)
+
+
+#define format_debug(A,B) do {					\
+	if (debug_format_file) {				\
+	    fprintf (stderr, "fmtdebug: %s=%d", (A), (int)(B));	\
+	}							\
+    } while (0)
+
+#define undump_size(A,B,C,D) do {					\
+	undump_int(x);							\
+	if (x<(A))  goto BAD_FMT;					\
+	if (x>(B))  too_small(C);					\
+	else format_debug (C,x);					\
+	(D) = x;							\
+    } while (0)
+
+
+@ @c
+boolean load_fmt_file(const char *fmtname)
+{
+    int j, k;                   /* all-purpose indices */
+    halfword p;                 /* all-purpose pointer */
+    int x;                      /* something undumped */
+    char *format_engine;
+    /* Undump constants for consistency check */
+    if (ini_version) {
+        libcfree(hash);
+        libcfree(eqtb);
+        libcfree(fixmem);
+        libcfree(varmem);
+    }
+    undump_int(x);
+    format_debug("format magic number", x);
+    if (x != 0x57325458)
+        goto BAD_FMT;           /* not a format file */
+
+    undump_int(x);
+    format_debug("format id", x);
+    if (x != FORMAT_ID)
+        goto BAD_FMT;           /* FORMAT_ID mismatch */
+
+    undump_int(x);
+    format_debug("engine name size", x);
+    if ((x < 0) || (x > 256))
+        goto BAD_FMT;           /* corrupted format file */
+
+    format_engine = xmalloc((unsigned) x);
+    undump_things(format_engine[0], x);
+    format_engine[x - 1] = 0;   /* force string termination, just in case */
+    if (strcmp(engine_name, format_engine)) {
+        wake_up_terminal();
+        wterm_cr();
+        fprintf(term_out, "---! %s was written by %s", fmtname, format_engine);
+        xfree(format_engine);
+        goto BAD_FMT;
+    }
+    xfree(format_engine);
+    undump_int(x);
+    format_debug("string pool checksum", x);
+    if (x != 0x57325458) {      /* todo: @@\$ *//* check that strings are the same */
+        wake_up_terminal();
+        wterm_cr();
+        fprintf(term_out, "---! %s was written by a different version",
+                fmtname);
+        goto BAD_FMT;
+    }
+    undump_int(x);
+    if (x != max_halfword)
+        goto BAD_FMT;           /* check |max_halfword| */
+    undump_int(hash_high);
+    if ((hash_high < 0) || (hash_high > sup_hash_extra))
+        goto BAD_FMT;
+    if (hash_extra < hash_high)
+        hash_extra = hash_high;
+    eqtb_top = eqtb_size + hash_extra;
+    if (hash_extra == 0)
+        hash_top = undefined_control_sequence;
+    else
+        hash_top = eqtb_top;
+    hash = xmallocarray(two_halves, (unsigned) (1 + hash_top));
+    memset(hash, 0, sizeof(two_halves) * (unsigned) (hash_top + 1));
+    eqtb = xmallocarray(memory_word, (unsigned) (eqtb_top + 1));
+    set_eq_type(undefined_control_sequence, undefined_cs_cmd);
+    set_equiv(undefined_control_sequence, null);
+    set_eq_level(undefined_control_sequence, level_zero);
+    for (x = eqtb_size + 1; x <= eqtb_top; x++)
+        eqtb[x] = eqtb[undefined_control_sequence];
+    undump_int(x);
+    if (x != eqtb_size)
+        goto BAD_FMT;
+    undump_int(x);
+    if (x != hash_prime)
+        goto BAD_FMT;
+
+    /* Undump the string pool */
+    str_ptr = undump_string_pool();
+    /* Undump the dynamic memory */
+    undump_node_mem();
+    undump_int(temp_token_head);
+    undump_int(hold_token_head);
+    undump_int(omit_template);
+    undump_int(null_list);
+    undump_int(backup_head);
+    undump_int(garbage);
+    undump_int(fix_mem_min);
+    undump_int(fix_mem_max);
+    fixmem = xmallocarray(smemory_word, fix_mem_max + 1);
+    memset(voidcast(fixmem), 0, (fix_mem_max + 1) * sizeof(smemory_word));
+    undump_int(fix_mem_end);
+    undump_int(avail);
+    undump_things(fixmem[fix_mem_min], fix_mem_end - fix_mem_min + 1);
+    undump_int(dyn_used);
+
+    /* Undump the table of equivalents */
+    /* Undump regions 1 to 6 of |eqtb| */
+    k = null_cs;
+    do {
+        undump_int(x);
+        if ((x < 1) || (k + x > eqtb_size + 1))
+            goto BAD_FMT;
+        undump_things(eqtb[k], x);
+        k = k + x;
+        undump_int(x);
+        if ((x < 0) || (k + x > eqtb_size + 1))
+            goto BAD_FMT;
+        for (j = k; j <= k + x - 1; j++)
+            eqtb[j] = eqtb[k - 1];
+        k = k + x;
+    } while (k <= eqtb_size);
+    if (hash_high > 0)          /* undump |hash_extra| part */
+        undump_things(eqtb[eqtb_size + 1], hash_high);
+
+    undump(hash_base, hash_top, par_loc);
+    par_token = cs_token_flag + par_loc;
+    undump(hash_base, hash_top, write_loc);
+    undump_math_codes();
+    undump_text_codes();
+    /* Undump the hash table */
+    undump_primitives();
+    undump(hash_base, frozen_control_sequence, hash_used);
+    p = hash_base - 1;
+    do {
+        undump(p + 1, hash_used, p);
+        undump_hh(hash[p]);
+    } while (p != hash_used);
+    undump_things(hash[hash_used + 1],
+                  undefined_control_sequence - 1 - hash_used);
+    if (debug_format_file)
+        print_csnames(hash_base, undefined_control_sequence - 1);
+    if (hash_high > 0) {
+        undump_things(hash[eqtb_size + 1], hash_high);
+        if (debug_format_file)
+            print_csnames(eqtb_size + 1, hash_high - (eqtb_size + 1));
+    }
+    undump_int(cs_count);
+
+    /* Undump the font information */
+    undump_int(x);
+    set_max_font_id(x);
+    for (k = 0; k <= max_font_id(); k++) {
+        /* Undump the array info for internal font number |k| */
+        undump_font(k);
+    }
+    undump_math_data();
+
+    /* Undump the hyphenation tables */
+    undump_language_data();
+
+    /* Undump a couple more things and the closing check word */
+    undump(batch_mode, error_stop_mode, interaction);
+    if (interactionoption != unspecified_mode)
+        interaction = interactionoption;
+    undump(0, str_ptr, format_ident);
+    undump(0, str_ptr, format_name);
+    undump_int(x);
+    if (x != 69069)
+        goto BAD_FMT;
+
+    /* Undump the lua bytecodes */
+    undump_luac_registers();
+
+    prev_depth_par = ignore_depth;
+    return true;                /* it worked! */
+  BAD_FMT:
+    wake_up_terminal();
+    wterm_cr();
+    fprintf(term_out, "(Fatal format file error; I'm stymied)");
+    return false;
+}

Modified: trunk/Build/source/texk/web2c/luatexdir/tex/equivalents.h
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/tex/equivalents.h	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/tex/equivalents.h	2017-03-11 00:31:06 UTC (rev 43454)
@@ -281,14 +281,18 @@
 #  define math_nolimits_mode_code 90
 #  define math_rules_mode_code 91
 #  define math_rules_fam_code 92
-#  define synctex_code 93                                               /* is synctex file generation enabled ?  */
-#  define shape_mode_code 94
-#  define first_valid_language_code 95
-#  define hyphenation_bounds_code 96
-#  define math_skip_mode_code 97
-#  define math_pre_display_gap_factor_code 98
+#  define math_italics_mode_code 93
+#  define synctex_code 94                                               /* is synctex file generation enabled ?  */
+#  define shape_mode_code 95
+#  define first_valid_language_code 96
+#  define hyphenation_bounds_code 97
+#  define math_skip_mode_code 98
+#  define math_pre_display_gap_factor_code 99
+#  define hyphen_penalty_mode_code 100
+#  define automatic_hyphen_penalty_code 101
+#  define explicit_hyphen_penalty_code 102
 
-#  define math_option_code (math_skip_mode_code+1)
+#  define math_option_code (explicit_hyphen_penalty_code+1)
 
 #  define mathoption_int_base_code (math_option_code+1)                 /* one reserve */
 #  define mathoption_int_last_code (mathoption_int_base_code+8)
@@ -648,8 +652,9 @@
 #define disable_lig_par                    int_par(disable_lig_code)
 #define disable_kern_par                   int_par(disable_kern_code)
 #define disable_space_par                  int_par(disable_space_code)
-#define scripts_mode_par                   int_par(math_scripts_mode_code)
-#define nolimits_mode_par                  int_par(math_nolimits_mode_code)
+#define math_scripts_mode_par              int_par(math_scripts_mode_code)
+#define math_nolimits_mode_par             int_par(math_nolimits_mode_code)
+#define math_italics_mode_par              int_par(math_italics_mode_code)
 #define math_rules_mode_par                int_par(math_rules_mode_code)
 #define math_rules_fam_par                 int_par(math_rules_fam_code)
 
@@ -765,6 +770,10 @@
 #define default_skew_char_par              int_par(default_skew_char_code)
 #define saving_hyph_codes_par              int_par(saving_hyph_codes_code)
 
+#define hyphen_penalty_mode_par            int_par(hyphen_penalty_mode_code)
+#define automatic_hyphen_penalty_par       int_par(automatic_hyphen_penalty_code)
+#define explicit_hyphen_penalty_par        int_par(explicit_hyphen_penalty_code)
+
 #define cur_lang_par                       int_par(cur_lang_code)
 #define cur_font_par                       equiv(cur_font_loc)
 
@@ -802,4 +811,71 @@
 #define xspace_skip_subtype (xspace_skip_code + 1)
 #define space_skip_subtype  (space_skip_code + 1)
 
+/*
+
+hyphen_penalty_mode_par   automatic_disc (-)                explicit_disc (\-)
+---------------------------------------------------------------------------------
+0 (default)               ex_hyphen_penalty_par             ex_hyphen_penalty_par
+1                         hyphen_penalty_par                hyphen_penalty_par
+2                         ex_hyphen_penalty_par             hyphen_penalty_par
+3                         hyphen_penalty_par                ex_hyphen_penalty_par
+4                         automatic_hyphen_penalty_par      explicit_disc_penalty_par
+5                         ex_hyphen_penalty_par             explicit_disc_penalty_par
+6                         hyphen_penalty_par                explicit_disc_penalty_par
+7                         automatic_hyphen_penalty_par      ex_hyphen_penalty_par
+8                         automatic_hyphen_penalty_par      hyphen_penalty_par
+*/
+
+#define set_automatic_disc_penalty(n) \
+    switch (hyphen_penalty_mode_par) { \
+        case 0: \
+        case 2: \
+        case 5: \
+            /* we take ex_hyphen_penalty */ \
+            disc_penalty(n) = ex_hyphen_penalty_par; \
+            break; \
+        case 1: \
+        case 3: \
+        case 6: \
+            /* we take hyphen_penalty */ \
+            disc_penalty(n) = hyphen_penalty_par; \
+            break; \
+        case 4: \
+        case 7: \
+        case 8: \
+            /* we take automatic_hyphen_penalty */ \
+            disc_penalty(n) = automatic_hyphen_penalty_par; \
+            break; \
+        default: \
+            /* what we've done since the beginning */ \
+            disc_penalty(n) = ex_hyphen_penalty_par; \
+            break; \
+    }
+
+#define set_explicit_disc_penalty(n) \
+    switch (hyphen_penalty_mode_par) { \
+        case 0: \
+        case 3: \
+        case 7: \
+            /* we take ex_hyphen_penalty */ \
+            disc_penalty(n) = ex_hyphen_penalty_par; \
+            break; \
+        case 1: \
+        case 2: \
+        case 8: \
+            /* we take hyphen_penalty */ \
+            disc_penalty(n) = hyphen_penalty_par; \
+            break; \
+        case 4: \
+        case 5: \
+        case 6: \
+            /* we take automatic_hyphen_penalty */ \
+            disc_penalty(n) = explicit_hyphen_penalty_par; \
+            break; \
+        default: \
+            /* what we've done since the beginning */ \
+            disc_penalty(n) = ex_hyphen_penalty_par; \
+            break; \
+    }
+
 #endif

Modified: trunk/Build/source/texk/web2c/luatexdir/tex/linebreak.w
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/tex/linebreak.w	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/tex/linebreak.w	2017-03-11 00:31:06 UTC (rev 43454)
@@ -104,14 +104,14 @@
     new_hyphenation(temp_head, cur_list.tail_field);
     cur_list.tail_field = new_ligkern(temp_head, cur_list.tail_field);
     if (is_char_node(cur_list.tail_field)) {
-        tail_append(new_penalty(inf_penalty));
+        tail_append(new_penalty(inf_penalty,line_penalty));
     } else if (type(cur_list.tail_field) != glue_node) {
-        tail_append(new_penalty(inf_penalty));
+        tail_append(new_penalty(inf_penalty,line_penalty));
     } else {
         halfword t = alink(cur_list.tail_field);
 		flush_node(cur_list.tail_field);
 		cur_list.tail_field = t;
-		tail_append(new_penalty(inf_penalty));
+		tail_append(new_penalty(inf_penalty,line_penalty));
     }
     final_par_glue = new_param_glue(par_fill_skip_code);
     couple_nodes(cur_list.tail_field, final_par_glue);

Modified: trunk/Build/source/texk/web2c/luatexdir/tex/maincontrol.w
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/tex/maincontrol.w	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/tex/maincontrol.w	2017-03-11 00:31:06 UTC (rev 43454)
@@ -1,3639 +1,3639 @@
-% maincontrol.w
-%
-% Copyright 2009-2010 Taco Hoekwater <taco@@luatex.org>
-%
-% 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 <http://www.gnu.org/licenses/>.
-
-@ @c
-
-#include "ptexlib.h"
-#include "lua/luatex-api.h"
-
-@ @c
-#define mode     mode_par
-#define tail     tail_par
-#define head     head_par
-#define dir_save dirs_par
-
-@ We come now to the |main_control| routine, which contains the master
-switch that causes all the various pieces of \TeX\ to do their things,
-in the right order.
-
-In a sense, this is the grand climax of the program: It applies all the
-tools that we have worked so hard to construct. In another sense, this is
-the messiest part of the program: It necessarily refers to other pieces
-of code all over the place, so that a person can't fully understand what is
-going on without paging back and forth to be reminded of conventions that
-are defined elsewhere. We are now at the hub of the web, the central nervous
-system that touches most of the other parts and ties them together.
-@^brain@>
-
-The structure of |main_control| itself is quite simple. There's a label
-called |big_switch|, at which point the next token of input is fetched
-using |get_x_token|. Then the program branches at high speed into one of
-about 100 possible directions, based on the value of the current
-mode and the newly fetched command code; the sum |abs(mode)+cur_cmd|
-indicates what to do next. For example, the case `|vmode+letter|' arises
-when a letter occurs in vertical mode (or internal vertical mode); this
-case leads to instructions that initialize a new paragraph and enter
-horizontal mode.
-
-The big |case| statement that contains this multiway switch has been labeled
-|reswitch|, so that the program can |goto reswitch| when the next token
-has already been fetched. Most of the cases are quite short; they call
-an ``action procedure'' that does the work for that case, and then they
-either |goto reswitch| or they ``fall through'' to the end of the |case|
-statement, which returns control back to |big_switch|. Thus, |main_control|
-is not an extremely large procedure, in spite of the multiplicity of things
-it must do; it is small enough to be handled by PASCAL compilers that put
-severe restrictions on procedure size.
-@!@^action procedure@>
-
-One case is singled out for special treatment, because it accounts for most
-of \TeX's activities in typical applications. The process of reading simple
-text and converting it into |char_node| records, while looking for ligatures
-and kerns, is part of \TeX's ``inner loop''; the whole program runs
-efficiently when its inner loop is fast, so this part has been written
-with particular care.
-
-We leave the |space_factor| unchanged if |sf_code(cur_chr)=0|; otherwise we
-set it equal to |sf_code(cur_chr)|, except that it should never change
-from a value less than 1000 to a value exceeding 1000. The most common
-case is |sf_code(cur_chr)=1000|, so we want that case to be fast.
-
- at c
-void adjust_space_factor(void)
-{
-    halfword s = get_sf_code(cur_chr);
-    if (s == 1000) {
-        space_factor_par = 1000;
-    } else if (s < 1000) {
-        if (s > 0)
-            space_factor_par = s;
-    } else if (space_factor_par < 1000) {
-        space_factor_par = 1000;
-    } else {
-        space_factor_par = s;
-    }
-}
-
-@ From Knuth: ``Having |font_glue| allocated for each text font saves
-both time and memory.''  That may be true, but it also punches through
-the API wall for fonts, so I removed that -- Taco. But a bit of caching
-is very welcome, which is why I need to have the next two globals:
-
-@ To handle the execution state of |main_control|'s eternal loop,
-an extra global variable is used, along with a macro to define
-its values.
-
- at c
-#define goto_next 0
-#define goto_skip_token 1
-#define goto_return 2
-
-static int main_control_state;
-
-@* Main control helpers.
-
-Here are all the functions that are called from |main_control| that
-are not already defined elsewhere. For the moment, this list simply
-in the order that the appear in |init_main_control|, below.
-
-@
- at c
-static void run_char_num (void) {
-    scan_char_num();
-    cur_chr = cur_val;
-    adjust_space_factor();
-    tail_append(new_char(cur_font_par, cur_chr));
-}
-
-static void run_char (void) {
-    adjust_space_factor();
-    tail_append(new_char(cur_font_par, cur_chr));
-}
-
-@
-The occurrence of blank spaces is almost part of \TeX's inner loop,
-since we usually encounter about one space for every five non-blank characters.
-Therefore |main_control| gives second-highest priority to ordinary spaces.
-
-When a glue parameter like \.{\\spaceskip} is set to `\.{0pt}', we will
-see to it later that the corresponding glue specification is precisely
-|zero_glue|, not merely a pointer to some specification that happens
-to be full of zeroes. Therefore it is simple to test whether a glue parameter
-is zero or~not.
-
- at c
-static void run_app_space (void) {
-    halfword p; /* was a global temp_ptr */
-    int method = disable_space_par ;
-    if (method == 1) {
-        /* don't inject anything, not even zero skip */
-    } else if (method == 2) {
-        p = new_glue(zero_glue);
-        couple_nodes(tail,p);
-        tail = p;
-    } else if ((abs(mode) + cur_cmd == hmode + spacer_cmd) && (!(space_factor_par == 1000))) {
-        app_space();
-    } else {
-        /* Append a normal inter-word space to the current list */
-        if (glue_is_zero(space_skip_par)) {
-            /* Find the glue specification for text spaces in the current font */
-            p = new_glue(zero_glue);
-            width(p) = space(cur_font_par);
-            stretch(p) = space_stretch(cur_font_par);
-            shrink(p) = space_shrink(cur_font_par);
-
-        } else {
-            p = new_param_glue(space_skip_code);
-        }
-        /* so from now we have a subtype with spaces: */
-        subtype(p) = space_skip_code + 1 ;
-        couple_nodes(tail,p);
-        tail = p;
-    }
-}
-
-@ Append a |boundary_node|
- at c
-static void run_boundary (void) {
-    halfword n ;
-    n = new_node(boundary_node,cur_chr);
-    if ((cur_chr == 1) || (cur_chr == 2) ) {
-        /* user boundary or protrusion boundary */
-        scan_int();
-        boundary_value(n) = cur_val;
-    }
-    couple_nodes(tail, n);
-    tail = n;
-}
-
-@ @c
-static void run_char_ghost (void) {
-    int t;
-    t = cur_chr;
-    get_x_token();
-    if ((cur_cmd == letter_cmd) || (cur_cmd == other_char_cmd)
-        || (cur_cmd == char_given_cmd) || (cur_cmd == char_num_cmd)) {
-        halfword p = new_glyph(get_cur_font(), cur_chr);
-        if (t == 0) {
-            set_is_leftghost(p);
-        } else {
-            set_is_rightghost(p);
-        }
-        tail_append(p);
-    }
-}
-
-@ @c
-static void run_relax (void) {
-    return;
-}
-
-@ |ignore_spaces| is a special case: after it has acted, |get_x_token| has already
-fetched the next token from the input, so that operation in |main_control|
-should be skipped.
-
- at c
-static void run_ignore_spaces (void) {
-    if (cur_chr == 0) {
-        /* Get the next non-blank non-call... */
-        do {
-            get_x_token();
-        } while (cur_cmd == spacer_cmd);
-        main_control_state = goto_skip_token;
-    } else {
-        int t = scanner_status;
-        scanner_status = normal;
-        get_next();
-        scanner_status = t;
-        cur_cs = prim_lookup(cs_text(cur_cs));
-        if (cur_cs != undefined_primitive) {
-            cur_cmd = get_prim_eq_type(cur_cs);
-            cur_chr = get_prim_equiv(cur_cs);
-            cur_tok = (cur_cmd * STRING_OFFSET) + cur_chr;
-            main_control_state = goto_skip_token;
-        }
-    }
-}
-
-@ |stop| is the second special case. We want |main_control| to return to its caller
-if there is nothing left to do.
-
- at c
-static void run_stop (void) {
-    if (its_all_over())
-       main_control_state= goto_return; /* this is the only way out */
-}
-
-@ @c
-static void run_non_math_math (void) {
-    back_input();
-    new_graf(true);
-}
-
-@ @c
-static void run_math_char_num (void) {
-    mathcodeval mval;           /* to build up an argument to |set_math_char| */
-    if (cur_chr == 0)
-        mval = scan_mathchar(tex_mathcode);
-    else if (cur_chr == 1)
-        mval = scan_mathchar(umath_mathcode);
-    else
-        mval = scan_mathchar(umathnum_mathcode);
-    math_char_in_text(mval);
-}
-
-@ @c
-static void run_math_given (void) {
-    mathcodeval mval;           /* to build up an argument to |set_math_char| */
-    mval = mathchar_from_integer(cur_chr, tex_mathcode);
-    math_char_in_text(mval);
-}
-
-static void run_xmath_given (void) {
-    mathcodeval mval;           /* to build up an argument to |set_math_char| */
-    mval = mathchar_from_integer(cur_chr, umath_mathcode);
-    math_char_in_text(mval);
-}
-
-@  The most important parts of |main_control| are concerned with \TeX's
-chief mission of box-making. We need to control the activities that put
-entries on vlists and hlists, as well as the activities that convert
-those lists into boxes. All of the necessary machinery has already been
-developed; it remains for us to ``push the buttons'' at the right times.
-
-As an introduction to these routines, let's consider one of the simplest
-cases: What happens when `\.{\\hrule}' occurs in vertical mode, or
-`\.{\\vrule}' in horizontal mode or math mode? The code in |main_control|
-is short, since the |scan_rule_spec| routine already does most of what is
-required; thus, there is no need for a special action procedure.
-
-Note that baselineskip calculations are disabled after a rule in vertical
-mode, by setting |prev_depth:=ignore_depth|.
-
- at c
-static void run_rule (void) {
-    tail_append(scan_rule_spec());
-    if (abs(mode) == vmode)
-        prev_depth_par = ignore_depth;
-    else if (abs(mode) == hmode)
-        space_factor_par = 1000;
-}
-
-@
-Many of the actions related to box-making are triggered by the appearance
-of braces in the input. For example, when the user says `\.{\\hbox}
-\.{to} \.{100pt\{$\langle\,\hbox{hlist}\,\rangle$\}}' in vertical mode,
-the information about the box size (100pt, |exactly|) is put onto |save_stack|
-with a level boundary word just above it, and |cur_group:=adjusted_hbox_group|;
-\TeX\ enters restricted horizontal mode to process the hlist. The right
-brace eventually causes |save_stack| to be restored to its former state,
-at which time the information about the box size (100pt, |exactly|) is
-available once again; a box is packaged and we leave restricted horizontal
-mode, appending the new box to the current list of the enclosing mode
-(in this case to the current list of vertical mode), followed by any
-vertical adjustments that were removed from the box by |hpack|.
-
-The next few sections of the program are therefore concerned with the
-treatment of left and right curly braces.
-
-If a left brace occurs in the middle of a page or paragraph, it simply
-introduces a new level of grouping, and the matching right brace will not have
-such a drastic effect. Such grouping affects neither the mode nor the
-current list.
-
- at c
-static void run_left_brace (void) {
-    new_save_level(simple_group);
-    eq_word_define(int_base + no_local_whatsits_code, 0);
-    eq_word_define(int_base + no_local_dirs_code, 0);
-}
-
-static void run_begin_group (void) {
-    new_save_level(semi_simple_group);
-    eq_word_define(int_base + no_local_whatsits_code, 0);
-    eq_word_define(int_base + no_local_dirs_code, 0);
-}
-
-static void run_end_group (void) {
-    if (cur_group == semi_simple_group) {
-        fixup_directions();
-    } else {
-        off_save();
-    }
-}
-
-@ Constructions that require a box are started by calling |scan_box| with
-a specified context code. The |scan_box| routine verifies
-that a |make_box| command comes next and then it calls |begin_box|.
-
- at c
-static void run_move (void) {
-    int t = cur_chr;
-    scan_normal_dimen();
-    if (t == 0)
-        scan_box(cur_val);
-    else
-        scan_box(-cur_val);
-}
-
-@ @c
-static void run_leader_ship (void) {
-    scan_box(leader_flag - a_leaders + cur_chr);
-}
-
-@ @c
-static void run_make_box (void) {
-    begin_box(0);
-}
-
-@ @c
-static void run_box_dir (void) {
-    scan_register_num();
-    cur_box = box(cur_val);
-    scan_optional_equals();
-    scan_direction();
-    if (cur_box != null)
-        box_dir(cur_box) = cur_val;
-}
-
-@ There is a really small patch to add a new primitive called
-\.{\\quitvmode}. In vertical modes, it is identical to \.{\\indent},
-but in horizontal and math modes it is really a no-op (as opposed to
-\.{\\indent}, which executes the |indent_in_hmode| procedure).
-
-A paragraph begins when horizontal-mode material occurs in vertical mode,
-or when the paragraph is explicitly started by `\.{\\quitvmode}',
-`\.{\\indent}' or `\.{\\noindent}'.
-
- at c
-static void run_start_par_vmode (void) {
-    new_graf((cur_chr > 0));
-}
-
-@ @c
-static void run_start_par (void) {
-   if (cur_chr != 2)
-       indent_in_hmode();
-}
-
-@ @c
-static void run_new_graf (void) {
-   back_input();
-   new_graf(true);
-}
-
-@ A paragraph ends when a |par_end| command is sensed, or when we are in
-horizontal mode when reaching the right brace of vertical-mode routines
-like \.{\\vbox}, \.{\\insert}, or \.{\\output}.
-
- at c
-static void run_par_end_vmode (void) {
-    normal_paragraph();
-    if (mode > 0) {
-        checked_page_filter(vmode_par);
-        build_page();
-    }
-}
-
-@ @c
-static void run_par_end_hmode (void) {
-    if (align_state < 0)
-        off_save();         /* this tries to  recover from an alignment that didn't end properly */
-    end_graf(bottom_level); /* this takes us to the enclosing mode, if |mode>0| */
-    if (mode == vmode) {
-        checked_page_filter(hmode_par);
-        build_page();
-    }
-}
-
-@ @c
-static void append_italic_correction_mmode (void) {
-    tail_append(new_kern(0)); /* what subtype to use */
-}
-
-@ @c
-static void run_local_box (void) {
-    append_local_box(cur_chr);
-}
-
-@ @c
-static void run_halign_mmode (void) {
-    if (privileged()) {
-        if (cur_group == math_shift_group)
-            init_align();
-        else
-            off_save();
-    }
-}
-
-@ @c
-static void run_eq_no (void) {
-    if (privileged()) {
-        if (cur_group == math_shift_group)
-            start_eq_no();
-        else
-            off_save();
-    }
-}
-
-@ @c
-static void run_letter_mmode (void) {
-   set_math_char(get_math_code(cur_chr));
-}
-
-@ @c
-static void run_char_num_mmode (void) {
-    scan_char_num();
-    cur_chr = cur_val;
-    set_math_char(get_math_code(cur_chr));
-}
-
-@ @c
-static void run_math_char_num_mmode (void) {
-    mathcodeval mval;           /* to build up an argument to |set_math_char| */
-    if (cur_chr == 0)
-        mval = scan_mathchar(tex_mathcode);
-    else if (cur_chr == 1)
-        mval = scan_mathchar(umath_mathcode);
-    else
-        mval = scan_mathchar(umathnum_mathcode);
-    set_math_char(mval);
-}
-
-@ @c
-static void run_math_given_mmode (void) {
-    mathcodeval mval;           /* to build up an argument to |set_math_char| */
-    mval = mathchar_from_integer(cur_chr, tex_mathcode);
-    set_math_char(mval);
-}
-
-static void run_xmath_given_mmode (void) {
-    mathcodeval mval;           /* to build up an argument to |set_math_char| */
-    mval = mathchar_from_integer(cur_chr, umath_mathcode);
-    set_math_char(mval);
-}
-
-@ @c
-static void run_delim_num (void) {
-    mathcodeval mval;           /* to build up an argument to |set_math_char| */
-    if (cur_chr == 0)
-        mval = scan_delimiter_as_mathchar(tex_mathcode);
-    else
-        mval = scan_delimiter_as_mathchar(umath_mathcode);
-    set_math_char(mval);
-
-}
-
-@ @c
-static void run_vcenter (void) {
-    scan_spec(vcenter_group);
-    normal_paragraph();
-    push_nest();
-    mode = -vmode;
-    prev_depth_par = ignore_depth;
-    if (every_vbox_par != null)
-        begin_token_list(every_vbox_par, every_vbox_text);
-}
-
-@ @c
-static void run_math_style (void) {
-    tail_append(new_style((small_number) cur_chr));
-}
-
-@ @c
-static void run_non_script (void) {
-    tail_append(new_glue(zero_glue));
-    subtype(tail) = cond_math_glue;
-}
-
-@ @c
-static void run_math_choice (void) {
-    if (cur_chr == 0)
-        append_choices();
-    else
-        setup_math_style();
-}
-
-@ @c
-static void run_math_shift (void) {
-    if (cur_group == math_shift_group)
-        after_math();
-    else
-        off_save();
-}
-
-@ @c
-static void run_after_assignment (void) {
-    get_token();
-    after_token = cur_tok;
-}
-
-@ @c
-static void run_after_group (void) {
-    get_token();
-    save_for_after(cur_tok);
-}
-
-@ @c
-static void run_extension (void) {
-    do_extension(0);
-}
-
-static void run_normal (void) {
-{
-    switch (cur_chr) {
-        case save_pos_code:
-            new_whatsit(save_pos_node);
-            break;
-        case save_cat_code_table_code:
-            scan_int();
-            if ((cur_val < 0) || (cur_val > 0x7FFF)) {
-                print_err("Invalid \\catcode table");
-                help1("All \\catcode table ids must be between 0 and 0x7FFF");
-                error();
-            } else {
-                if (cur_val == cat_code_table_par) {
-                    print_err("Invalid \\catcode table");
-                    help1("You cannot overwrite the current \\catcode table");
-                    error();
-                } else {
-                    copy_cat_codes(cat_code_table_par, cur_val);
-                }
-            }
-            break;
-        case init_cat_code_table_code:
-            scan_int();
-            if ((cur_val < 0) || (cur_val > 0x7FFF)) {
-                print_err("Invalid \\catcode table");
-                help1("All \\catcode table ids must be between 0 and 0x7FFF");
-                error();
-            } else {
-                if (cur_val == cat_code_table_par) {
-                    print_err("Invalid \\catcode table");
-                    help1("You cannot overwrite the current \\catcode table");
-                    error();
-                } else {
-                    initex_cat_codes(cur_val);
-                }
-            }
-            break;
-        case set_random_seed_code:
-            /*  Negative random seed values are silently converted to positive ones */
-            scan_int();
-            if (cur_val < 0)
-                negate(cur_val);
-            random_seed = cur_val;
-            init_randoms(random_seed);
-            break;
-        case late_lua_code:
-            new_whatsit(late_lua_node); /* type == normal */
-            late_lua_name(tail) = scan_lua_state();
-            (void) scan_toks(false, false);
-            late_lua_data(tail) = def_ref;
-            break;
-        case expand_font_code:
-            read_expand_font();
-            break;
-        default:
-            confusion("int1");
-            break;
-        }
-    }
-}
-
-/*
-    this is experimental and not used for production, only for testing and writing
-    macros (some options stay)
-
-*/
-
-#define mathoption_set_int(A) \
-    scan_int(); \
-    word_define(mathoption_int_base+A, cur_val);
-
-static void run_option(void) {
-    int a = 0 ;
-    switch (cur_chr) {
-        case math_option_code:
-            if (scan_keyword("old")) {
-                mathoption_set_int(c_mathoption_old_code);
-            } else if (scan_keyword("noitaliccompensation")) {
-                mathoption_set_int(c_mathoption_no_italic_compensation_code);
-            } else if (scan_keyword("nocharitalic")) {
-                mathoption_set_int(c_mathoption_no_char_italic_code);
-            } else if (scan_keyword("useoldfractionscaling")) {
-                mathoption_set_int(c_mathoption_use_old_fraction_scaling_code);
-            } else if (scan_keyword("umathcodemeaning")) {
-                mathoption_set_int(c_mathoption_umathcode_meaning_code);
-            } else {
-                normal_warning("mathoption","unknown key");
-            }
-            break;
-        default:
-            /* harmless */
-            break;
-    }
-}
-
-@ For mode-independent commands, the following macro is useful.
-
-Also, there is a list of cases where the user has probably gotten into or out of math
-mode by mistake. \TeX\ will insert a dollar sign and rescan the current token, and
-it makes sense ot have a macro for that as well.
-
- at c
-#define any_mode(A,B) jump_table[vmode+(A)]=B; jump_table[hmode+(A)]=B; jump_table[mmode+(A)]=B
-#define non_math(A,B) jump_table[vmode+(A)]=B; jump_table[hmode+(A)]=B;
-
-
-@ The |main_control| uses a jump table, and |init_main_control| sets that table up.
- at c
-typedef void (*main_control_function) (void);
-main_control_function *jump_table;
-
-static void init_main_control (void) {
-    jump_table = xmalloc((mmode+max_command_cmd+1) * sizeof(main_control_function)) ;
-
-    jump_table[hmode + char_num_cmd] = run_char_num;
-    jump_table[hmode + letter_cmd] = run_char;
-    jump_table[hmode + other_char_cmd] = run_char;
-    jump_table[hmode + char_given_cmd] = run_char;
-    jump_table[hmode + spacer_cmd] = run_app_space;
-    jump_table[hmode + ex_space_cmd] = run_app_space;
-    jump_table[mmode + ex_space_cmd] = run_app_space;
-    jump_table[hmode + boundary_cmd] = run_boundary;
-    jump_table[hmode + char_ghost_cmd] = run_char_ghost;
-    jump_table[mmode + char_ghost_cmd] = run_char_ghost;
-    any_mode(relax_cmd, run_relax);
-    jump_table[vmode + spacer_cmd] = run_relax;
-    jump_table[mmode + spacer_cmd] = run_relax;
-    jump_table[mmode + boundary_cmd] = run_relax;
-    any_mode(ignore_spaces_cmd,run_ignore_spaces);
-    jump_table[vmode + stop_cmd] = run_stop;
-    jump_table[vmode + math_char_num_cmd] = run_non_math_math;
-    jump_table[vmode + math_given_cmd] = run_non_math_math;
-    jump_table[vmode + xmath_given_cmd] = run_non_math_math;
-    jump_table[hmode + math_char_num_cmd] = run_math_char_num;
-    jump_table[hmode + math_given_cmd] = run_math_given;
-    jump_table[hmode + xmath_given_cmd] = run_xmath_given;
-
-    jump_table[vmode + vmove_cmd] = report_illegal_case;
-    jump_table[hmode + hmove_cmd] = report_illegal_case;
-    jump_table[mmode + hmove_cmd] = report_illegal_case;
-    any_mode(last_item_cmd, report_illegal_case);
-    jump_table[vmode + vadjust_cmd] = report_illegal_case;
-    jump_table[vmode + ital_corr_cmd] = report_illegal_case;
-    non_math(eq_no_cmd,report_illegal_case);
-    any_mode(mac_param_cmd,report_illegal_case);
-
-    non_math(sup_mark_cmd, insert_dollar_sign);
-    non_math(sub_mark_cmd, insert_dollar_sign);
-    non_math(super_sub_script_cmd, insert_dollar_sign);
-    non_math(math_comp_cmd, insert_dollar_sign);
-    non_math(delim_num_cmd, insert_dollar_sign);
-    non_math(left_right_cmd, insert_dollar_sign);
-    non_math(above_cmd, insert_dollar_sign);
-    non_math(radical_cmd, insert_dollar_sign);
-    non_math(math_style_cmd, insert_dollar_sign);
-    non_math(math_choice_cmd, insert_dollar_sign);
-    non_math(vcenter_cmd, insert_dollar_sign);
-    non_math(non_script_cmd, insert_dollar_sign);
-    non_math(mkern_cmd, insert_dollar_sign);
-    non_math(limit_switch_cmd, insert_dollar_sign);
-    non_math(mskip_cmd, insert_dollar_sign);
-    non_math(math_accent_cmd, insert_dollar_sign);
-    jump_table[mmode + endv_cmd] =  insert_dollar_sign;
-    jump_table[mmode + par_end_cmd] =  insert_dollar_sign_par_end;
-    jump_table[mmode + stop_cmd] =  insert_dollar_sign;
-    jump_table[mmode + vskip_cmd] =  insert_dollar_sign;
-    jump_table[mmode + un_vbox_cmd] =  insert_dollar_sign;
-    jump_table[mmode + valign_cmd] =  insert_dollar_sign;
-    jump_table[mmode + hrule_cmd] =  insert_dollar_sign;
-    jump_table[mmode + no_hrule_cmd] =  insert_dollar_sign;
-    jump_table[vmode + hrule_cmd] = run_rule;
-    jump_table[vmode + no_hrule_cmd] = run_rule;
-    jump_table[hmode + vrule_cmd] = run_rule;
-    jump_table[hmode + no_vrule_cmd] = run_rule;
-    jump_table[mmode + vrule_cmd] = run_rule;
-    jump_table[mmode + no_vrule_cmd] = run_rule;
-    jump_table[vmode + vskip_cmd] = append_glue;
-    jump_table[hmode + hskip_cmd] = append_glue;
-    jump_table[mmode + hskip_cmd] = append_glue;
-    jump_table[mmode + mskip_cmd] = append_glue;
-    any_mode(kern_cmd, append_kern);
-    jump_table[mmode + mkern_cmd] = append_kern;
-    non_math(left_brace_cmd, run_left_brace);
-    any_mode(begin_group_cmd,run_begin_group);
-    any_mode(end_group_cmd, run_end_group);
-    any_mode(right_brace_cmd, handle_right_brace);
-    jump_table[vmode + hmove_cmd] = run_move;
-    jump_table[hmode + vmove_cmd] = run_move;
-    jump_table[mmode + vmove_cmd] = run_move;
-    any_mode(leader_ship_cmd, run_leader_ship);
-    any_mode(make_box_cmd, run_make_box);
-    any_mode(assign_box_dir_cmd, run_box_dir);
-    jump_table[vmode + start_par_cmd] = run_start_par_vmode;
-    jump_table[hmode + start_par_cmd] = run_start_par;
-    jump_table[mmode + start_par_cmd] = run_start_par;
-    jump_table[vmode + letter_cmd] = run_new_graf;
-    jump_table[vmode + other_char_cmd] = run_new_graf;
-    jump_table[vmode + char_num_cmd] = run_new_graf;
-    jump_table[vmode + char_given_cmd] = run_new_graf;
-    jump_table[vmode + char_ghost_cmd] = run_new_graf;
-    jump_table[vmode + math_shift_cmd] = run_new_graf;
-    jump_table[vmode + math_shift_cs_cmd] = run_new_graf;
-    jump_table[vmode + un_hbox_cmd] = run_new_graf;
-    jump_table[vmode + vrule_cmd] = run_new_graf;
-    jump_table[vmode + no_vrule_cmd] = run_new_graf;
-    jump_table[vmode + accent_cmd] = run_new_graf;
-    jump_table[vmode + discretionary_cmd] = run_new_graf;
-    jump_table[vmode + hskip_cmd] = run_new_graf;
-    jump_table[vmode + valign_cmd] = run_new_graf;
-    jump_table[vmode + ex_space_cmd] = run_new_graf;
-    jump_table[vmode + boundary_cmd] = run_new_graf;
-    jump_table[vmode + par_end_cmd] = run_par_end_vmode;
-    jump_table[hmode + par_end_cmd] = run_par_end_hmode;
-    jump_table[hmode + stop_cmd] = head_for_vmode;
-    jump_table[hmode + vskip_cmd] = head_for_vmode;
-    jump_table[hmode + hrule_cmd] = head_for_vmode;
-    jump_table[hmode + no_hrule_cmd] = head_for_vmode;
-    jump_table[hmode + un_vbox_cmd] = head_for_vmode;
-    jump_table[hmode + halign_cmd] = head_for_vmode;
-    any_mode(insert_cmd,begin_insert_or_adjust);
-    jump_table[hmode + vadjust_cmd] = begin_insert_or_adjust;
-    jump_table[mmode + vadjust_cmd] = begin_insert_or_adjust;
-    any_mode(mark_cmd, handle_mark);
-    any_mode(break_penalty_cmd, append_penalty);
-    any_mode(remove_item_cmd, delete_last);
-    jump_table[vmode + un_vbox_cmd] = unpackage;
-    jump_table[hmode + un_hbox_cmd] = unpackage;
-    jump_table[mmode + un_hbox_cmd] = unpackage;
-    jump_table[hmode + ital_corr_cmd] = append_italic_correction;
-    jump_table[mmode + ital_corr_cmd] = append_italic_correction_mmode;
-    jump_table[hmode + discretionary_cmd] = append_discretionary;
-    jump_table[mmode + discretionary_cmd] = append_discretionary;
-    any_mode(assign_local_box_cmd, run_local_box);
-    jump_table[hmode + accent_cmd] = make_accent;
-    any_mode(car_ret_cmd,align_error);
-    any_mode(tab_mark_cmd,align_error);
-    any_mode(no_align_cmd,no_align_error);
-    any_mode(omit_cmd, omit_error);
-    jump_table[vmode + halign_cmd] = init_align;
-    jump_table[hmode + valign_cmd] = init_align;
-    jump_table[mmode + halign_cmd] = run_halign_mmode;
-    jump_table[vmode + endv_cmd] = do_endv;
-    jump_table[hmode + endv_cmd] = do_endv;
-    any_mode(end_cs_name_cmd, cs_error);
-    jump_table[hmode + math_shift_cmd] = init_math;
-    jump_table[hmode + math_shift_cs_cmd] = init_math;
-    jump_table[mmode + eq_no_cmd] = run_eq_no;
-    jump_table[mmode + left_brace_cmd] = math_left_brace;
-    jump_table[mmode + letter_cmd] = run_letter_mmode;
-    jump_table[mmode + other_char_cmd] = run_letter_mmode;
-    jump_table[mmode + char_given_cmd] = run_letter_mmode;
-    jump_table[mmode + char_num_cmd] = run_char_num_mmode;
-    jump_table[mmode + math_char_num_cmd] = run_math_char_num_mmode;
-    jump_table[mmode + math_given_cmd] = run_math_given_mmode;
-    jump_table[mmode + xmath_given_cmd] = run_xmath_given_mmode;
-    jump_table[mmode + delim_num_cmd] = run_delim_num;
-    jump_table[mmode + math_comp_cmd] = math_math_comp;
-    jump_table[mmode + limit_switch_cmd] = math_limit_switch;
-    jump_table[mmode + radical_cmd] = math_radical;
-    jump_table[mmode + accent_cmd] = math_ac;
-    jump_table[mmode + math_accent_cmd] = math_ac;
-    jump_table[mmode + vcenter_cmd] = run_vcenter;
-    jump_table[mmode + math_style_cmd] = run_math_style;
-    jump_table[mmode + non_script_cmd] = run_non_script;
-    jump_table[mmode + math_choice_cmd] = run_math_choice;
-    jump_table[mmode + above_cmd] = math_fraction;
-    jump_table[mmode + sub_mark_cmd] = sub_sup;
-    jump_table[mmode + sup_mark_cmd] = sub_sup;
-    jump_table[mmode + super_sub_script_cmd] = sub_sup;
-    jump_table[mmode + left_right_cmd] = math_left_right;
-    jump_table[mmode + math_shift_cmd] = run_math_shift;
-    jump_table[mmode + math_shift_cs_cmd] = run_math_shift;
-    any_mode(toks_register_cmd, prefixed_command);
-    any_mode(assign_toks_cmd, prefixed_command);
-    any_mode(assign_int_cmd, prefixed_command);
-    any_mode(assign_attr_cmd, prefixed_command);
-    any_mode(assign_dir_cmd, prefixed_command);
-    any_mode(assign_dimen_cmd, prefixed_command);
-    any_mode(assign_glue_cmd, prefixed_command);
-    any_mode(assign_mu_glue_cmd, prefixed_command);
-    any_mode(assign_font_dimen_cmd, prefixed_command);
-    any_mode(assign_font_int_cmd, prefixed_command);
-    any_mode(set_aux_cmd, prefixed_command);
-    any_mode(set_prev_graf_cmd, prefixed_command);
-    any_mode(set_page_dimen_cmd, prefixed_command);
-    any_mode(set_page_int_cmd, prefixed_command);
-    any_mode(set_box_dimen_cmd, prefixed_command);
-    any_mode(set_tex_shape_cmd, prefixed_command);
-    any_mode(set_etex_shape_cmd, prefixed_command);
-    any_mode(def_char_code_cmd, prefixed_command);
-    any_mode(def_del_code_cmd, prefixed_command);
-    any_mode(extdef_math_code_cmd, prefixed_command);
-    any_mode(extdef_del_code_cmd, prefixed_command);
-    any_mode(def_family_cmd, prefixed_command);
-    any_mode(set_math_param_cmd, prefixed_command);
-    any_mode(set_font_cmd, prefixed_command);
-    any_mode(def_font_cmd, prefixed_command);
-    any_mode(letterspace_font_cmd, prefixed_command);
-    any_mode(copy_font_cmd, prefixed_command);
-    any_mode(set_font_id_cmd, prefixed_command);
-    any_mode(register_cmd, prefixed_command);
-    any_mode(advance_cmd, prefixed_command);
-    any_mode(multiply_cmd, prefixed_command);
-    any_mode(divide_cmd, prefixed_command);
-    any_mode(prefix_cmd, prefixed_command);
-    any_mode(let_cmd, prefixed_command);
-    any_mode(shorthand_def_cmd, prefixed_command);
-    any_mode(read_to_cs_cmd, prefixed_command);
-    any_mode(def_cmd, prefixed_command);
-    any_mode(set_box_cmd, prefixed_command);
-    any_mode(hyph_data_cmd, prefixed_command);
-    any_mode(set_interaction_cmd, prefixed_command);
-    any_mode(after_assignment_cmd,run_after_assignment);
-    any_mode(after_group_cmd,run_after_group);
-    any_mode(in_stream_cmd,open_or_close_in);
-    any_mode(message_cmd,issue_message);
-    any_mode(case_shift_cmd, shift_case);
-    any_mode(xray_cmd, show_whatever);
-    any_mode(normal_cmd, run_normal);
-    any_mode(extension_cmd, run_extension);
-    any_mode(option_cmd, run_option);
-}
-
-@ And here is |main_control| itself.  It is quite short nowadays.
-
- at c
-void main_control(void)
-{
-    main_control_state = goto_next;
-    init_main_control () ;
-
-    if (equiv(every_job_loc) != null)
-        begin_token_list(equiv(every_job_loc), every_job_text);
-
-    while (1) {
-        if (main_control_state == goto_skip_token)
-                main_control_state = goto_next; /* reset */
-        else
-            get_x_token();
-
-        /* Give diagnostic information, if requested */
-        /* When a new token has just been fetched at |big_switch|, we have an
-           ideal place to monitor \TeX's activity. */
-        if (interrupt != 0 && OK_to_interrupt) {
-            back_input();
-            check_interrupt();
-            continue;
-        }
-        if (tracing_commands_par > 0)
-            show_cur_cmd_chr();
-
-        (jump_table[(abs(mode) + cur_cmd)])(); /* run the command */
-
-        if (main_control_state == goto_return) {
-            return;
-        }
-    }
-    return; /* not reached */
-}
-
-@ @c
-void app_space(void)
-{                               /* handle spaces when |space_factor<>1000| */
-    halfword q;                 /* glue node */
-    if ((space_factor_par >= 2000) && (! glue_is_zero(xspace_skip_par))) {
-        q = new_param_glue(xspace_skip_code);
-        /* so from now we have a subtype with spaces: */
-        subtype(q) = xspace_skip_code + 1;
-    } else {
-        if (!glue_is_zero(space_skip_par)) {
-            q = new_glue(space_skip_par);
-        } else {
-            q = new_glue(zero_glue);
-            width(q) = space(cur_font_par);
-            stretch(q) = space_stretch(cur_font_par);
-            shrink(q) = space_shrink(cur_font_par);
-        }
-        /* Modify the glue specification in |q| according to the space factor */
-        if (space_factor_par >= 2000)
-            width(q) = width(q) + extra_space(cur_font_par);
-        stretch(q) = xn_over_d(stretch(q), space_factor_par, 1000);
-        shrink(q) = xn_over_d(shrink(q), 1000, space_factor_par);
-
-        /* so from now we have a subtype with spaces: */
-        subtype(q) = space_skip_code + 1;
-    }
-    couple_nodes(tail, q);
-    tail = q;
-}
-
-@ @c
-void insert_dollar_sign(void)
-{
-    back_input();
-    cur_tok = math_shift_token + '$';
-    print_err("Missing $ inserted");
-    help2("I've inserted a begin-math/end-math symbol since I think",
-          "you left one out. Proceed, with fingers crossed.");
-    ins_error();
-}
-
-@  We can silently ignore  \.{\\par}s in a math formula.
-
- at c
-void insert_dollar_sign_par_end(void)
-{
-    if (!suppress_mathpar_error_par) {
-        insert_dollar_sign() ;
-    }
-}
-
-@ The `|you_cant|' procedure prints a line saying that the current command
-is illegal in the current mode; it identifies these things symbolically.
-
- at c
-void you_cant(void)
-{
-    print_err("You can't use `");
-    print_cmd_chr((quarterword) cur_cmd, cur_chr);
-    print_in_mode(mode);
-}
-
-@
-When erroneous situations arise, \TeX\ usually issues an error message
-specific to the particular error. For example, `\.{\\noalign}' should
-not appear in any mode, since it is recognized by the |align_peek| routine
-in all of its legitimate appearances; a special error message is given
-when `\.{\\noalign}' occurs elsewhere. But sometimes the most appropriate
-error message is simply that the user is not allowed to do what he or she
-has attempted. For example, `\.{\\moveleft}' is allowed only in vertical mode,
-and `\.{\\lower}' only in non-vertical modes.  Such cases are enumerated
-here and in the other sections referred to under `See also \dots.'
-
- at c
-void report_illegal_case(void)
-{
-    you_cant();
-    help4("Sorry, but I'm not programmed to handle this case;",
-          "I'll just pretend that you didn''t ask for it.",
-          "If you're in the wrong mode, you might be able to",
-          "return to the right one by typing `I}' or `I$' or `I\\par'.");
-    error();
-}
-
-@ Some operations are allowed only in privileged modes, i.e., in cases
-that |mode>0|. The |privileged| function is used to detect violations
-of this rule; it issues an error message and returns |false| if the
-current |mode| is negative.
-
- at c
-boolean privileged(void)
-{
-    if (mode > 0) {
-        return true;
-    } else {
-        report_illegal_case();
-        return false;
-    }
-}
-
-@ We don't want to leave |main_control| immediately when a |stop| command
-is sensed, because it may be necessary to invoke an \.{\\output} routine
-several times before things really grind to a halt. (The output routine
-might even say `\.{\\gdef\\end\{...\}}', to prolong the life of the job.)
-Therefore |its_all_over| is |true| only when the current page
-and contribution list are empty, and when the last output was not a
-``dead cycle.''
-
- at c
-boolean its_all_over(void)
-{                               /* do this when \.{\\end} or \.{\\dump} occurs */
-    if (privileged()) {
-        if ((page_head == page_tail) && (head == tail) && (dead_cycles == 0)) {
-            return true;
-        }
-        back_input();           /* we will try to end again after ejecting residual material */
-        tail_append(new_null_box());
-        width(tail) = hsize_par;
-        tail_append(new_glue(fill_glue));
-        tail_append(new_penalty(-010000000000));
-        normal_page_filter(end);
-        build_page();           /* append \.{\\hbox to \\hsize\{\}\\vfill\\penalty-'10000000000} */
-    }
-    return false;
-}
-
-
-@ The |hskip| and |vskip| command codes are used for control sequences
-like \.{\\hss} and \.{\\vfil} as well as for \.{\\hskip} and \.{\\vskip}.
-The difference is in the value of |cur_chr|.
-
-All the work relating to glue creation has been relegated to the
-following subroutine. It does not call |build_page|, because it is
-used in at least one place where that would be a mistake.
-
- at c
-void append_glue(void)
-{
-    int s = cur_chr;
-    switch (s) {
-        case fil_code:
-            cur_val = new_glue(fil_glue);
-            break;
-        case fill_code:
-            cur_val = new_glue(fill_glue);
-            break;
-        case ss_code:
-            cur_val = new_glue(ss_glue);
-            break;
-        case fil_neg_code:
-            cur_val = new_glue(fil_neg_glue);
-            break;
-        case skip_code:
-            scan_glue(glue_val_level);
-            break;
-        case mskip_code:
-            scan_glue(mu_val_level);
-            break;
-    }
-    /* now |cur_val| points to the glue specification */
-    tail_append(new_glue(cur_val));
-    flush_node(cur_val);
-    if (s > skip_code) {
-        subtype(tail) = mu_glue;
-    }
-}
-
-@ @c
-void append_kern(void)
-{
-    int s;                      /* |subtype| of the kern node */
-    s = cur_chr;
-    scan_dimen((s == mu_glue), false, false);
-    tail_append(new_kern(cur_val));
-    subtype(tail) = (quarterword) s;
-}
-
-@ We have to deal with errors in which braces and such things are not
-properly nested. Sometimes the user makes an error of commission by
-inserting an extra symbol, but sometimes the user makes an error of omission.
-\TeX\ can't always tell one from the other, so it makes a guess and tries
-to avoid getting into a loop.
-
-The |off_save| routine is called when the current group code is wrong. It tries
-to insert something into the user's input that will help clean off
-the top level.
-
- at c
-void off_save(void)
-{
-    halfword p, q;              /* inserted token */
-    if (cur_group == bottom_level) {
-        /* Drop current token and complain that it was unmatched */
-        print_err("Extra ");
-        print_cmd_chr((quarterword) cur_cmd, cur_chr);
-        help1("Things are pretty mixed up, but I think the worst is over.");
-        error();
-
-    } else {
-        back_input();
-        p = get_avail();
-        set_token_link(temp_token_head, p);
-        print_err("Missing ");
-        /* Prepare to insert a token that matches |cur_group|, and print what it is */
-        /* At this point, |link(temp_token_head)=p|, a pointer to an empty one-word node. */
-        switch (cur_group) {
-            case semi_simple_group:
-                set_token_info(p, cs_token_flag + frozen_end_group);
-                tprint_esc("endgroup");
-                break;
-            case math_shift_group:
-                set_token_info(p, math_shift_token + '$');
-                print_char('$');
-                break;
-            case math_left_group:
-                set_token_info(p, cs_token_flag + frozen_right);
-                q = get_avail();
-                set_token_link(p, q);
-                p = token_link(p);
-                set_token_info(p, other_token + '.');
-                tprint_esc("right.");
-                break;
-            default:
-                set_token_info(p, right_brace_token + '}');
-                print_char('}');
-                break;
-        }
-        tprint(" inserted");
-        ins_list(token_link(temp_token_head));
-        help5("I've inserted something that you may have forgotten.",
-              "(See the <inserted text> above.)",
-              "With luck, this will get me unwedged. But if you",
-              "really didn't forget anything, try typing `2' now; then",
-              "my insertion and my current dilemma will both disappear.");
-        error();
-    }
-}
-
-@ The routine for a |right_brace| character branches into many subcases,
-since a variety of things may happen, depending on |cur_group|. Some
-types of groups are not supposed to be ended by a right brace; error
-messages are given in hopes of pinpointing the problem. Most branches
-of this routine will be filled in later, when we are ready to understand
-them; meanwhile, we must prepare ourselves to deal with such errors.
-
- at c
-void handle_right_brace(void)
-{
-    halfword p, q;              /* for short-term use */
-    scaled d;                   /* holds |split_max_depth| in |insert_group| */
-    int f;                      /* holds |floating_penalty| in |insert_group| */
-    p = null;
-    switch (cur_group) {
-        case simple_group:
-            fixup_directions();
-            break;
-        case bottom_level:
-            print_err("Too many }'s");
-            help2("You've closed more groups than you opened.",
-                  "Such booboos are generally harmless, so keep going.");
-            error();
-            break;
-        case semi_simple_group:
-        case math_shift_group:
-        case math_left_group:
-            extra_right_brace();
-            break;
-        case hbox_group:
-            /* When the right brace occurs at the end of an \.{\\hbox} or \.{\\vbox} or
-               \.{\\vtop} construction, the |package| routine comes into action. We might
-               also have to finish a paragraph that hasn't ended. */
-            package(0);
-            break;
-        case adjusted_hbox_group:
-            adjust_tail = adjust_head;
-            pre_adjust_tail = pre_adjust_head;
-            package(0);
-            break;
-        case vbox_group:
-            end_graf(vbox_group);
-            package(0);
-            break;
-        case vtop_group:
-            end_graf(vtop_group);
-            package(vtop_code);
-            break;
-        case insert_group:
-            end_graf(insert_group);
-            q = new_glue(split_top_skip_par);
-            d = split_max_depth_par;
-            f = floating_penalty_par;
-            unsave();
-            save_ptr--;
-            /* now |saved_value(0)| is the insertion number, or the |vadjust| subtype */
-            p = vpack(vlink(head), 0, additional, -1);
-            pop_nest();
-            if (saved_type(0) == saved_insert) {
-                tail_append(new_node(ins_node, saved_value(0)));
-                height(tail) = height(p) + depth(p);
-                ins_ptr(tail) = list_ptr(p);
-                split_top_ptr(tail) = q;
-                depth(tail) = d;
-                float_cost(tail) = f;
-            } else if (saved_type(0) == saved_adjust) {
-                tail_append(new_node(adjust_node, saved_value(0)));
-                adjust_ptr(tail) = list_ptr(p);
-                flush_node(q);
-            } else {
-                confusion("insert_group");
-            }
-            list_ptr(p) = null;
-            flush_node(p);
-            if (nest_ptr == 0) {
-                checked_page_filter(insert);
-                build_page();
-            }
-            break;
-        case output_group:
-            /* this is needed in case the \.{\\output} executes a \.{\\textdir} command. */
-            if (dir_level(text_dir_ptr) == cur_level) {
-                /* DIR: Remove from |text_dir_ptr| */
-                halfword text_dir_tmp = vlink(text_dir_ptr);
-                flush_node(text_dir_ptr);
-                text_dir_ptr = text_dir_tmp;
-            }
-            resume_after_output();
-            break;
-        case disc_group:
-            build_discretionary();
-            break;
-        case local_box_group:
-            build_local_box();
-            break;
-        case align_group:
-            back_input();
-            cur_tok = cs_token_flag + frozen_cr;
-            print_err("Missing \\cr inserted");
-            help1("I'm guessing that you meant to end an alignment here.");
-            ins_error();
-            break;
-        case no_align_group:
-            end_graf(no_align_group);
-            unsave();
-            align_peek();
-            break;
-        case vcenter_group:
-            end_graf(vcenter_group);
-            finish_vcenter();
-            break;
-        case math_choice_group:
-            build_choices();
-            break;
-        case math_group:
-            close_math_group(p);
-            break;
-        default:
-            confusion("rightbrace");
-            break;
-    }
-}
-
-@ @c
-void extra_right_brace(void)
-{
-    print_err("Extra }, or forgotten ");
-    switch (cur_group) {
-        case semi_simple_group:
-            tprint_esc("endgroup");
-            break;
-        case math_shift_group:
-            print_char('$');
-            break;
-        case math_left_group:
-            tprint_esc("right");
-            break;
-    }
-    help5("I've deleted a group-closing symbol because it seems to be",
-          "spurious, as in `$x}$'. But perhaps the } is legitimate and",
-          "you forgot something else, as in `\\hbox{$x}'. In such cases",
-          "the way to recover is to insert both the forgotten and the",
-          "deleted material, e.g., by typing `I$}'.");
-    error();
-    incr(align_state);
-}
-
-@ Here is where we clear the parameters that are supposed to revert to their
-default values after every paragraph and when internal vertical mode is entered.
-
- at c
-void normal_paragraph(void)
-{
-    if (looseness_par != 0)
-        eq_word_define(int_base + looseness_code, 0);
-    if (hang_indent_par != 0)
-        eq_word_define(dimen_base + hang_indent_code, 0);
-    if (hang_after_par != 1)
-        eq_word_define(int_base + hang_after_code, 1);
-    if (par_shape_par_ptr != null)
-        eq_define(par_shape_loc, shape_ref_cmd, null);
-    if (inter_line_penalties_par_ptr != null)
-        eq_define(inter_line_penalties_loc, shape_ref_cmd, null);
-    if (shape_mode_par > 0)
-        eq_word_define(dimen_base + shape_mode_code, 0);
-}
-
-@ The global variable |cur_box| will point to a newly-made box. If the box
-is void, we will have |cur_box=null|. Otherwise we will have
-|type(cur_box)=hlist_node| or |vlist_node| or |rule_node|; the |rule_node|
-case can occur only with leaders.
-
- at c
-halfword cur_box;               /* box to be placed into its context */
-
-@ The |box_end| procedure does the right thing with |cur_box|, if
-|box_context| represents the context as explained above.
-
- at c
-void box_end(int box_context)
-{
-    if (box_context < box_flag) {
-        /* Append box |cur_box| to the current list, shifted by |box_context| */
-        /*
-           The global variable |adjust_tail| will be non-null if and only if the
-           current box might include adjustments that should be appended to the
-           current vertical list.
-         */
-        if (cur_box != null) {
-            shift_amount(cur_box) = box_context;
-            if (abs(mode) == vmode) {
-                if (pre_adjust_tail != null) {
-                    if (pre_adjust_head != pre_adjust_tail)
-                        append_list(pre_adjust_head, pre_adjust_tail);
-                    pre_adjust_tail = null;
-                }
-                append_to_vlist(cur_box,lua_key_index(box));
-                if (adjust_tail != null) {
-                    if (adjust_head != adjust_tail)
-                        append_list(adjust_head, adjust_tail);
-                    adjust_tail = null;
-                }
-                if (mode > 0) {
-                    checked_page_filter(box);
-                    build_page();
-                }
-            } else {
-                if (abs(mode) == hmode)
-                    space_factor_par = 1000;
-                else
-                    cur_box = new_sub_box(cur_box);
-                couple_nodes(tail, cur_box);
-                tail = cur_box;
-            }
-        }
-    } else if (box_context < ship_out_flag) {
-        /* Store |cur_box| in a box register */
-        if (box_context < global_box_flag)
-            eq_define(box_base + box_context - box_flag, box_ref_cmd, cur_box);
-        else
-            geq_define(box_base + box_context - global_box_flag, box_ref_cmd, cur_box);
-    } else if (cur_box != null) {
-        if (box_context > ship_out_flag) {
-            /* Append a new leader node that uses |cur_box| */
-            /* Get the next non-blank non-relax... */
-            do {
-                get_x_token();
-            } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));
-            if (((cur_cmd == hskip_cmd) && (abs(mode) != vmode)) ||
-                ((cur_cmd == vskip_cmd) && (abs(mode) == vmode))) {
-                append_glue();
-                subtype(tail) = (quarterword) (box_context - (leader_flag - a_leaders));
-                leader_ptr(tail) = cur_box;
-            } else {
-                print_err("Leaders not followed by proper glue");
-                help3
-                    ("You should say `\\leaders <box or rule><hskip or vskip>'.",
-                     "I found the <box or rule>, but there's no suitable",
-                     "<hskip or vskip>, so I'm ignoring these leaders.");
-                back_error();
-                flush_node_list(cur_box);
-            }
-        } else {
-            ship_out(static_pdf, cur_box, SHIPPING_PAGE);
-        }
-    }
-}
-
-@ the next input should specify a box or perhaps a rule
-
- at c
-void scan_box(int box_context)
-{
-    /* Get the next non-blank non-relax... */
-    do {
-        get_x_token();
-    } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));
-    if (cur_cmd == make_box_cmd) {
-        begin_box(box_context);
-    } else if ((box_context >= leader_flag) &&
-            ((cur_cmd == hrule_cmd) || (cur_cmd == vrule_cmd) ||
-             (cur_cmd == no_hrule_cmd) || (cur_cmd == no_vrule_cmd))) {
-        cur_box = scan_rule_spec();
-        box_end(box_context);
-    } else {
-        print_err("A <box> was supposed to be here");
-        help3("I was expecting to see \\hbox or \\vbox or \\copy or \\box or",
-              "something like that. So you might find something missing in",
-              "your output. But keep trying; you can fix this later.");
-        back_error();
-    }
-}
-
-@ @c
-void new_graf(boolean indented)
-{
-    halfword p, q, dir_graf_tmp;
-    halfword dir_rover;
-    prev_graf_par = 0;
-    if ((mode == vmode) || (head != tail)) {
-        tail_append(new_param_glue(par_skip_code));
-    }
-    push_nest();
-    mode = hmode;
-    space_factor_par = 1000;
-    /* LOCAL: Add local paragraph node */
-    tail_append(make_local_par_node(new_graf_par_code));
-    if (indented) {
-        p = new_null_box();
-        box_dir(p) = par_direction_par;
-        width(p) = par_indent_par;
-        subtype(p) = indent_list;
-        q = tail;
-        tail_append(p);
-    } else {
-        q = tail;
-    }
-    dir_rover = text_dir_ptr;
-    while (dir_rover != null) {
-        if ((vlink(dir_rover) != null) || (dir_dir(dir_rover) != par_direction_par)) {
-            dir_graf_tmp = new_dir(dir_dir(dir_rover));
-            try_couple_nodes(dir_graf_tmp,vlink(q));
-            couple_nodes(q,dir_graf_tmp);
-        }
-        dir_rover = vlink(dir_rover);
-    }
-    q = head;
-    while (vlink(q) != null)
-        q = vlink(q);
-    tail = q;
-    if (every_par_par != null)
-        begin_token_list(every_par_par, every_par_text);
-    if (nest_ptr == 1) {
-        checked_page_filter(new_graf);
-        build_page();           /* put |par_skip| glue on current page */
-    }
-}
-
-@ @c
-void indent_in_hmode(void)
-{
-    halfword p;
-    if (cur_chr > 0) {          /* \.{\\indent} */
-        p = new_null_box();
-        width(p) = par_indent_par;
-        if (abs(mode) == hmode)
-            space_factor_par = 1000;
-        else
-            p = new_sub_box(p);
-        tail_append(p);
-    }
-}
-
-@ @c
-void head_for_vmode(void)
-{
-    if (mode < 0) {
-        if ((cur_cmd != hrule_cmd) && (cur_cmd != no_hrule_cmd)) {
-            off_save();
-        } else {
-            print_err("You can't use `\\hrule' here except with leaders");
-            help2("To put a horizontal rule in an hbox or an alignment,",
-                  "you should use \\leaders or \\hrulefill (see The TeXbook).");
-            error();
-        }
-    } else {
-        back_input();
-        cur_tok = par_token;
-        back_input();
-        token_type = inserted;
-    }
-}
-
-@ TODO (BUG?): |dir_save| would have been set by |line_break| by means
-of |post_line_break|, but this is not done right now, as it introduces
-pretty heavy memory leaks. This means the current code is probably
-wrong in some way that relates to in-paragraph displays.
-
- at c
-void end_graf(int line_break_context)
-{
-    if (mode == hmode) {
-        if ((head == tail) || (vlink(head) == tail)) {
-            if (vlink(head) == tail)
-                flush_node(vlink(head));
-            pop_nest();         /* null paragraphs are ignored, all contain a |local_paragraph| node */
-        } else {
-            line_break(false, line_break_context);
-        }
-        if (dir_save != null) {
-            flush_node_list(dir_save);
-            dir_save = null;
-        }
-        normal_paragraph();
-        error_count = 0;
-    }
-}
-
-@ @c
-void begin_insert_or_adjust(void)
-{
-    if (cur_cmd != vadjust_cmd) {
-        scan_register_num();
-        if (cur_val == output_box_par) {
-            print_err("You can't \\insert");
-            print_int(output_box_par);
-            help1("I'm changing to \\insert0; box \\outputbox is special.");
-            error();
-            cur_val = 0;
-        }
-        set_saved_record(0, saved_insert, 0, cur_val);
-    } else if (scan_keyword("pre")) {
-        set_saved_record(0, saved_adjust, 0, 1);
-    } else {
-        set_saved_record(0, saved_adjust, 0, 0);
-    }
-    save_ptr++;
-    new_save_level(insert_group);
-    scan_left_brace();
-    normal_paragraph();
-    push_nest();
-    mode = -vmode;
-    prev_depth_par = ignore_depth;
-}
-
-@ I (TH)'ve renamed the |make_mark| procedure to this, because if the
-current chr code is 1, then the actual command was \.{\\clearmarks},
-which does not generate a mark node but instead destroys the current
-mark tokenlists.
-
- at c
-void handle_mark(void)
-{
-    halfword p;                 /* new node */
-    halfword c;                 /* the mark class */
-    if (cur_chr == clear_marks_code) {
-        scan_mark_num();
-        c = cur_val;
-        delete_top_mark(c);
-        delete_bot_mark(c);
-        delete_first_mark(c);
-        delete_split_first_mark(c);
-        delete_split_bot_mark(c);
-    } else {
-        if (cur_chr == 0) {
-            c = 0;
-        } else {
-            scan_mark_num();
-            c = cur_val;
-            if (c > biggest_used_mark)
-                biggest_used_mark = c;
-        }
-        p = scan_toks(false, true);
-        p = new_node(mark_node, 0);     /* the |subtype| is not used */
-        mark_class(p) = c;
-        mark_ptr(p) = def_ref;
-        couple_nodes(tail, p);
-        tail = p;
-    }
-}
-
-@ @c
-void append_penalty(void)
-{
-    scan_int();
-    tail_append(new_penalty(cur_val));
-    if (mode == vmode) {
-        checked_page_filter(penalty);
-        build_page();
-    }
-}
-
-@ When |delete_last| is called, |cur_chr| is the |type| of node that
-will be deleted, if present.
-
-The |remove_item| command removes a penalty, kern, or glue node if it
-appears at the tail of the current list, using a brute-force linear scan.
-Like \.{\\lastbox}, this command is not allowed in vertical mode (except
-internal vertical mode), since the current list in vertical mode is sent
-to the page builder.  But if we happen to be able to implement it in
-vertical mode, we do.
-
- at c
-void delete_last(void)
-{
-    halfword p, q;              /* run through the current list */
-    if ((mode == vmode) && (tail == head)) {
-        /* Apologize for inability to do the operation now,
-           unless \.{\\unskip} follows non-glue */
-        if ((cur_chr != glue_node) || (last_glue != max_halfword)) {
-            you_cant();
-            if (cur_chr == kern_node) {
-                help2
-                    ("Sorry...I usually can't take things from the current page.",
-                     "Try `I\\kern-\\lastkern' instead.");
-            } else if (cur_chr != glue_node) {
-                help2
-                    ("Sorry...I usually can't take things from the current page.",
-                     "Perhaps you can make the output routine do it.");
-            } else {
-                help2
-                    ("Sorry...I usually can't take things from the current page.",
-                     "Try `I\\vskip-\\lastskip' instead.");
-            }
-            error();
-        }
-    } else {
-        /* todo: clean this up */
-        if (!is_char_node(tail)) {
-            if (type(tail) == cur_chr) {
-                q = head;
-                do {
-                    p = q;
-                    if (!is_char_node(q)) {
-                        if (type(q) == disc_node) {
-                            if (p == tail)
-                                return;
-                        }
-                    }
-                    q = vlink(p);
-                } while (q != tail);
-                vlink(p) = null;
-                flush_node_list(tail);
-                tail = p;
-            }
-        }
-    }
-}
-
-@ @c
-void unpackage(void)
-{
-    halfword p;                 /* the box */
-    halfword r;                 /* to remove marginal kern nodes */
-    int c;                      /* should we copy? */
-    halfword s;                 /* for varmem assignment */
-    if (cur_chr > copy_code) {
-        /* Handle saved items and |goto done| */
-        try_couple_nodes(tail, disc_ptr[cur_chr]);
-        disc_ptr[cur_chr] = null;
-        goto DONE;
-    }
-    c = cur_chr;
-    scan_register_num();
-    p = box(cur_val);
-    if (p == null)
-        return;
-    if ((abs(mode) == mmode)
-        || ((abs(mode) == vmode) && (type(p) != vlist_node))
-        || ((abs(mode) == hmode) && (type(p) != hlist_node))) {
-        print_err("Incompatible list can't be unboxed");
-        help3("Sorry, Pandora. (You sneaky devil.)",
-              "I refuse to unbox an \\hbox in vertical mode or vice versa.",
-              "And I can't open any boxes in math mode.");
-        error();
-        return;
-    }
-    if (c == copy_code) {
-        s = copy_node_list(list_ptr(p));
-        try_couple_nodes(tail,s);
-    } else {
-        try_couple_nodes(tail,list_ptr(p));
-        box(cur_val) = null;
-        list_ptr(p) = null;
-        flush_node(p);
-    }
-  DONE:
-    while (vlink(tail) != null) {
-        r = vlink(tail);
-        if (!is_char_node(r) && (type(r) == margin_kern_node)) {
-            try_couple_nodes(tail,vlink(r));
-            flush_node(r);
-        }
-        tail = vlink(tail);
-    }
-}
-
-@
-Italic corrections are converted to kern nodes when the |ital_corr| command
-follows a character. In math mode the same effect is achieved by appending
-a kern of zero here, since italic corrections are supplied later.
-
- at c
-void append_italic_correction(void)
-{
-    halfword p;                 /* |char_node| at the tail of the current list */
-    internal_font_number f;     /* the font in the |char_node| */
-    if (tail != head) {
-        if (is_char_node(tail))
-            p = tail;
-        else
-            return;
-        f = font(p);
-        tail_append(new_kern(char_italic(f, character(p))));
-        subtype(tail) = italic_kern;
-    }
-}
-
-@ @c
-void append_local_box(int kind)
-{
-    incr(save_ptr);
-    set_saved_record(-1, saved_boxtype, 0, kind);
-    new_save_level(local_box_group);
-    scan_left_brace();
-    push_nest();
-    mode = -hmode;
-    space_factor_par = 1000;
-}
-
-@ Discretionary nodes are easy in the common case `\.{\\-}', but in the
-general case we must process three braces full of items.
-
-The space factor does not change when we append a discretionary node,
-but it starts out as 1000 in the subsidiary lists.
-
- at c
-void append_discretionary(void)
-{
-    int c;
-    tail_append(new_disc());
-    subtype(tail) = (quarterword) cur_chr;
-    if (cur_chr == explicit_disc) {
-        /* \- */
-        c = get_pre_hyphen_char(cur_lang_par);
-        if (c != 0) {
-            vlink(pre_break(tail)) = new_char(equiv(cur_font_loc), c);
-            alink(vlink(pre_break(tail))) = pre_break(tail);
-            tlink(pre_break(tail)) = vlink(pre_break(tail));
-        }
-        c = get_post_hyphen_char(cur_lang_par);
-        if (c != 0) {
-            vlink(post_break(tail)) = new_char(equiv(cur_font_loc), c);
-            alink(vlink(post_break(tail))) = post_break(tail);
-            tlink(post_break(tail)) = vlink(post_break(tail));
-        }
-        disc_penalty(tail) = ex_hyphen_penalty_par;
-    } else {
-        /* \discretionary */
-        if (scan_keyword("penalty")) {
-            scan_int();
-            disc_penalty(tail) = cur_val;
-        }
-        incr(save_ptr);
-        set_saved_record(-1, saved_disc, 0, 0);
-        new_save_level(disc_group);
-        scan_left_brace();
-        push_nest();
-        mode = -hmode;
-        space_factor_par = 1000;
-        /* already preset: disc_penalty(tail) = hyphen_penalty_par; */
-    }
-}
-
-@ The test for |p != null| ensures that empty \.{\\localleftbox} and
-    \.{\\localrightbox} commands are not applied.
-
- at c
-void build_local_box(void)
-{
-    halfword p;
-    int kind;
-    unsave();
-    assert(saved_type(-1) == saved_boxtype);
-    kind = saved_value(-1);
-    decr(save_ptr);
-    p = vlink(head);
-    pop_nest();
-    if (p != null)
-        p = hpack(p, 0, additional, -1);
-    if (kind == 0)
-        eq_define(local_left_box_base, box_ref_cmd, p);
-    else
-        eq_define(local_right_box_base, box_ref_cmd, p);
-    if (abs(mode) == hmode) {
-        /* LOCAL: Add local paragraph node */
-        tail_append(make_local_par_node(local_box_par_code));
-    }
-    eq_word_define(int_base + no_local_whatsits_code, no_local_whatsits_par + 1);
-}
-
-@ The three discretionary lists are constructed somewhat as if they were
-hboxes. A~subroutine called |build_discretionary| handles the transitions.
-(This is sort of fun.)
-
- at c
-void build_discretionary(void)
-{
-    halfword p, q;              /* for link manipulation */
-    int n;                      /* length of discretionary list */
-    unsave();
-    /* Prune the current list, if necessary, until it contains only
-       |char_node|, |kern_node|, |hlist_node|, |vlist_node| and
-       |rule_node| items; set |n| to the length of the list,
-       and set |q| to the lists tail */
-    /* During this loop, |p=vlink(q)| and there are |n| items preceding |p|. */
-    q = head;
-    p = vlink(q);
-    n = 0;
-    while (p != null) {
-        if (!is_char_node(p) && type(p) > rule_node && type(p) != kern_node) {
-            print_err("Improper discretionary list");
-            help1("Discretionary lists must contain only boxes and kerns.");
-            error();
-            begin_diagnostic();
-            tprint_nl("The following discretionary sublist has been deleted:");
-            show_box(p);
-            end_diagnostic(true);
-            flush_node_list(p);
-            vlink(q) = null;
-            break;
-        }
-        alink(p) = q;
-        q = p;
-        p = vlink(q);
-        incr(n);
-    }
-
-    p = vlink(head);
-    pop_nest();
-    assert(saved_type(-1) == saved_disc);
-    switch (saved_value(-1)) {
-    case 0:
-        if (n > 0) {
-            vlink(pre_break(tail)) = p;
-            alink(p) = pre_break(tail);
-            tlink(pre_break(tail)) = q;
-        }
-        break;
-    case 1:
-        if (n > 0) {
-            vlink(post_break(tail)) = p;
-            alink(p) = post_break(tail);
-            tlink(post_break(tail)) = q;
-        }
-        break;
-    case 2:
-        /* Attach list |p| to the current list, and record its length;
-           then finish up and |return| */
-        if ((n > 0) && (abs(mode) == mmode)) {
-            print_err("Illegal math \\discretionary");
-            help2("Sorry: The third part of a discretionary break must be",
-                  "empty, in math formulas. I had to delete your third part.");
-            flush_node_list(p);
-            error();
-        } else {
-            if (n > 0) {
-                vlink(no_break(tail)) = p;
-                alink(p) = no_break(tail);
-                tlink(no_break(tail)) = q;
-            }
-        }
-        decr(save_ptr);
-        return;
-        break;
-    }                           /* there are no other cases */
-    set_saved_record(-1, saved_disc, 0, (saved_value(-1) + 1));
-    new_save_level(disc_group);
-    scan_left_brace();
-    push_nest();
-    mode = -hmode;
-    space_factor_par = 1000;
-}
-
-@ The positioning of accents is straightforward but tedious. Given an accent
-of width |a|, designed for characters of height |x| and slant |s|;
-and given a character of width |w|, height |h|, and slant |t|: We will shift
-the accent down by |x-h|, and we will insert kern nodes that have the effect of
-centering the accent over the character and shifting the accent to the
-right by $\delta={1\over2}(w-a)+h\cdot t-x\cdot s$.  If either character is
-absent from the font, we will simply use the other, without shifting.
-
- at c
-void make_accent(void)
-{
-    double s, t;                /* amount of slant */
-    halfword p, q, r;           /* character, box, and kern nodes */
-    internal_font_number f;     /* relevant font */
-    scaled a, h, x, w, delta;   /* heights and widths, as explained above */
-    scan_char_num();
-    f = equiv(cur_font_loc);
-    p = new_glyph(f, cur_val);
-    if (p != null) {
-        x = x_height(f);
-        s = float_cast(slant(f)) / float_constant(65536);       /* real division */
-        a = glyph_width(p);
-        do_assignments();
-        /* Create a character node |q| for the next character,
-           but set |q:=null| if problems arise */
-        q = null;
-        f = equiv(cur_font_loc);
-        if ((cur_cmd == letter_cmd) ||
-            (cur_cmd == other_char_cmd) || (cur_cmd == char_given_cmd)) {
-            q = new_glyph(f, cur_chr);
-        } else if (cur_cmd == char_num_cmd) {
-            scan_char_num();
-            q = new_glyph(f, cur_val);
-        } else {
-            back_input();
-        }
-
-        if (q != null) {
-            /* Append the accent with appropriate kerns, then set |p:=q| */
-            /* The kern nodes appended here must be distinguished from other kerns, lest
-               they be wiped away by the hyphenation algorithm or by a previous line break.
-
-               The two kerns are computed with (machine-dependent) |real| arithmetic, but
-               their sum is machine-independent; the net effect is machine-independent,
-               because the user cannot remove these nodes nor access them via \.{\\lastkern}.
-             */
-            t = float_cast(slant(f)) / float_constant(65536);   /* real division */
-            w = glyph_width(q);
-            h = glyph_height(q);
-            if (h != x) {       /* the accent must be shifted up or down */
-                p = hpack(p, 0, additional, -1);
-                shift_amount(p) = x - h;
-            }
-            delta = round(float_cast(w - a) / float_constant(2) + h * t - x * s);       /* real multiplication */
-            r = new_kern(delta);
-            subtype(r) = accent_kern;
-            couple_nodes(tail, r);
-            couple_nodes(r, p);
-            tail = new_kern(-a - delta);
-            subtype(tail) = accent_kern;
-            couple_nodes(p, tail);
-            p = q;
-
-        }
-        couple_nodes(tail, p);
-        tail = p;
-        space_factor_par = 1000;
-    }
-}
-
-@ 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.
-
- at c
-void align_error(void)
-{
-    if (abs(align_state) > 2) {
-        /* Express consternation over the fact that no alignment is in progress */
-        print_err("Misplaced ");
-        print_cmd_chr((quarterword) cur_cmd, cur_chr);
-        if (cur_tok == tab_token + '&') {
-            help6("I can't figure out why you would want to use a tab mark",
-                  "here. If you just want an ampersand, the remedy is",
-                  "simple: Just type `I\\&' now. But if some right brace",
-                  "up above has ended a previous alignment prematurely,",
-                  "you're probably due for more error messages, and you",
-                  "might try typing `S' now just to see what is salvageable.");
-        } else {
-            help5("I can't figure out why you would want to use a tab mark",
-                  "or \\cr or \\span just now. If something like a right brace",
-                  "up above has ended a previous alignment prematurely,",
-                  "you're probably due for more error messages, and you",
-                  "might try typing `S' now just to see what is salvageable.");
-        }
-        error();
-
-    } else {
-        back_input();
-        if (align_state < 0) {
-            print_err("Missing { inserted");
-            incr(align_state);
-            cur_tok = left_brace_token + '{';
-        } else {
-            print_err("Missing } inserted");
-            decr(align_state);
-            cur_tok = right_brace_token + '}';
-        }
-        help3("I've put in what seems to be necessary to fix",
-              "the current column of the current alignment.",
-              "Try to go on, since this might almost work.");
-        ins_error();
-    }
-}
-
-@ The help messages here contain a little white lie, since \.{\\noalign}
-and \.{\\omit} are allowed also after `\.{\\noalign\{...\}}'.
-
- at c
-void no_align_error(void)
-{
-    print_err("Misplaced \\noalign");
-    help2("I expect to see \\noalign only after the \\cr of",
-          "an alignment. Proceed, and I'll ignore this case.");
-    error();
-}
-
-void omit_error(void)
-{
-    print_err("Misplaced \\omit");
-    help2("I expect to see \\omit only after tab marks or the \\cr of",
-          "an alignment. Proceed, and I'll ignore this case.");
-    error();
-}
-
-@ 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 |fin_align| removes it.
-
-A devious user might force an |endv| command to occur just about anywhere;
-we must defeat such hacks.
-
- at c
-void do_endv(void)
-{
-    base_ptr = input_ptr;
-    input_stack[base_ptr] = cur_input;
-    while ((input_stack[base_ptr].index_field != v_template) &&
-           (input_stack[base_ptr].loc_field == null) &&
-           (input_stack[base_ptr].state_field == token_list))
-        decr(base_ptr);
-    if ((input_stack[base_ptr].index_field != v_template) ||
-        (input_stack[base_ptr].loc_field != null) ||
-        (input_stack[base_ptr].state_field != token_list))
-        fatal_error("(interwoven alignment preambles are not allowed)");
-    /*.interwoven alignment preambles... */
-    if (cur_group == align_group) {
-        end_graf(align_group);
-        if (fin_col())
-            fin_row();
-    } else {
-        off_save();
-    }
-}
-
-@ Finally, \.{\\endcsname} is not supposed to get through to |main_control|.
-
- at c
-void cs_error(void)
-{
-    print_err("Extra \\endcsname");
-    help1("I'm ignoring this, since I wasn't doing a \\csname.");
-    error();
-}
-
-@
-  Assignments to values in |eqtb| can be global or local. Furthermore, a
-  control sequence can be defined to be `\.{\\long}', `\.{\\protected}',
-  or `\.{\\outer}', and it might or might not be expanded. The prefixes
-  `\.{\\global}', `\.{\\long}', `\.{\\protected}',
-  and `\.{\\outer}' can occur in any order. Therefore we assign binary numeric
-  codes, making it possible to accumulate the union of all specified prefixes
-  by adding the corresponding codes.  (PASCAL's |set| operations could also
-  have been used.)
-
-  Every prefix, and every command code that might or might not be prefixed,
-  calls the action procedure |prefixed_command|. This routine accumulates
-  a sequence of prefixes until coming to a non-prefix, then it carries out
-  the command.
-
-@ If the user says, e.g., `\.{\\global\\global}', the redundancy is
-silently accepted.
-
-
-@ The different types of code values have different legal ranges; the
-following program is careful to check each case properly.
-
- at c
-#define check_def_code(A) do {						\
-	if (((cur_val<0)&&(p<(A)))||(cur_val>n)) {			\
-	    print_err("Invalid code (");				\
-	    print_int(cur_val);						\
-	    if (p<(A))							\
-		tprint("), should be in the range 0..");		\
-	    else							\
-		tprint("), should be at most ");			\
-	    print_int(n);						\
-	    help1("I'm going to use 0 instead of that illegal code value."); \
-	    error();							\
-	    cur_val=0;							\
-	}								\
-} while (0)
-
-@ @c
-/*
-halfword swap_hang_indent(halfword indentation, halfword shape_mode) {
-    if (shape_mode == 1 || shape_mode == 3 || shape_mode == -1 || shape_mode == -3) {
-        return negate(indentation);
-    } else {
-        return indentation;
-    }
-}
-
-halfword swap_parshape_indent(halfword indentation, halfword width, halfword shape_mode) {
-    if (shape_mode == 2 || shape_mode == 3 || shape_mode == -2 || shape_mode == -3) {
-        return hsize_par - width - indentation;
-    } else {
-        return indentation;
-    }
-}
-
-*/
-
-void prefixed_command(void)
-{
-    int a;                      /* accumulated prefix codes so far */
-    internal_font_number f;     /* identifies a font */
-    halfword j;                 /* index into a \.{\\parshape} specification */
-    halfword p, q;              /* for temporary short-term use */
-    int n;                      /* ditto */
-    boolean e, check_glue;      /* should a definition be expanded? or was \.{\\let} not done? */
-    mathcodeval mval;           /* for handling of \.{\\mathchardef}s */
-    a = 0;
-    while (cur_cmd == prefix_cmd) {
-        if (!odd(a / cur_chr))
-            a = a + cur_chr;
-        /* Get the next non-blank non-relax... */
-        do {
-            get_x_token();
-        } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));
-
-        if (cur_cmd <= max_non_prefixed_command) {
-            /* Discard erroneous prefixes and |return| */
-            print_err("You can't use a prefix with `");
-            print_cmd_chr((quarterword) cur_cmd, cur_chr);
-            print_char('\'');
-            help2
-                ("I'll pretend you didn't say \\long or \\outer or \\global or",
-                 "\\protected.");
-            back_error();
-            return;
-        }
-        if (tracing_commands_par > 2)
-            show_cur_cmd_chr();
-    }
-    /* Discard the prefixes \.{\\long} and \.{\\outer} if they are irrelevant */
-    if (a >= 8) {
-        j = protected_token;
-        a = a - 8;
-    } else {
-        j = 0;
-    }
-    if ((cur_cmd != def_cmd) && ((a % 4 != 0) || (j != 0))) {
-        print_err("You can't use `\\long' or `\\outer' or `\\protected' with `");
-        print_cmd_chr((quarterword) cur_cmd, cur_chr);
-        print_char('\'');
-        help1("I'll pretend you didn't say \\long or \\outer or \\protected here.");
-        error();
-    }
-    /* Adjust for the setting of \.{\\globaldefs} */
-    if (global_defs_par != 0) {
-        if (global_defs_par < 0) {
-            if (is_global(a))
-                a = a - 4;
-        } else {
-            if (!is_global(a))
-                a = a + 4;
-        }
-    }
-    switch (cur_cmd) {
-        case set_font_cmd:
-            /* Here's an example of the way many of the following routines operate.
-               (Unfortunately, they aren't all as simple as this.) */
-            define(cur_font_loc, data_cmd, cur_chr);
-            break;
-        case def_cmd:
-            /* When a |def| command has been scanned,
-               |cur_chr| is odd if the definition is supposed to be global, and
-               |cur_chr>=2| if the definition is supposed to be expanded. */
-
-            if (odd(cur_chr) && !is_global(a) && (global_defs_par >= 0))
-                a = a + 4;
-            e = (cur_chr >= 2);
-            get_r_token();
-            p = cur_cs;
-            q = scan_toks(true, e);
-            if (j != 0) {
-                q = get_avail();
-                set_token_info(q, j);
-                set_token_link(q, token_link(def_ref));
-                set_token_link(def_ref, q);
-            }
-            define(p, call_cmd + (a % 4), def_ref);
-            break;
-        case let_cmd:
-            n = cur_chr;
-            if (n == normal) {
-                get_r_token();
-                p = cur_cs;
-                do {
-                    get_token();
-                } while (cur_cmd == spacer_cmd);
-                if (cur_tok == other_token + '=') {
-                    get_token();
-                    if (cur_cmd == spacer_cmd)
-                        get_token();
-                }
-            } else if (n == normal + 1) {
-                /* futurelet */
-                get_r_token();
-                p = cur_cs;
-                get_token();
-                q = cur_tok;
-                get_token();
-                back_input();
-                cur_tok = q;
-                /* look ahead, then back up */
-                /* note that |back_input| doesn't affect |cur_cmd|, |cur_chr| */
-                back_input();
-            } else {
-                /* letcharcode */
-                scan_int();
-                if (cur_val > 0) {
-                    cur_cs = active_to_cs(cur_val, true);
-                    set_token_info(cur_cs, cur_cs + cs_token_flag);
-                    p = cur_cs;
-                    do {
-                        get_token();
-                    } while (cur_cmd == spacer_cmd);
-                    if (cur_tok == other_token + '=') {
-                        get_token();
-                        if (cur_cmd == spacer_cmd)
-                            get_token();
-                    }
-                } else {
-                    p = null;
-                    tex_error("invalid number for \\letcharcode",NULL);
-                }
-            }
-            if (cur_cmd >= call_cmd)
-                add_token_ref(cur_chr);
-            define(p, cur_cmd, cur_chr);
-            break;
-        case shorthand_def_cmd:
-            /* We temporarily define |p| to be |relax|, so that an occurrence of |p|
-               while scanning the definition will simply stop the scanning instead of
-               producing an ``undefined control sequence'' error or expanding the
-               previous meaning.  This allows, for instance, `\.{\\chardef\\foo=123\\foo}'.
-             */
-            n = cur_chr;
-            get_r_token();
-            p = cur_cs;
-            define(p, relax_cmd, too_big_char);
-            scan_optional_equals();
-            switch (n) {
-            case char_def_code:
-                scan_char_num();
-                define(p, char_given_cmd, cur_val);
-                break;
-            case math_char_def_code:
-                mval = scan_mathchar(tex_mathcode);
-                if (math_umathcode_meaning_par == 1) {
-                    cur_val = (mval.class_value + (8 * mval.family_value)) * (65536 * 32) + mval.character_value;
-                    define(p, xmath_given_cmd, cur_val);
-                } else {
-                    cur_val = (mval.class_value * 16 + mval.family_value) * 256 + mval.character_value;
-                    define(p, math_given_cmd, cur_val);
-                }
-                break;
-            case xmath_char_def_code:
-                mval = scan_mathchar(umath_mathcode);
-                cur_val = (mval.class_value + (8 * mval.family_value)) * (65536 * 32) + mval.character_value;
-                define(p, xmath_given_cmd, cur_val);
-                break;
-            case umath_char_def_code:
-                mval = scan_mathchar(umathnum_mathcode);
-                cur_val = (mval.class_value + (8 * mval.family_value)) * (65536 * 32) + mval.character_value;
-                define(p, xmath_given_cmd, cur_val);
-                break;
-            default:
-                scan_register_num();
-                switch (n) {
-                case count_def_code:
-                    define(p, assign_int_cmd, count_base + cur_val);
-                    break;
-                case attribute_def_code:
-                    define(p, assign_attr_cmd, attribute_base + cur_val);
-                    break;
-                case dimen_def_code:
-                    define(p, assign_dimen_cmd, scaled_base + cur_val);
-                    break;
-                case skip_def_code:
-                    define(p, assign_glue_cmd, skip_base + cur_val);
-                    break;
-                case mu_skip_def_code:
-                    define(p, assign_mu_glue_cmd, mu_skip_base + cur_val);
-                    break;
-                case toks_def_code:
-                    define(p, assign_toks_cmd, toks_base + cur_val);
-                    break;
-                default:
-                    confusion("shorthand_def");
-                    break;
-                }
-                break;
-            }
-            break;
-        case read_to_cs_cmd:
-            j = cur_chr;
-            scan_int();
-            n = cur_val;
-            if (!scan_keyword("to")) {
-                print_err("Missing `to' inserted");
-                help2("You should have said `\\read<number> to \\cs'.",
-                      "I'm going to look for the \\cs now.");
-                error();
-            }
-            get_r_token();
-            p = cur_cs;
-            read_toks(n, p, j);
-            define(p, call_cmd, cur_val);
-            break;
-        case toks_register_cmd:
-        case assign_toks_cmd:
-            /* The token-list parameters, \.{\\output} and \.{\\everypar}, etc., receive
-               their values in the following way. (For safety's sake, we place an
-               enclosing pair of braces around an \.{\\output} list.) */
-            q = cur_cs;
-            if (cur_cmd == toks_register_cmd) {
-                scan_register_num();
-                p = toks_base + cur_val;
-            } else {
-                p = cur_chr;        /* |p=every_par_loc| or |output_routine_loc| or \dots */
-            }
-            scan_optional_equals();
-            /* Get the next non-blank non-relax non-call token */
-            do {
-                get_x_token();
-            } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));
-
-            if (cur_cmd != left_brace_cmd) {
-                /* If the right-hand side is a token parameter
-                   or token register, finish the assignment and |goto done| */
-                if (cur_cmd == toks_register_cmd) {
-                    scan_register_num();
-                    cur_cmd = assign_toks_cmd;
-                    cur_chr = toks_base + cur_val;
-                }
-                if (cur_cmd == assign_toks_cmd) {
-                    q = equiv(cur_chr);
-                    if (q == null) {
-                        define(p, undefined_cs_cmd, null);
-                    } else {
-                        add_token_ref(q);
-                        define(p, call_cmd, q);
-                    }
-                    goto DONE;
-                }
-            }
-            back_input();
-            cur_cs = q;
-            q = scan_toks(false, false);
-            if (token_link(def_ref) == null) {      /* empty list: revert to the default */
-                define(p, undefined_cs_cmd, null);
-                free_avail(def_ref);
-            } else {
-                if (p == output_routine_loc) {      /* enclose in curlies */
-                    p = get_avail();
-                    set_token_link(q, p);
-                    p = output_routine_loc;
-                    q = token_link(q);
-                    set_token_info(q, right_brace_token + '}');
-                    q = get_avail();
-                    set_token_info(q, left_brace_token + '{');
-                    set_token_link(q, token_link(def_ref));
-                    set_token_link(def_ref, q);
-                }
-                define(p, call_cmd, def_ref);
-            }
-            break;
-        case assign_int_cmd:
-            /* Similar routines are used to assign values to the numeric parameters. */
-            p = cur_chr;
-            scan_optional_equals();
-            scan_int();
-            assign_internal_value(a, p, cur_val);
-            break;
-        case assign_attr_cmd:
-            p = cur_chr;
-            scan_optional_equals();
-            scan_int();
-            if ((p - attribute_base) > max_used_attr)
-                max_used_attr = (p - attribute_base);
-            attr_list_cache = cache_disabled;
-            word_define(p, cur_val);
-            break;
-        case assign_dir_cmd:
-            /* DIR: Assign direction codes */
-            scan_direction();
-            switch (cur_chr) {
-                case int_base + page_direction_code:
-                    eq_word_define(int_base + page_direction_code, cur_val);
-                    break;
-                case int_base + body_direction_code:
-                    eq_word_define(int_base + body_direction_code, cur_val);
-                    break;
-                case int_base + par_direction_code:
-                    eq_word_define(int_base + par_direction_code, cur_val);
-                    break;
-                case int_base + math_direction_code:
-                    eq_word_define(int_base + math_direction_code, cur_val);
-                    break;
-                case int_base + text_direction_code:
-                case int_base + line_direction_code:
-                    /*
-                        pre version 0.97 this was a commented section because various tests hint that this
-                        is unnecessary and sometimes even produces weird results, like:
-
-                            (\hbox{\textdir TRT ABC\textdir TLT DEF}))
-
-                        becomes
-
-                            (DEFCBA)
-
-                        in the output when we use
-
-                            tail_append(new_dir(text_direction_par)
-
-                        but when we append the reverse of the current it goes better
-
-                    */
-                    check_glue = (cur_chr == (int_base + line_direction_code));
-                    if (check_glue) {
-                        cur_chr = int_base + text_direction_code ;
-                    }
-                    if (abs(mode) == hmode) {
-                        if (no_local_dirs_par > 0) {
-                            /* tail is non zero but we test anyway */
-                            if (check_glue && (tail != null && type(tail) == glue_node))  {
-                                halfword prev = alink(tail);
-                                halfword dirn = new_dir(text_direction_par - dir_swap);
-                                couple_nodes(prev,dirn);
-                                couple_nodes(dirn,tail);
-                            } else {
-                                tail_append(new_dir(text_direction_par - dir_swap));
-                            }
-                        } else {
-                            /* what is the use of nolocaldirs .. maybe we should get rid of it */
-                        }
-                        update_text_dir_ptr(cur_val);
-                        tail_append(new_dir(cur_val));
-                        dir_level(tail) = cur_level;
-                    } else {
-                        update_text_dir_ptr(cur_val);
-                    }
-                    /*  original:
-
-                        // if ((no_local_dirs_par > 0) && (abs(mode) == hmode)) {
-                        //  // tail_append(new_dir(text_direction_par)              // kind of wrong
-                        //     tail_append(new_dir(text_direction_par - dir_swap)); // better
-                        // }
-
-                        update_text_dir_ptr(cur_val);
-                        if (abs(mode) == hmode) {
-                            tail_append(new_dir(cur_val));
-                            dir_level(tail) = cur_level;
-                        }
-                    */
-                    eq_word_define(int_base + text_direction_code, cur_val);
-                    eq_word_define(int_base + no_local_dirs_code, no_local_dirs_par + 1);
-                    break;
-                }
-            break;
-        case assign_dimen_cmd:
-            p = cur_chr;
-            scan_optional_equals();
-            scan_normal_dimen();
-            assign_internal_value(a, p, cur_val);
-            break;
-        case assign_glue_cmd:
-        case assign_mu_glue_cmd:
-            p = cur_chr;
-            n = cur_cmd;
-            scan_optional_equals();
-            if (n == assign_mu_glue_cmd)
-                scan_glue(mu_val_level);
-            else
-                scan_glue(glue_val_level);
-            define(p, glue_ref_cmd, cur_val);
-            break;
-        case def_char_code_cmd:
-        case def_del_code_cmd:
-            /* Let |n| be the largest legal code value, based on |cur_chr| */
-            if (cur_chr == cat_code_base)
-                n = max_char_code;
-            else if (cur_chr == sf_code_base)
-                n = 077777;
-            else
-                n = biggest_char;
-
-            p = cur_chr;
-            if (cur_chr == math_code_base) {
-                if (is_global(a))
-                    cur_val1 = level_one;
-                else
-                    cur_val1 = cur_level;
-                scan_extdef_math_code(cur_val1, tex_mathcode);
-            } else if (cur_chr == lc_code_base) {
-                scan_char_num();
-                p = cur_val;
-                scan_optional_equals();
-                scan_int();
-                check_def_code(lc_code_base);
-                define_lc_code(p, cur_val);
-            } else if (cur_chr == uc_code_base) {
-                scan_char_num();
-                p = cur_val;
-                scan_optional_equals();
-                scan_int();
-                check_def_code(uc_code_base);
-                define_uc_code(p, cur_val);
-            } else if (cur_chr == sf_code_base) {
-                scan_char_num();
-                p = cur_val;
-                scan_optional_equals();
-                scan_int();
-                check_def_code(sf_code_base);
-                define_sf_code(p, cur_val);
-            } else if (cur_chr == cat_code_base) {
-                scan_char_num();
-                p = cur_val;
-                scan_optional_equals();
-                scan_int();
-                check_def_code(cat_code_base);
-                define_cat_code(p, cur_val);
-            } else if (cur_chr == del_code_base) {
-                if (is_global(a))
-                    cur_val1 = level_one;
-                else
-                    cur_val1 = cur_level;
-                scan_extdef_del_code(cur_val1, tex_mathcode);
-            }
-            break;
-        case extdef_math_code_cmd:
-        case extdef_del_code_cmd:
-            if (is_global(a))
-                cur_val1 = level_one;
-            else
-                cur_val1 = cur_level;
-            if (cur_chr == math_code_base)
-                scan_extdef_math_code(cur_val1, umath_mathcode);
-            else if (cur_chr == math_code_base + 1)
-                scan_extdef_math_code(cur_val1, umathnum_mathcode);
-            else if (cur_chr == del_code_base)
-                scan_extdef_del_code(cur_val1, umath_mathcode);
-            else if (cur_chr == del_code_base + 1)
-                scan_extdef_del_code(cur_val1, umathnum_mathcode);
-            break;
-        case def_family_cmd:
-            p = cur_chr;
-            scan_math_family_int();
-            cur_val1 = cur_val;
-            scan_optional_equals();
-            scan_font_ident();
-            define_fam_fnt(cur_val1, p, cur_val);
-            break;
-        case set_math_param_cmd:
-            p = cur_chr;
-            get_token();
-            if (cur_cmd != math_style_cmd) {
-                print_err("Missing math style, treated as \\displaystyle");
-                help1
-                    ("A style should have been here; I inserted `\\displaystyle'.");
-                cur_val1 = display_style;
-                back_error();
-            } else {
-                cur_val1 = cur_chr;
-            }
-            scan_optional_equals();
-            if (p < math_param_first_mu_glue) {
-                if (p == math_param_radical_degree_raise)
-                    scan_int();
-                else
-                    scan_dimen(false, false, false);
-            } else {
-                scan_glue(mu_val_level);
-                if (cur_val == thin_mu_skip_par)
-                    cur_val = thin_mu_skip_code;
-                else if (cur_val == med_mu_skip_par)
-                    cur_val = med_mu_skip_code;
-                else if (cur_val == thick_mu_skip_par)
-                    cur_val = thick_mu_skip_code;
-            }
-            define_math_param(p, cur_val1, cur_val);
-            break;
-        case register_cmd:
-        case advance_cmd:
-        case multiply_cmd:
-        case divide_cmd:
-            do_register_command(a);
-            break;
-        case set_box_cmd:
-            /* The processing of boxes is somewhat different, because we may need
-               to scan and create an entire box before we actually change the value
-               of the old one. */
-            scan_register_num();
-            if (is_global(a))
-                n = global_box_flag + cur_val;
-            else
-                n = box_flag + cur_val;
-            scan_optional_equals();
-            if (set_box_allowed) {
-                scan_box(n);
-            } else {
-                print_err("Improper \\setbox");
-                help2("Sorry, \\setbox is not allowed after \\halign in a display,",
-                      "or between \\accent and an accented character.");
-                error();
-            }
-            break;
-        case set_aux_cmd:
-            /* The |space_factor| or |prev_depth| settings are changed when a |set_aux|
-               command is sensed. Similarly, |prev_graf| is changed in the presence of
-               |set_prev_graf|, and |dead_cycles| or |insert_penalties| in the presence of
-               |set_page_int|. These definitions are always global. */
-            alter_aux();
-            break;
-        case set_prev_graf_cmd:
-            alter_prev_graf();
-            break;
-        case set_page_dimen_cmd:
-            alter_page_so_far();
-            break;
-        case set_page_int_cmd:
-            alter_integer();
-            break;
-        case set_box_dimen_cmd:
-            /* When some dimension of a box register is changed, the change isn't exactly
-               global; but \TeX\ does not look at the \.{\\global} switch. */
-            alter_box_dimen();
-            break;
-        case set_tex_shape_cmd:
-            q = cur_chr;
-            scan_optional_equals();
-            scan_int();
-            n = cur_val;
-            if (n <= 0) {
-                p = null;
-            } else {
-                p = new_node(shape_node, 2 * (n + 1) + 1);
-                vinfo(p + 1) = n;
-                for (j = 1; j <= n; j++) {
-                    scan_normal_dimen();
-                    varmem[p + 2 * j].cint = cur_val;       /* indentation */
-                    scan_normal_dimen();
-                    varmem[p + 2 * j + 1].cint = cur_val;   /* width */
-                }
-            }
-            define(q, shape_ref_cmd, p);
-            break;
-        case set_etex_shape_cmd:
-            q = cur_chr;
-            scan_optional_equals();
-            scan_int();
-            n = cur_val;
-            if (n <= 0) {
-                p = null;
-            } else {
-                n = (cur_val / 2) + 1;
-                p = new_node(shape_node, 2 * n + 1 + 1);
-                vinfo(p + 1) = n;
-                n = cur_val;
-                varmem[p + 2].cint = n;     /* number of penalties */
-                for (j = p + 3; j <= p + n + 2; j++) {
-                    scan_int();
-                    varmem[j].cint = cur_val;       /* penalty values */
-                }
-                if (!odd(n))
-                    varmem[p + n + 3].cint = 0;     /* unused */
-            }
-            define(q, shape_ref_cmd, p);
-            break;
-        case hyph_data_cmd:
-            /* All of \TeX's parameters are kept in |eqtb| except the font information,
-               the interaction mode, and the hyphenation tables; these are strictly global.
-             */
-            switch (cur_chr) {
-                case 0:
-                    new_hyph_exceptions();
-                    break;
-                case 1:
-                    new_patterns();
-                    break;
-                case 2:
-                    new_pre_hyphen_char();
-                    break;
-                case 3:
-                    new_post_hyphen_char();
-                    break;
-                case 4:
-                    new_pre_exhyphen_char();
-                    break;
-                case 5:
-                    new_post_exhyphen_char();
-                    break;
-                case 6:
-                    new_hyphenation_min();
-                    break;
-                case 7:
-                    new_hj_code();
-                    break;
-            }
-            break;
-        case assign_font_dimen_cmd:
-            set_font_dimen();
-            break;
-        case assign_font_int_cmd:
-            n = cur_chr;
-            scan_font_ident();
-            f = cur_val;
-            if (n == no_lig_code) {
-                set_no_ligatures(f);
-            } else if (n < lp_code_base) {
-                scan_optional_equals();
-                scan_int();
-                if (n == 0)
-                    set_hyphen_char(f, cur_val);
-                else
-                    set_skew_char(f, cur_val);
-            } else {
-                scan_char_num();
-                p = cur_val;
-                scan_optional_equals();
-                scan_int();
-                switch (n) {
-                    case lp_code_base:
-                        set_lp_code(f, p, cur_val);
-                        break;
-                    case rp_code_base:
-                        set_rp_code(f, p, cur_val);
-                        break;
-                    case ef_code_base:
-                        set_ef_code(f, p, cur_val);
-                        break;
-                    case tag_code:
-                        set_tag_code(f, p, cur_val);
-                        break;
-                }
-            }
-            break;
-        case def_font_cmd:
-            /* Here is where the information for a new font gets loaded. */
-            tex_def_font((small_number) a);
-            break;
-        case letterspace_font_cmd:
-            new_letterspaced_font((small_number) a);
-            break;
-        case copy_font_cmd:
-            make_font_copy((small_number) a);
-            break;
-        case set_font_id_cmd:
-            scan_int();
-            if (is_valid_font(cur_val))
-                zset_cur_font(cur_val);
-            break ;
-        case set_interaction_cmd:
-            new_interaction();
-            break;
-        default:
-            confusion("prefix");
-            break;
-    }                           /* end of Assignments cases */
-  DONE:
-    /* Insert a token saved by \.{\\afterassignment}, if any */
-    if (after_token != 0) {
-        cur_tok = after_token;
-        back_input();
-        after_token = 0;
-    }
-}
-
-@ @c
-void fixup_directions(void)
-{
-    int temp_no_whatsits = no_local_whatsits_par;
-    int temp_no_dirs = no_local_dirs_par;
-    int temporary_dir = text_direction_par;
-    if (dir_level(text_dir_ptr) == cur_level) {
-        /* DIR: Remove from |text_dir_ptr| */
-        halfword text_dir_tmp = vlink(text_dir_ptr);
-        flush_node(text_dir_ptr);
-        text_dir_ptr = text_dir_tmp;
-    }
-    unsave();
-    if (abs(mode) == hmode) {
-        if (temp_no_dirs != 0) {
-            /* DIR: Add local dir node */
-            tail_append(new_dir(text_direction_par));
-            dir_dir(tail) = temporary_dir - dir_swap;
-        }
-        if (temp_no_whatsits != 0) {
-            /* LOCAL: Add local paragraph node */
-            tail_append(make_local_par_node(hmode_par_par_code));
-        }
-    }
-}
-
-@ When a control sequence is to be defined, by \.{\\def} or \.{\\let} or
-something similar, the |get_r_token| routine will substitute a special
-control sequence for a token that is not redefinable.
-
- at c
-void get_r_token(void)
-{
-  RESTART:
-    do {
-        get_token();
-    } while (cur_tok == space_token);
-    if ((cur_cs == 0) || (cur_cs > eqtb_top) ||
-        ((cur_cs > frozen_control_sequence) && (cur_cs <= eqtb_size))) {
-        print_err("Missing control sequence inserted");
-        help5("Please don't say `\\def cs{...}', say `\\def\\cs{...}'.",
-              "I've inserted an inaccessible control sequence so that your",
-              "definition will be completed without mixing me up too badly.",
-              "You can recover graciously from this error, if you're",
-              "careful; see exercise 27.2 in The TeXbook.");
-        if (cur_cs == 0)
-            back_input();
-        cur_tok = cs_token_flag + frozen_protection;
-        ins_error();
-        goto RESTART;
-    }
-}
-
-@ @c
-void assign_internal_value(int a, halfword p, int val)
-{
-    halfword n;
-    if ((p >= int_base) && (p < attribute_base)) {
-        switch ((p - int_base)) {
-        case cat_code_table_code:
-            if (valid_catcode_table(val)) {
-                if (val != cat_code_table_par)
-                    word_define(p, val);
-            } else {
-                print_err("Invalid \\catcode table");
-                help2
-                    ("You can only switch to a \\catcode table that is initialized",
-                     "using \\savecatcodetable or \\initcatcodetable, or to table 0");
-                error();
-            }
-            break;
-        case output_box_code:
-            if ((val > 65535) | (val < 0)) {
-                print_err("Invalid \\outputbox");
-                help1
-                    ("The value for \\outputbox has to be between 0 and 65535.");
-                error();
-            } else {
-                word_define(p, val);
-            }
-            break;
-        case new_line_char_code:
-            if (val > 127) {
-                print_err("Invalid \\newlinechar");
-                help2
-                    ("The value for \\newlinechar has to be no higher than 127.",
-                     "Your invalid assignment will be ignored.");
-                error();
-            } else {
-                word_define(p, val);
-            }
-            break;
-        case end_line_char_code:
-            if (val > 127) {
-                print_err("Invalid \\endlinechar");
-                help2
-                    ("The value for \\endlinechar has to be no higher than 127.",
-                     "Your invalid assignment will be ignored.");
-                error();
-            } else {
-                word_define(p, val);
-            }
-            break;
-        case language_code:
-            if (val < 0) {
-                word_define(int_base + cur_lang_code, -1);
-                word_define(p, -1);
-            } else if (val > 16383) {
-                print_err("Invalid \\language");
-                help2
-                    ("The absolute value for \\language has to be no higher than 16383.",
-                     "Your invalid assignment will be ignored.");
-                error();
-            } else {
-                word_define(int_base + cur_lang_code, val);
-                word_define(p, val);
-            }
-            break;
-        default:
-            word_define(p, val);
-            break;
-        }
-        /* If we are defining subparagraph penalty levels while we are
-           in hmode, then we put out a whatsit immediately, otherwise
-           we leave it alone.  This mechanism might not be sufficiently
-           powerful, and some other algorithm, searching down the stack,
-           might be necessary.  Good first step. */
-        if ((abs(mode) == hmode) &&
-            ((p == (int_base + local_inter_line_penalty_code)) ||
-             (p == (int_base + local_broken_penalty_code)))) {
-            /* LOCAL: Add local paragraph node */
-            tail_append(make_local_par_node(penalty_par_code));
-            eq_word_define(int_base + no_local_whatsits_code, no_local_whatsits_par + 1);
-        }
-    } else if ((p >= dimen_base) && (p <= eqtb_size)) {
-        if (p == (dimen_base + page_left_offset_code)) {
-            n = val - one_true_inch;
-            word_define(dimen_base + h_offset_code, n);
-        } else if (p == (dimen_base + h_offset_code)) {
-            n = val + one_true_inch;
-            word_define(dimen_base + page_left_offset_code, n);
-        } else if (p == (dimen_base + page_top_offset_code)) {
-            n = val - one_true_inch;
-            word_define(dimen_base + v_offset_code, n);
-        } else if (p == (dimen_base + v_offset_code)) {
-            n = val + one_true_inch;
-            word_define(dimen_base + page_top_offset_code, n);
-        }
-        word_define(p, val);
-    } else if ((p >= local_base) && (p < toks_base)) {  /* internal locals  */
-        define(p, call_cmd, val);
-    } else {
-        confusion("assign internal value");
-    }
-}
-
-@ We use the fact that |register<advance<multiply<divide|
-
-Compute the register location |l| and its type |p|; but |return| if invalid
-Here we use the fact that the consecutive codes |int_val..mu_val| and
-|assign_int..assign_mu_glue| correspond to each other nicely.
-
- at c
-void do_register_command(int a)
-{
-    int p;
-    halfword q = cur_cmd;
-    halfword l = 0;
-    if (q != register_cmd) {
-        get_x_token();
-        if ((cur_cmd >= assign_int_cmd) && (cur_cmd <= assign_mu_glue_cmd)) {
-            l = cur_chr;
-            p = cur_cmd - assign_int_cmd;
-            goto FOUND;
-        }
-        if (cur_cmd != register_cmd) {
-            print_err("You can't use `");
-            print_cmd_chr((quarterword) cur_cmd, cur_chr);
-            tprint("' after ");
-            print_cmd_chr((quarterword) q, 0);
-            help1("I'm forgetting what you said and not changing anything.");
-            error();
-            return;
-        }
-    }
-    p = cur_chr;
-    scan_register_num();
-    if (p == int_val_level)
-        l = cur_val + count_base;
-    else if (p == attr_val_level)
-        l = cur_val + attribute_base;
-    else if (p == dimen_val_level)
-        l = cur_val + scaled_base;
-    else if (p == glue_val_level)
-        l = cur_val + skip_base;
-    else if (p == mu_val_level)
-        l = cur_val + mu_skip_base;
-  FOUND:
-    if (q == register_cmd) {
-        scan_optional_equals();
-    } else if (scan_keyword("by")) {
-        /* optional `\.{by}' */
-    }
-    arith_error = false;
-    if (q < multiply_cmd) {
-        /* Compute result of |register| or |advance|, put it in |cur_val| */
-        if (p < glue_val_level) {
-            if ((p == int_val_level) || (p == attr_val_level))
-                scan_int();
-            else
-                scan_normal_dimen();
-            if (q == advance_cmd)
-                cur_val = cur_val + eqtb[l].cint;
-        } else {
-            /* we can probably save a copy */
-            scan_glue(p);
-            if (q == advance_cmd) {
-                /* Compute the sum of two glue specs */
-                halfword r = equiv(l);
-                q = new_spec(cur_val);
-                flush_node(cur_val);
-                width(q) = width(q) + width(r);
-                if (stretch(q) == 0) {
-                    stretch_order(q) = normal;
-                }
-                if (stretch_order(q) == stretch_order(r)) {
-                    stretch(q) = stretch(q) + stretch(r);
-                } else if ((stretch_order(q) < stretch_order(r)) && (stretch(r) != 0)) {
-                    stretch(q) = stretch(r);
-                    stretch_order(q) = stretch_order(r);
-                }
-                if (shrink(q) == 0) {
-                    shrink_order(q) = normal;
-                }
-                if (shrink_order(q) == shrink_order(r)) {
-                    shrink(q) = shrink(q) + shrink(r);
-                } else if ((shrink_order(q) < shrink_order(r)) && (shrink(r) != 0)) {
-                    shrink(q) = shrink(r);
-                    shrink_order(q) = shrink_order(r);
-                }
-                cur_val = q;
-            }
-        }
-    } else {
-        /* Compute result of |multiply| or |divide|, put it in |cur_val| */
-        scan_int();
-        if (p < glue_val_level) {
-            if (q == multiply_cmd) {
-                if ((p == int_val_level) || (p == attr_val_level)) {
-                    cur_val = mult_integers(eqtb[l].cint, cur_val);
-                } else {
-                    cur_val = nx_plus_y(eqtb[l].cint, cur_val, 0);
-                }
-            } else {
-                cur_val = x_over_n(eqtb[l].cint, cur_val);
-            }
-        } else {
-            halfword s = equiv(l);
-            halfword r = new_spec(s);
-            if (q == multiply_cmd) {
-                width(r) = nx_plus_y(width(s), cur_val, 0);
-                stretch(r) = nx_plus_y(stretch(s), cur_val, 0);
-                shrink(r) = nx_plus_y(shrink(s), cur_val, 0);
-            } else {
-                width(r) = x_over_n(width(s), cur_val);
-                stretch(r) = x_over_n(stretch(s), cur_val);
-                shrink(r) = x_over_n(shrink(s), cur_val);
-            }
-            cur_val = r;
-        }
-    }
-    if (arith_error) {
-        print_err("Arithmetic overflow");
-        help2("I can't carry out that multiplication or division,",
-              "since the result is out of range.");
-        if (p >= glue_val_level)
-            flush_node(cur_val);
-        error();
-        return;
-    }
-    if (p < glue_val_level) {
-        if (p == attr_val_level) {
-            if ((l - attribute_base) > max_used_attr)
-                max_used_attr = (l - attribute_base);
-            attr_list_cache = cache_disabled;
-        }
-        if ((p == int_val_level) || (p == dimen_val_level))
-            assign_internal_value(a, l, cur_val);
-        else
-            word_define(l, cur_val);
-    } else {
-        define(l, glue_ref_cmd, cur_val);
-    }
-}
-
-@ @c
-void alter_aux(void)
-{
-    halfword c;                 /* |hmode| or |vmode| */
-    if (cur_chr != abs(mode)) {
-        report_illegal_case();
-    } else {
-        c = cur_chr;
-        scan_optional_equals();
-        if (c == vmode) {
-            scan_normal_dimen();
-            prev_depth_par = cur_val;
-        } else {
-            scan_int();
-            if ((cur_val <= 0) || (cur_val > 32767)) {
-                print_err("Bad space factor");
-                help1("I allow only values in the range 1..32767 here.");
-                int_error(cur_val);
-            } else {
-                space_factor_par = cur_val;
-            }
-        }
-    }
-}
-
-@ @c
-void alter_prev_graf(void)
-{
-    int p;                      /* index into |nest| */
-    p = nest_ptr;
-    while (abs(nest[p].mode_field) != vmode)
-        decr(p);
-    scan_optional_equals();
-    scan_int();
-    if (cur_val < 0) {
-        print_err("Bad \\prevgraf");
-        help1("I allow only nonnegative values here.");
-        int_error(cur_val);
-    } else {
-        nest[p].pg_field = cur_val;
-    }
-}
-
-@ @c
-void alter_page_so_far(void)
-{
-    int c;                      /* index into |page_so_far| */
-    c = cur_chr;
-    scan_optional_equals();
-    scan_normal_dimen();
-    page_so_far[c] = cur_val;
-}
-
-@ @c
-void alter_integer(void)
-{
-    int c;                      /* 0 for \.{\\deadcycles}, 1 for \.{\\insertpenalties}, etc. */
-    c = cur_chr;
-    scan_optional_equals();
-    scan_int();
-    if (c == 0) {
-        dead_cycles = cur_val;
-    } else if (c == 2) {
-        if ((cur_val < batch_mode) || (cur_val > error_stop_mode)) {
-            print_err("Bad interaction mode");
-            help2("Modes are 0=batch, 1=nonstop, 2=scroll, and",
-                  "3=errorstop. Proceed, and I'll ignore this case.");
-            int_error(cur_val);
-        } else {
-            cur_chr = cur_val;
-            new_interaction();
-        }
-    } else {
-        insert_penalties = cur_val;
-    }
-}
-
-@ @c
-void alter_box_dimen(void)
-{
-    int c;                      /* |width_offset| or |height_offset| or |depth_offset| */
-    int b;                      /* box number */
-    c = cur_chr;
-    scan_register_num();
-    b = cur_val;
-    scan_optional_equals();
-    scan_normal_dimen();
-    if (box(b) != null)
-        varmem[box(b) + c].cint = cur_val;
-}
-
-@ @c
-void new_interaction(void)
-{
-    print_ln();
-    interaction = cur_chr;
-    if (interaction == batch_mode)
-        kpse_make_tex_discard_errors = 1;
-    else
-        kpse_make_tex_discard_errors = 0;
-    fixup_selector(log_opened_global);
-}
-
-@ The \.{\\afterassignment} command puts a token into the global
-variable |after_token|. This global variable is examined just after
-every assignment has been performed.
-
- at c
-halfword after_token;           /* zero, or a saved token */
-
-@ Here is a procedure that might be called `Get the next non-blank non-relax
-non-call non-assignment token'.
-
- at c
-void do_assignments(void)
-{
-    while (true) {
-        /* Get the next non-blank non-relax... */
-        do {
-            get_x_token();
-        } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));
-        if (cur_cmd <= max_non_prefixed_command)
-            return;
-        set_box_allowed = false;
-        prefixed_command();
-        set_box_allowed = true;
-    }
-}
-
-@ @c
-void open_or_close_in(void)
-{
-    int c;                      /* 1 for \.{\\openin}, 0 for \.{\\closein} */
-    int n;                      /* stream number */
-    char *fn;
-    c = cur_chr;
-    scan_four_bit_int();
-    n = cur_val;
-    if (read_open[n] != closed) {
-        lua_a_close_in(read_file[n], (n + 1));
-        read_open[n] = closed;
-    }
-    if (c != 0) {
-        scan_optional_equals();
-        do {
-            get_x_token();
-        } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));
-        back_input();
-        if (cur_cmd != left_brace_cmd) {
-            scan_file_name();   /* set |cur_name| to desired file name */
-            if (cur_ext == get_nullstr())
-                cur_ext = maketexstring(".tex");
-        } else {
-            scan_file_name_toks();
-        }
-        fn = pack_file_name(cur_name, cur_area, cur_ext);
-        if (lua_a_open_in(&(read_file[n]), fn, (n + 1))) {
-            read_open[n] = just_open;
-        }
-    }
-}
-
-@ @c
-boolean long_help_seen;         /* has the long \.{\\errmessage} help been used? */
-
-void issue_message(void)
-{
-    int old_setting;            /* holds |selector| setting */
-    int c;                      /* identifies \.{\\message} and \.{\\errmessage} */
-    str_number s;               /* the message */
-    c = cur_chr;
-    (void) scan_toks(false, true);
-    old_setting = selector;
-    selector = new_string;
-    token_show(def_ref);
-    selector = old_setting;
-    flush_list(def_ref);
-    str_room(1);
-    s = make_string();
-    if (c == 0) {
-        /* Print string |s| on the terminal */
-        if (term_offset + (int) str_length(s) > max_print_line - 2)
-            print_ln();
-        else if ((term_offset > 0) || (file_offset > 0))
-            print_char(' ');
-        print(s);
-        update_terminal();
-
-    } else {
-        /* Print string |s| as an error message */
-        /* If \.{\\errmessage} occurs often in |scroll_mode|, without user-defined
-           \.{\\errhelp}, we don't want to give a long help message each time. So we
-           give a verbose explanation only once. */
-        print_err("");
-        print(s);
-        if (err_help_par != null) {
-            use_err_help = true;
-        } else if (long_help_seen) {
-            help1("(That was another \\errmessage.)");
-        } else {
-            if (interaction < error_stop_mode)
-                long_help_seen = true;
-            help4("This error message was generated by an \\errmessage",
-                  "command, so I can't give any explicit help.",
-                  "Pretend that you're Hercule Poirot: Examine all clues,",
-                  "and deduce the truth by order and method.");
-        }
-        error();
-        use_err_help = false;
-
-    }
-    flush_str(s);
-}
-
-@ The |error| routine calls on |give_err_help| if help is requested from
-the |err_help| parameter.
-
- at c
-void give_err_help(void)
-{
-    token_show(err_help_par);
-}
-
-@ The \.{\\uppercase} and \.{\\lowercase} commands are implemented by
-building a token list and then changing the cases of the letters in it.
-
- at c
-void shift_case(void)
-{
-    halfword b;                 /* |lc_code_base| or |uc_code_base| */
-    halfword p;                 /* runs through the token list */
-    halfword t;                 /* token */
-    halfword c;                 /* character code */
-    halfword i;                 /* inbetween */
-    b = cur_chr;
-    p = scan_toks(false, false);
-    p = token_link(def_ref);
-    while (p != null) {
-        /* Change the case of the token in |p|, if a change is appropriate */
-        /*
-           When the case of a |chr_code| changes, we don't change the |cmd|.
-           We also change active characters.
-         */
-        t = token_info(p);
-        if (t < cs_token_flag) {
-            c = t % STRING_OFFSET;
-            if (b == uc_code_base)
-                i = get_uc_code(c);
-            else
-                i = get_lc_code(c);
-            if (i != 0)
-                set_token_info(p, t - c + i);
-        } else if (is_active_cs(cs_text(t - cs_token_flag))) {
-            c = active_cs_value(cs_text(t - cs_token_flag));
-            if (b == uc_code_base)
-                i = get_uc_code(c);
-            else
-                i = get_lc_code(c);
-            if (i != 0)
-                set_token_info(p, active_to_cs(i, true) + cs_token_flag);
-        }
-        p = token_link(p);
-    }
-    back_list(token_link(def_ref));
-    free_avail(def_ref);        /* omit reference count */
-}
-
-@ We come finally to the last pieces missing from |main_control|, namely the
-`\.{\\show}' commands that are useful when debugging.
-
- at c
-void show_whatever(void)
-{
-    halfword p;                 /* tail of a token list to show */
-    int t;                      /* type of conditional being shown */
-    int m;                      /* upper bound on |fi_or_else| codes */
-    int l;                      /* line where that conditional began */
-    int n;                      /* level of \.{\\if...\\fi} nesting */
-    switch (cur_chr) {
-    case show_lists:
-        begin_diagnostic();
-        show_activities();
-        break;
-    case show_box_code:
-        /* Show the current contents of a box */
-        scan_register_num();
-        begin_diagnostic();
-        tprint_nl("> \\box");
-        print_int(cur_val);
-        print_char('=');
-        if (box(cur_val) == null)
-            tprint("void");
-        else
-            show_box(box(cur_val));
-        break;
-    case show_code:
-        /* Show the current meaning of a token, then |goto common_ending| */
-        get_token();
-        if (interaction == error_stop_mode)
-            wake_up_terminal();
-        tprint_nl("> ");
-        if (cur_cs != 0) {
-            sprint_cs(cur_cs);
-            print_char('=');
-        }
-        print_meaning();
-        goto COMMON_ENDING;
-        break;
-        /* Cases for |show_whatever| */
-    case show_groups:
-        begin_diagnostic();
-        show_save_groups();
-        break;
-    case show_ifs:
-        begin_diagnostic();
-        tprint_nl("");
-        print_ln();
-        if (cond_ptr == null) {
-            tprint_nl("### ");
-            tprint("no active conditionals");
-        } else {
-            p = cond_ptr;
-            n = 0;
-            do {
-                incr(n);
-                p = vlink(p);
-            } while (p != null);
-            p = cond_ptr;
-            t = cur_if;
-            l = if_line;
-            m = if_limit;
-            do {
-                tprint_nl("### level ");
-                print_int(n);
-                tprint(": ");
-                print_cmd_chr(if_test_cmd, t);
-                if (m == fi_code)
-                    tprint_esc("else");
-                print_if_line(l);
-                decr(n);
-                t = if_limit_subtype(p);
-                l = if_line_field(p);
-                m = if_limit_type(p);
-                p = vlink(p);
-            } while (p != null);
-        }
-        break;
-    default:
-        /* Show the current value of some parameter or register,
-           then |goto common_ending| */
-        p = the_toks();
-        if (interaction == error_stop_mode)
-            wake_up_terminal();
-        tprint_nl("> ");
-        token_show(temp_token_head);
-        flush_list(token_link(temp_token_head));
-        goto COMMON_ENDING;
-        break;
-    }
-    /* Complete a potentially long \.{\\show} command */
-    end_diagnostic(true);
-    print_err("OK");
-    if (selector == term_and_log) {
-        if (tracing_online_par <= 0) {
-            selector = term_only;
-            tprint(" (see the transcript file)");
-            selector = term_and_log;
-        }
-    }
-  COMMON_ENDING:
-    if (interaction < error_stop_mode) {
-        help0();
-        decr(error_count);
-    } else if (tracing_online_par > 0) {
-        help3("This isn't an error message; I'm just \\showing something.",
-              "Type `I\\show...' to show more (e.g., \\show\\cs,",
-              "\\showthe\\count10, \\showbox255, \\showlists).");
-    } else {
-        help5("This isn't an error message; I'm just \\showing something.",
-              "Type `I\\show...' to show more (e.g., \\show\\cs,",
-              "\\showthe\\count10, \\showbox255, \\showlists).",
-              "And type `I\\tracingonline=1\\show...' to show boxes and",
-              "lists on your terminal as well as in the transcript file.");
-    }
-    error();
-}
-
-@ @c
-void initialize(void)
-{                               /* this procedure gets things started properly */
-    int k;                      /* index into |mem|, |eqtb|, etc. */
-    /* Initialize whatever \TeX\ might access */
-    /* Set initial values of key variables */
-    initialize_errors();
-    initialize_arithmetic();
-    max_used_attr = -1;
-    attr_list_cache = cache_disabled;
-    initialize_nesting();
-
-    /* Start a new current page */
-    page_contents = empty;
-    page_tail = page_head;
-#if 0
-    vlink(page_head) = null;
-#endif
-    last_glue = max_halfword;
-    last_penalty = 0;
-    last_kern = 0;
-    last_node_type = -1;
-    page_depth = 0;
-    page_max_depth = 0;
-
-    initialize_equivalents();
-    no_new_control_sequence = true;     /* new identifiers are usually forbidden */
-    init_primitives();
-
-    mag_set = 0;
-    initialize_marks();
-    initialize_read();
-
-    static_pdf = init_pdf_struct(static_pdf); /* should be init_backend() */
-
-    format_ident = 0;
-    format_name = get_nullstr();
-    initialize_directions();
-    initialize_write_files();
-    seconds_and_micros(epochseconds, microseconds);
-    initialize_start_time(static_pdf);
-
-    edit_name_start = 0;
-    stop_at_space = true;
-
-    if (ini_version) {
-        /* Initialize table entries (done by \.{INITEX} only) */
-
-        init_node_mem(500);
-        initialize_tokens();
-        /* Initialize the special list heads and constant nodes */
-        initialize_alignments();
-        initialize_buildpage();
-
-        initialize_active();
-
-        set_eq_type(undefined_control_sequence, undefined_cs_cmd);
-        set_equiv(undefined_control_sequence, null);
-        set_eq_level(undefined_control_sequence, level_zero);
-        for (k = null_cs; k <= (eqtb_top - 1); k++)
-            eqtb[k] = eqtb[undefined_control_sequence];
-        set_equiv(glue_base, zero_glue);
-        set_eq_level(glue_base, level_one);
-        set_eq_type(glue_base, glue_ref_cmd);
-        for (k = glue_base + 1; k <= local_base - 1; k++) {
-            eqtb[k] = eqtb[glue_base];
-        }
-        par_shape_par_ptr = null;
-        set_eq_type(par_shape_loc, shape_ref_cmd);
-        set_eq_level(par_shape_loc, level_one);
-        for (k = etex_pen_base; k <= (etex_pens - 1); k++)
-            eqtb[k] = eqtb[par_shape_loc];
-        for (k = output_routine_loc; k <= toks_base + biggest_reg; k++)
-            eqtb[k] = eqtb[undefined_control_sequence];
-        box(0) = null;
-        set_eq_type(box_base, box_ref_cmd);
-        set_eq_level(box_base, level_one);
-        for (k = box_base + 1; k <= (box_base + biggest_reg); k++)
-            eqtb[k] = eqtb[box_base];
-        cur_font_par = null_font;
-        set_eq_type(cur_font_loc, data_cmd);
-        set_eq_level(cur_font_loc, level_one);
-        set_equiv(cat_code_base, 0);
-        set_eq_type(cat_code_base, data_cmd);
-        set_eq_level(cat_code_base, level_one);
-        eqtb[internal_math_param_base] = eqtb[cat_code_base];
-        eqtb[lc_code_base] = eqtb[cat_code_base];
-        eqtb[uc_code_base] = eqtb[cat_code_base];
-        eqtb[sf_code_base] = eqtb[cat_code_base];
-        eqtb[math_code_base] = eqtb[cat_code_base];
-        cat_code_table_par = 0;
-        initialize_math_codes();
-        initialize_text_codes();
-        initex_cat_codes(0);
-        for (k = '0'; k <= '9'; k++)
-            set_math_code(k, math_use_current_family_code, 0, k, level_one);
-        for (k = 'A'; k <= 'Z'; k++) {
-            set_math_code(k, math_use_current_family_code, 1, k, level_one);
-            set_math_code((k + 32), math_use_current_family_code, 1, (k + 32), level_one);
-            set_lc_code(k, k + 32, level_one);
-            set_lc_code(k + 32, k + 32, level_one);
-            set_uc_code(k, k, level_one);
-            set_uc_code(k + 32, k, level_one);
-            set_sf_code(k, 999, level_one);
-        }
-        for (k = int_base; k <= attribute_base - 1; k++)
-            eqtb[k].cint = 0;
-        for (k = attribute_base; k <= del_code_base - 1; k++)
-            eqtb[k].cint = UNUSED_ATTRIBUTE;
-        mag_par = 1000;
-        tolerance_par = 10000;
-        hang_after_par = 1;
-        max_dead_cycles_par = 25;
-        math_pre_display_gap_factor_par = 2000;
-        escape_char_par = '\\';
-        end_line_char_par = carriage_return;
-        set_del_code('.', 0, 0, 0, 0, level_one); /* this null delimiter is used in error recovery */
-        ex_hyphen_char_par = '-';
-        output_box_par = 255;
-        for (k = dimen_base; k <= eqtb_size; k++)
-            eqtb[k].cint = 0;
-        page_left_offset_par = one_inch;
-        page_top_offset_par = one_inch;
-        page_right_offset_par = one_inch;
-        page_bottom_offset_par = one_inch;
-        ini_init_primitives();
-        hash_used = frozen_control_sequence;    /* nothing is used */
-        hash_high = 0;
-        cs_count = 0;
-        set_eq_type(frozen_dont_expand, dont_expand_cmd);
-        cs_text(frozen_dont_expand) = maketexstring("notexpanded:");
-        set_eq_type(frozen_primitive, ignore_spaces_cmd);
-        set_equiv(frozen_primitive, 1);
-        set_eq_level(frozen_primitive, level_one);
-        cs_text(frozen_primitive) = maketexstring("primitive");
-        create_null_font();
-        font_bytes = 0;
-        px_dimen_par = one_bp;
-        math_eqno_gap_step_par = 1000 ;
-        cs_text(frozen_protection) = maketexstring("inaccessible");
-        format_ident = maketexstring(" (INITEX)");
-        cs_text(end_write) = maketexstring("endwrite");
-        set_eq_level(end_write, level_one);
-        set_eq_type(end_write, outer_call_cmd);
-        set_equiv(end_write, null);
-
-    }
-    synctexoffset = int_base + synctex_code;
-
-}
+% maincontrol.w
+%
+% Copyright 2009-2010 Taco Hoekwater <taco@@luatex.org>
+%
+% 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 <http://www.gnu.org/licenses/>.
+
+@ @c
+
+#include "ptexlib.h"
+#include "lua/luatex-api.h"
+
+@ @c
+#define mode     mode_par
+#define tail     tail_par
+#define head     head_par
+#define dir_save dirs_par
+
+@ We come now to the |main_control| routine, which contains the master
+switch that causes all the various pieces of \TeX\ to do their things,
+in the right order.
+
+In a sense, this is the grand climax of the program: It applies all the
+tools that we have worked so hard to construct. In another sense, this is
+the messiest part of the program: It necessarily refers to other pieces
+of code all over the place, so that a person can't fully understand what is
+going on without paging back and forth to be reminded of conventions that
+are defined elsewhere. We are now at the hub of the web, the central nervous
+system that touches most of the other parts and ties them together.
+@^brain@>
+
+The structure of |main_control| itself is quite simple. There's a label
+called |big_switch|, at which point the next token of input is fetched
+using |get_x_token|. Then the program branches at high speed into one of
+about 100 possible directions, based on the value of the current
+mode and the newly fetched command code; the sum |abs(mode)+cur_cmd|
+indicates what to do next. For example, the case `|vmode+letter|' arises
+when a letter occurs in vertical mode (or internal vertical mode); this
+case leads to instructions that initialize a new paragraph and enter
+horizontal mode.
+
+The big |case| statement that contains this multiway switch has been labeled
+|reswitch|, so that the program can |goto reswitch| when the next token
+has already been fetched. Most of the cases are quite short; they call
+an ``action procedure'' that does the work for that case, and then they
+either |goto reswitch| or they ``fall through'' to the end of the |case|
+statement, which returns control back to |big_switch|. Thus, |main_control|
+is not an extremely large procedure, in spite of the multiplicity of things
+it must do; it is small enough to be handled by PASCAL compilers that put
+severe restrictions on procedure size.
+@!@^action procedure@>
+
+One case is singled out for special treatment, because it accounts for most
+of \TeX's activities in typical applications. The process of reading simple
+text and converting it into |char_node| records, while looking for ligatures
+and kerns, is part of \TeX's ``inner loop''; the whole program runs
+efficiently when its inner loop is fast, so this part has been written
+with particular care.
+
+We leave the |space_factor| unchanged if |sf_code(cur_chr)=0|; otherwise we
+set it equal to |sf_code(cur_chr)|, except that it should never change
+from a value less than 1000 to a value exceeding 1000. The most common
+case is |sf_code(cur_chr)=1000|, so we want that case to be fast.
+
+ at c
+void adjust_space_factor(void)
+{
+    halfword s = get_sf_code(cur_chr);
+    if (s == 1000) {
+        space_factor_par = 1000;
+    } else if (s < 1000) {
+        if (s > 0)
+            space_factor_par = s;
+    } else if (space_factor_par < 1000) {
+        space_factor_par = 1000;
+    } else {
+        space_factor_par = s;
+    }
+}
+
+@ From Knuth: ``Having |font_glue| allocated for each text font saves
+both time and memory.''  That may be true, but it also punches through
+the API wall for fonts, so I removed that -- Taco. But a bit of caching
+is very welcome, which is why I need to have the next two globals:
+
+@ To handle the execution state of |main_control|'s eternal loop,
+an extra global variable is used, along with a macro to define
+its values.
+
+ at c
+#define goto_next 0
+#define goto_skip_token 1
+#define goto_return 2
+
+static int main_control_state;
+
+@* Main control helpers.
+
+Here are all the functions that are called from |main_control| that
+are not already defined elsewhere. For the moment, this list simply
+in the order that the appear in |init_main_control|, below.
+
+@
+ at c
+static void run_char_num (void) {
+    scan_char_num();
+    cur_chr = cur_val;
+    adjust_space_factor();
+    tail_append(new_char(cur_font_par, cur_chr));
+}
+
+static void run_char (void) {
+    adjust_space_factor();
+    tail_append(new_char(cur_font_par, cur_chr));
+}
+
+@
+The occurrence of blank spaces is almost part of \TeX's inner loop,
+since we usually encounter about one space for every five non-blank characters.
+Therefore |main_control| gives second-highest priority to ordinary spaces.
+
+When a glue parameter like \.{\\spaceskip} is set to `\.{0pt}', we will
+see to it later that the corresponding glue specification is precisely
+|zero_glue|, not merely a pointer to some specification that happens
+to be full of zeroes. Therefore it is simple to test whether a glue parameter
+is zero or~not.
+
+ at c
+static void run_app_space (void) {
+    halfword p; /* was a global temp_ptr */
+    int method = disable_space_par ;
+    if (method == 1) {
+        /* don't inject anything, not even zero skip */
+    } else if (method == 2) {
+        p = new_glue(zero_glue);
+        couple_nodes(tail,p);
+        tail = p;
+    } else if ((abs(mode) + cur_cmd == hmode + spacer_cmd) && (!(space_factor_par == 1000))) {
+        app_space();
+    } else {
+        /* Append a normal inter-word space to the current list */
+        if (glue_is_zero(space_skip_par)) {
+            /* Find the glue specification for text spaces in the current font */
+            p = new_glue(zero_glue);
+            width(p) = space(cur_font_par);
+            stretch(p) = space_stretch(cur_font_par);
+            shrink(p) = space_shrink(cur_font_par);
+
+        } else {
+            p = new_param_glue(space_skip_code);
+        }
+        /* so from now we have a subtype with spaces: */
+        subtype(p) = space_skip_code + 1 ;
+        couple_nodes(tail,p);
+        tail = p;
+    }
+}
+
+@ Append a |boundary_node|
+ at c
+static void run_boundary (void) {
+    halfword n ;
+    n = new_node(boundary_node,cur_chr);
+    if ((cur_chr == 1) || (cur_chr == 2) ) {
+        /* user boundary or protrusion boundary */
+        scan_int();
+        boundary_value(n) = cur_val;
+    }
+    couple_nodes(tail, n);
+    tail = n;
+}
+
+@ @c
+static void run_char_ghost (void) {
+    int t;
+    t = cur_chr;
+    get_x_token();
+    if ((cur_cmd == letter_cmd) || (cur_cmd == other_char_cmd)
+        || (cur_cmd == char_given_cmd) || (cur_cmd == char_num_cmd)) {
+        halfword p = new_glyph(get_cur_font(), cur_chr);
+        if (t == 0) {
+            set_is_leftghost(p);
+        } else {
+            set_is_rightghost(p);
+        }
+        tail_append(p);
+    }
+}
+
+@ @c
+static void run_relax (void) {
+    return;
+}
+
+@ |ignore_spaces| is a special case: after it has acted, |get_x_token| has already
+fetched the next token from the input, so that operation in |main_control|
+should be skipped.
+
+ at c
+static void run_ignore_spaces (void) {
+    if (cur_chr == 0) {
+        /* Get the next non-blank non-call... */
+        do {
+            get_x_token();
+        } while (cur_cmd == spacer_cmd);
+        main_control_state = goto_skip_token;
+    } else {
+        int t = scanner_status;
+        scanner_status = normal;
+        get_next();
+        scanner_status = t;
+        cur_cs = prim_lookup(cs_text(cur_cs));
+        if (cur_cs != undefined_primitive) {
+            cur_cmd = get_prim_eq_type(cur_cs);
+            cur_chr = get_prim_equiv(cur_cs);
+            cur_tok = (cur_cmd * STRING_OFFSET) + cur_chr;
+            main_control_state = goto_skip_token;
+        }
+    }
+}
+
+@ |stop| is the second special case. We want |main_control| to return to its caller
+if there is nothing left to do.
+
+ at c
+static void run_stop (void) {
+    if (its_all_over())
+       main_control_state= goto_return; /* this is the only way out */
+}
+
+@ @c
+static void run_non_math_math (void) {
+    back_input();
+    new_graf(true);
+}
+
+@ @c
+static void run_math_char_num (void) {
+    mathcodeval mval;           /* to build up an argument to |set_math_char| */
+    if (cur_chr == 0)
+        mval = scan_mathchar(tex_mathcode);
+    else if (cur_chr == 1)
+        mval = scan_mathchar(umath_mathcode);
+    else
+        mval = scan_mathchar(umathnum_mathcode);
+    math_char_in_text(mval);
+}
+
+@ @c
+static void run_math_given (void) {
+    mathcodeval mval;           /* to build up an argument to |set_math_char| */
+    mval = mathchar_from_integer(cur_chr, tex_mathcode);
+    math_char_in_text(mval);
+}
+
+static void run_xmath_given (void) {
+    mathcodeval mval;           /* to build up an argument to |set_math_char| */
+    mval = mathchar_from_integer(cur_chr, umath_mathcode);
+    math_char_in_text(mval);
+}
+
+@  The most important parts of |main_control| are concerned with \TeX's
+chief mission of box-making. We need to control the activities that put
+entries on vlists and hlists, as well as the activities that convert
+those lists into boxes. All of the necessary machinery has already been
+developed; it remains for us to ``push the buttons'' at the right times.
+
+As an introduction to these routines, let's consider one of the simplest
+cases: What happens when `\.{\\hrule}' occurs in vertical mode, or
+`\.{\\vrule}' in horizontal mode or math mode? The code in |main_control|
+is short, since the |scan_rule_spec| routine already does most of what is
+required; thus, there is no need for a special action procedure.
+
+Note that baselineskip calculations are disabled after a rule in vertical
+mode, by setting |prev_depth:=ignore_depth|.
+
+ at c
+static void run_rule (void) {
+    tail_append(scan_rule_spec());
+    if (abs(mode) == vmode)
+        prev_depth_par = ignore_depth;
+    else if (abs(mode) == hmode)
+        space_factor_par = 1000;
+}
+
+@
+Many of the actions related to box-making are triggered by the appearance
+of braces in the input. For example, when the user says `\.{\\hbox}
+\.{to} \.{100pt\{$\langle\,\hbox{hlist}\,\rangle$\}}' in vertical mode,
+the information about the box size (100pt, |exactly|) is put onto |save_stack|
+with a level boundary word just above it, and |cur_group:=adjusted_hbox_group|;
+\TeX\ enters restricted horizontal mode to process the hlist. The right
+brace eventually causes |save_stack| to be restored to its former state,
+at which time the information about the box size (100pt, |exactly|) is
+available once again; a box is packaged and we leave restricted horizontal
+mode, appending the new box to the current list of the enclosing mode
+(in this case to the current list of vertical mode), followed by any
+vertical adjustments that were removed from the box by |hpack|.
+
+The next few sections of the program are therefore concerned with the
+treatment of left and right curly braces.
+
+If a left brace occurs in the middle of a page or paragraph, it simply
+introduces a new level of grouping, and the matching right brace will not have
+such a drastic effect. Such grouping affects neither the mode nor the
+current list.
+
+ at c
+static void run_left_brace (void) {
+    new_save_level(simple_group);
+    eq_word_define(int_base + no_local_whatsits_code, 0);
+    eq_word_define(int_base + no_local_dirs_code, 0);
+}
+
+static void run_begin_group (void) {
+    new_save_level(semi_simple_group);
+    eq_word_define(int_base + no_local_whatsits_code, 0);
+    eq_word_define(int_base + no_local_dirs_code, 0);
+}
+
+static void run_end_group (void) {
+    if (cur_group == semi_simple_group) {
+        fixup_directions();
+    } else {
+        off_save();
+    }
+}
+
+@ Constructions that require a box are started by calling |scan_box| with
+a specified context code. The |scan_box| routine verifies
+that a |make_box| command comes next and then it calls |begin_box|.
+
+ at c
+static void run_move (void) {
+    int t = cur_chr;
+    scan_normal_dimen();
+    if (t == 0)
+        scan_box(cur_val);
+    else
+        scan_box(-cur_val);
+}
+
+@ @c
+static void run_leader_ship (void) {
+    scan_box(leader_flag - a_leaders + cur_chr);
+}
+
+@ @c
+static void run_make_box (void) {
+    begin_box(0);
+}
+
+@ @c
+static void run_box_dir (void) {
+    scan_register_num();
+    cur_box = box(cur_val);
+    scan_optional_equals();
+    scan_direction();
+    if (cur_box != null)
+        box_dir(cur_box) = cur_val;
+}
+
+@ There is a really small patch to add a new primitive called
+\.{\\quitvmode}. In vertical modes, it is identical to \.{\\indent},
+but in horizontal and math modes it is really a no-op (as opposed to
+\.{\\indent}, which executes the |indent_in_hmode| procedure).
+
+A paragraph begins when horizontal-mode material occurs in vertical mode,
+or when the paragraph is explicitly started by `\.{\\quitvmode}',
+`\.{\\indent}' or `\.{\\noindent}'.
+
+ at c
+static void run_start_par_vmode (void) {
+    new_graf((cur_chr > 0));
+}
+
+@ @c
+static void run_start_par (void) {
+   if (cur_chr != 2)
+       indent_in_hmode();
+}
+
+@ @c
+static void run_new_graf (void) {
+   back_input();
+   new_graf(true);
+}
+
+@ A paragraph ends when a |par_end| command is sensed, or when we are in
+horizontal mode when reaching the right brace of vertical-mode routines
+like \.{\\vbox}, \.{\\insert}, or \.{\\output}.
+
+ at c
+static void run_par_end_vmode (void) {
+    normal_paragraph();
+    if (mode > 0) {
+        checked_page_filter(vmode_par);
+        build_page();
+    }
+}
+
+@ @c
+static void run_par_end_hmode (void) {
+    if (align_state < 0)
+        off_save();         /* this tries to  recover from an alignment that didn't end properly */
+    end_graf(bottom_level); /* this takes us to the enclosing mode, if |mode>0| */
+    if (mode == vmode) {
+        checked_page_filter(hmode_par);
+        build_page();
+    }
+}
+
+@ @c
+static void append_italic_correction_mmode (void) {
+    tail_append(new_kern(0)); /* what subtype to use */
+}
+
+@ @c
+static void run_local_box (void) {
+    append_local_box(cur_chr);
+}
+
+@ @c
+static void run_halign_mmode (void) {
+    if (privileged()) {
+        if (cur_group == math_shift_group)
+            init_align();
+        else
+            off_save();
+    }
+}
+
+@ @c
+static void run_eq_no (void) {
+    if (privileged()) {
+        if (cur_group == math_shift_group)
+            start_eq_no();
+        else
+            off_save();
+    }
+}
+
+@ @c
+static void run_letter_mmode (void) {
+   set_math_char(get_math_code(cur_chr));
+}
+
+@ @c
+static void run_char_num_mmode (void) {
+    scan_char_num();
+    cur_chr = cur_val;
+    set_math_char(get_math_code(cur_chr));
+}
+
+@ @c
+static void run_math_char_num_mmode (void) {
+    mathcodeval mval;           /* to build up an argument to |set_math_char| */
+    if (cur_chr == 0)
+        mval = scan_mathchar(tex_mathcode);
+    else if (cur_chr == 1)
+        mval = scan_mathchar(umath_mathcode);
+    else
+        mval = scan_mathchar(umathnum_mathcode);
+    set_math_char(mval);
+}
+
+@ @c
+static void run_math_given_mmode (void) {
+    mathcodeval mval;           /* to build up an argument to |set_math_char| */
+    mval = mathchar_from_integer(cur_chr, tex_mathcode);
+    set_math_char(mval);
+}
+
+static void run_xmath_given_mmode (void) {
+    mathcodeval mval;           /* to build up an argument to |set_math_char| */
+    mval = mathchar_from_integer(cur_chr, umath_mathcode);
+    set_math_char(mval);
+}
+
+@ @c
+static void run_delim_num (void) {
+    mathcodeval mval;           /* to build up an argument to |set_math_char| */
+    if (cur_chr == 0)
+        mval = scan_delimiter_as_mathchar(tex_mathcode);
+    else
+        mval = scan_delimiter_as_mathchar(umath_mathcode);
+    set_math_char(mval);
+
+}
+
+@ @c
+static void run_vcenter (void) {
+    scan_spec(vcenter_group);
+    normal_paragraph();
+    push_nest();
+    mode = -vmode;
+    prev_depth_par = ignore_depth;
+    if (every_vbox_par != null)
+        begin_token_list(every_vbox_par, every_vbox_text);
+}
+
+@ @c
+static void run_math_style (void) {
+    tail_append(new_style((small_number) cur_chr));
+}
+
+@ @c
+static void run_non_script (void) {
+    tail_append(new_glue(zero_glue));
+    subtype(tail) = cond_math_glue;
+}
+
+@ @c
+static void run_math_choice (void) {
+    if (cur_chr == 0)
+        append_choices();
+    else
+        setup_math_style();
+}
+
+@ @c
+static void run_math_shift (void) {
+    if (cur_group == math_shift_group)
+        after_math();
+    else
+        off_save();
+}
+
+@ @c
+static void run_after_assignment (void) {
+    get_token();
+    after_token = cur_tok;
+}
+
+@ @c
+static void run_after_group (void) {
+    get_token();
+    save_for_after(cur_tok);
+}
+
+@ @c
+static void run_extension (void) {
+    do_extension(0);
+}
+
+static void run_normal (void) {
+{
+    switch (cur_chr) {
+        case save_pos_code:
+            new_whatsit(save_pos_node);
+            break;
+        case save_cat_code_table_code:
+            scan_int();
+            if ((cur_val < 0) || (cur_val > 0x7FFF)) {
+                print_err("Invalid \\catcode table");
+                help1("All \\catcode table ids must be between 0 and 0x7FFF");
+                error();
+            } else {
+                if (cur_val == cat_code_table_par) {
+                    print_err("Invalid \\catcode table");
+                    help1("You cannot overwrite the current \\catcode table");
+                    error();
+                } else {
+                    copy_cat_codes(cat_code_table_par, cur_val);
+                }
+            }
+            break;
+        case init_cat_code_table_code:
+            scan_int();
+            if ((cur_val < 0) || (cur_val > 0x7FFF)) {
+                print_err("Invalid \\catcode table");
+                help1("All \\catcode table ids must be between 0 and 0x7FFF");
+                error();
+            } else {
+                if (cur_val == cat_code_table_par) {
+                    print_err("Invalid \\catcode table");
+                    help1("You cannot overwrite the current \\catcode table");
+                    error();
+                } else {
+                    initex_cat_codes(cur_val);
+                }
+            }
+            break;
+        case set_random_seed_code:
+            /*  Negative random seed values are silently converted to positive ones */
+            scan_int();
+            if (cur_val < 0)
+                negate(cur_val);
+            random_seed = cur_val;
+            init_randoms(random_seed);
+            break;
+        case late_lua_code:
+            new_whatsit(late_lua_node); /* type == normal */
+            late_lua_name(tail) = scan_lua_state();
+            (void) scan_toks(false, false);
+            late_lua_data(tail) = def_ref;
+            break;
+        case expand_font_code:
+            read_expand_font();
+            break;
+        default:
+            confusion("int1");
+            break;
+        }
+    }
+}
+
+/*
+    this is experimental and not used for production, only for testing and writing
+    macros (some options stay)
+
+*/
+
+#define mathoption_set_int(A) \
+    scan_int(); \
+    word_define(mathoption_int_base+A, cur_val);
+
+static void run_option(void) {
+    int a = 0 ;
+    switch (cur_chr) {
+        case math_option_code:
+            if (scan_keyword("old")) {
+                mathoption_set_int(c_mathoption_old_code);
+            } else if (scan_keyword("noitaliccompensation")) {
+                mathoption_set_int(c_mathoption_no_italic_compensation_code);
+            } else if (scan_keyword("nocharitalic")) {
+                mathoption_set_int(c_mathoption_no_char_italic_code);
+            } else if (scan_keyword("useoldfractionscaling")) {
+                mathoption_set_int(c_mathoption_use_old_fraction_scaling_code);
+            } else if (scan_keyword("umathcodemeaning")) {
+                mathoption_set_int(c_mathoption_umathcode_meaning_code);
+            } else {
+                normal_warning("mathoption","unknown key");
+            }
+            break;
+        default:
+            /* harmless */
+            break;
+    }
+}
+
+@ For mode-independent commands, the following macro is useful.
+
+Also, there is a list of cases where the user has probably gotten into or out of math
+mode by mistake. \TeX\ will insert a dollar sign and rescan the current token, and
+it makes sense ot have a macro for that as well.
+
+ at c
+#define any_mode(A,B) jump_table[vmode+(A)]=B; jump_table[hmode+(A)]=B; jump_table[mmode+(A)]=B
+#define non_math(A,B) jump_table[vmode+(A)]=B; jump_table[hmode+(A)]=B;
+
+
+@ The |main_control| uses a jump table, and |init_main_control| sets that table up.
+ at c
+typedef void (*main_control_function) (void);
+main_control_function *jump_table;
+
+static void init_main_control (void) {
+    jump_table = xmalloc((mmode+max_command_cmd+1) * sizeof(main_control_function)) ;
+
+    jump_table[hmode + char_num_cmd] = run_char_num;
+    jump_table[hmode + letter_cmd] = run_char;
+    jump_table[hmode + other_char_cmd] = run_char;
+    jump_table[hmode + char_given_cmd] = run_char;
+    jump_table[hmode + spacer_cmd] = run_app_space;
+    jump_table[hmode + ex_space_cmd] = run_app_space;
+    jump_table[mmode + ex_space_cmd] = run_app_space;
+    jump_table[hmode + boundary_cmd] = run_boundary;
+    jump_table[hmode + char_ghost_cmd] = run_char_ghost;
+    jump_table[mmode + char_ghost_cmd] = run_char_ghost;
+    any_mode(relax_cmd, run_relax);
+    jump_table[vmode + spacer_cmd] = run_relax;
+    jump_table[mmode + spacer_cmd] = run_relax;
+    jump_table[mmode + boundary_cmd] = run_relax;
+    any_mode(ignore_spaces_cmd,run_ignore_spaces);
+    jump_table[vmode + stop_cmd] = run_stop;
+    jump_table[vmode + math_char_num_cmd] = run_non_math_math;
+    jump_table[vmode + math_given_cmd] = run_non_math_math;
+    jump_table[vmode + xmath_given_cmd] = run_non_math_math;
+    jump_table[hmode + math_char_num_cmd] = run_math_char_num;
+    jump_table[hmode + math_given_cmd] = run_math_given;
+    jump_table[hmode + xmath_given_cmd] = run_xmath_given;
+
+    jump_table[vmode + vmove_cmd] = report_illegal_case;
+    jump_table[hmode + hmove_cmd] = report_illegal_case;
+    jump_table[mmode + hmove_cmd] = report_illegal_case;
+    any_mode(last_item_cmd, report_illegal_case);
+    jump_table[vmode + vadjust_cmd] = report_illegal_case;
+    jump_table[vmode + ital_corr_cmd] = report_illegal_case;
+    non_math(eq_no_cmd,report_illegal_case);
+    any_mode(mac_param_cmd,report_illegal_case);
+
+    non_math(sup_mark_cmd, insert_dollar_sign);
+    non_math(sub_mark_cmd, insert_dollar_sign);
+    non_math(super_sub_script_cmd, insert_dollar_sign);
+    non_math(math_comp_cmd, insert_dollar_sign);
+    non_math(delim_num_cmd, insert_dollar_sign);
+    non_math(left_right_cmd, insert_dollar_sign);
+    non_math(above_cmd, insert_dollar_sign);
+    non_math(radical_cmd, insert_dollar_sign);
+    non_math(math_style_cmd, insert_dollar_sign);
+    non_math(math_choice_cmd, insert_dollar_sign);
+    non_math(vcenter_cmd, insert_dollar_sign);
+    non_math(non_script_cmd, insert_dollar_sign);
+    non_math(mkern_cmd, insert_dollar_sign);
+    non_math(limit_switch_cmd, insert_dollar_sign);
+    non_math(mskip_cmd, insert_dollar_sign);
+    non_math(math_accent_cmd, insert_dollar_sign);
+    jump_table[mmode + endv_cmd] =  insert_dollar_sign;
+    jump_table[mmode + par_end_cmd] =  insert_dollar_sign_par_end;
+    jump_table[mmode + stop_cmd] =  insert_dollar_sign;
+    jump_table[mmode + vskip_cmd] =  insert_dollar_sign;
+    jump_table[mmode + un_vbox_cmd] =  insert_dollar_sign;
+    jump_table[mmode + valign_cmd] =  insert_dollar_sign;
+    jump_table[mmode + hrule_cmd] =  insert_dollar_sign;
+    jump_table[mmode + no_hrule_cmd] =  insert_dollar_sign;
+    jump_table[vmode + hrule_cmd] = run_rule;
+    jump_table[vmode + no_hrule_cmd] = run_rule;
+    jump_table[hmode + vrule_cmd] = run_rule;
+    jump_table[hmode + no_vrule_cmd] = run_rule;
+    jump_table[mmode + vrule_cmd] = run_rule;
+    jump_table[mmode + no_vrule_cmd] = run_rule;
+    jump_table[vmode + vskip_cmd] = append_glue;
+    jump_table[hmode + hskip_cmd] = append_glue;
+    jump_table[mmode + hskip_cmd] = append_glue;
+    jump_table[mmode + mskip_cmd] = append_glue;
+    any_mode(kern_cmd, append_kern);
+    jump_table[mmode + mkern_cmd] = append_kern;
+    non_math(left_brace_cmd, run_left_brace);
+    any_mode(begin_group_cmd,run_begin_group);
+    any_mode(end_group_cmd, run_end_group);
+    any_mode(right_brace_cmd, handle_right_brace);
+    jump_table[vmode + hmove_cmd] = run_move;
+    jump_table[hmode + vmove_cmd] = run_move;
+    jump_table[mmode + vmove_cmd] = run_move;
+    any_mode(leader_ship_cmd, run_leader_ship);
+    any_mode(make_box_cmd, run_make_box);
+    any_mode(assign_box_dir_cmd, run_box_dir);
+    jump_table[vmode + start_par_cmd] = run_start_par_vmode;
+    jump_table[hmode + start_par_cmd] = run_start_par;
+    jump_table[mmode + start_par_cmd] = run_start_par;
+    jump_table[vmode + letter_cmd] = run_new_graf;
+    jump_table[vmode + other_char_cmd] = run_new_graf;
+    jump_table[vmode + char_num_cmd] = run_new_graf;
+    jump_table[vmode + char_given_cmd] = run_new_graf;
+    jump_table[vmode + char_ghost_cmd] = run_new_graf;
+    jump_table[vmode + math_shift_cmd] = run_new_graf;
+    jump_table[vmode + math_shift_cs_cmd] = run_new_graf;
+    jump_table[vmode + un_hbox_cmd] = run_new_graf;
+    jump_table[vmode + vrule_cmd] = run_new_graf;
+    jump_table[vmode + no_vrule_cmd] = run_new_graf;
+    jump_table[vmode + accent_cmd] = run_new_graf;
+    jump_table[vmode + discretionary_cmd] = run_new_graf;
+    jump_table[vmode + hskip_cmd] = run_new_graf;
+    jump_table[vmode + valign_cmd] = run_new_graf;
+    jump_table[vmode + ex_space_cmd] = run_new_graf;
+    jump_table[vmode + boundary_cmd] = run_new_graf;
+    jump_table[vmode + par_end_cmd] = run_par_end_vmode;
+    jump_table[hmode + par_end_cmd] = run_par_end_hmode;
+    jump_table[hmode + stop_cmd] = head_for_vmode;
+    jump_table[hmode + vskip_cmd] = head_for_vmode;
+    jump_table[hmode + hrule_cmd] = head_for_vmode;
+    jump_table[hmode + no_hrule_cmd] = head_for_vmode;
+    jump_table[hmode + un_vbox_cmd] = head_for_vmode;
+    jump_table[hmode + halign_cmd] = head_for_vmode;
+    any_mode(insert_cmd,begin_insert_or_adjust);
+    jump_table[hmode + vadjust_cmd] = begin_insert_or_adjust;
+    jump_table[mmode + vadjust_cmd] = begin_insert_or_adjust;
+    any_mode(mark_cmd, handle_mark);
+    any_mode(break_penalty_cmd, append_penalty);
+    any_mode(remove_item_cmd, delete_last);
+    jump_table[vmode + un_vbox_cmd] = unpackage;
+    jump_table[hmode + un_hbox_cmd] = unpackage;
+    jump_table[mmode + un_hbox_cmd] = unpackage;
+    jump_table[hmode + ital_corr_cmd] = append_italic_correction;
+    jump_table[mmode + ital_corr_cmd] = append_italic_correction_mmode;
+    jump_table[hmode + discretionary_cmd] = append_discretionary;
+    jump_table[mmode + discretionary_cmd] = append_discretionary;
+    any_mode(assign_local_box_cmd, run_local_box);
+    jump_table[hmode + accent_cmd] = make_accent;
+    any_mode(car_ret_cmd,align_error);
+    any_mode(tab_mark_cmd,align_error);
+    any_mode(no_align_cmd,no_align_error);
+    any_mode(omit_cmd, omit_error);
+    jump_table[vmode + halign_cmd] = init_align;
+    jump_table[hmode + valign_cmd] = init_align;
+    jump_table[mmode + halign_cmd] = run_halign_mmode;
+    jump_table[vmode + endv_cmd] = do_endv;
+    jump_table[hmode + endv_cmd] = do_endv;
+    any_mode(end_cs_name_cmd, cs_error);
+    jump_table[hmode + math_shift_cmd] = init_math;
+    jump_table[hmode + math_shift_cs_cmd] = init_math;
+    jump_table[mmode + eq_no_cmd] = run_eq_no;
+    jump_table[mmode + left_brace_cmd] = math_left_brace;
+    jump_table[mmode + letter_cmd] = run_letter_mmode;
+    jump_table[mmode + other_char_cmd] = run_letter_mmode;
+    jump_table[mmode + char_given_cmd] = run_letter_mmode;
+    jump_table[mmode + char_num_cmd] = run_char_num_mmode;
+    jump_table[mmode + math_char_num_cmd] = run_math_char_num_mmode;
+    jump_table[mmode + math_given_cmd] = run_math_given_mmode;
+    jump_table[mmode + xmath_given_cmd] = run_xmath_given_mmode;
+    jump_table[mmode + delim_num_cmd] = run_delim_num;
+    jump_table[mmode + math_comp_cmd] = math_math_comp;
+    jump_table[mmode + limit_switch_cmd] = math_limit_switch;
+    jump_table[mmode + radical_cmd] = math_radical;
+    jump_table[mmode + accent_cmd] = math_ac;
+    jump_table[mmode + math_accent_cmd] = math_ac;
+    jump_table[mmode + vcenter_cmd] = run_vcenter;
+    jump_table[mmode + math_style_cmd] = run_math_style;
+    jump_table[mmode + non_script_cmd] = run_non_script;
+    jump_table[mmode + math_choice_cmd] = run_math_choice;
+    jump_table[mmode + above_cmd] = math_fraction;
+    jump_table[mmode + sub_mark_cmd] = sub_sup;
+    jump_table[mmode + sup_mark_cmd] = sub_sup;
+    jump_table[mmode + super_sub_script_cmd] = sub_sup;
+    jump_table[mmode + left_right_cmd] = math_left_right;
+    jump_table[mmode + math_shift_cmd] = run_math_shift;
+    jump_table[mmode + math_shift_cs_cmd] = run_math_shift;
+    any_mode(toks_register_cmd, prefixed_command);
+    any_mode(assign_toks_cmd, prefixed_command);
+    any_mode(assign_int_cmd, prefixed_command);
+    any_mode(assign_attr_cmd, prefixed_command);
+    any_mode(assign_dir_cmd, prefixed_command);
+    any_mode(assign_dimen_cmd, prefixed_command);
+    any_mode(assign_glue_cmd, prefixed_command);
+    any_mode(assign_mu_glue_cmd, prefixed_command);
+    any_mode(assign_font_dimen_cmd, prefixed_command);
+    any_mode(assign_font_int_cmd, prefixed_command);
+    any_mode(set_aux_cmd, prefixed_command);
+    any_mode(set_prev_graf_cmd, prefixed_command);
+    any_mode(set_page_dimen_cmd, prefixed_command);
+    any_mode(set_page_int_cmd, prefixed_command);
+    any_mode(set_box_dimen_cmd, prefixed_command);
+    any_mode(set_tex_shape_cmd, prefixed_command);
+    any_mode(set_etex_shape_cmd, prefixed_command);
+    any_mode(def_char_code_cmd, prefixed_command);
+    any_mode(def_del_code_cmd, prefixed_command);
+    any_mode(extdef_math_code_cmd, prefixed_command);
+    any_mode(extdef_del_code_cmd, prefixed_command);
+    any_mode(def_family_cmd, prefixed_command);
+    any_mode(set_math_param_cmd, prefixed_command);
+    any_mode(set_font_cmd, prefixed_command);
+    any_mode(def_font_cmd, prefixed_command);
+    any_mode(letterspace_font_cmd, prefixed_command);
+    any_mode(copy_font_cmd, prefixed_command);
+    any_mode(set_font_id_cmd, prefixed_command);
+    any_mode(register_cmd, prefixed_command);
+    any_mode(advance_cmd, prefixed_command);
+    any_mode(multiply_cmd, prefixed_command);
+    any_mode(divide_cmd, prefixed_command);
+    any_mode(prefix_cmd, prefixed_command);
+    any_mode(let_cmd, prefixed_command);
+    any_mode(shorthand_def_cmd, prefixed_command);
+    any_mode(read_to_cs_cmd, prefixed_command);
+    any_mode(def_cmd, prefixed_command);
+    any_mode(set_box_cmd, prefixed_command);
+    any_mode(hyph_data_cmd, prefixed_command);
+    any_mode(set_interaction_cmd, prefixed_command);
+    any_mode(after_assignment_cmd,run_after_assignment);
+    any_mode(after_group_cmd,run_after_group);
+    any_mode(in_stream_cmd,open_or_close_in);
+    any_mode(message_cmd,issue_message);
+    any_mode(case_shift_cmd, shift_case);
+    any_mode(xray_cmd, show_whatever);
+    any_mode(normal_cmd, run_normal);
+    any_mode(extension_cmd, run_extension);
+    any_mode(option_cmd, run_option);
+}
+
+@ And here is |main_control| itself.  It is quite short nowadays.
+
+ at c
+void main_control(void)
+{
+    main_control_state = goto_next;
+    init_main_control () ;
+
+    if (equiv(every_job_loc) != null)
+        begin_token_list(equiv(every_job_loc), every_job_text);
+
+    while (1) {
+        if (main_control_state == goto_skip_token)
+                main_control_state = goto_next; /* reset */
+        else
+            get_x_token();
+
+        /* Give diagnostic information, if requested */
+        /* When a new token has just been fetched at |big_switch|, we have an
+           ideal place to monitor \TeX's activity. */
+        if (interrupt != 0 && OK_to_interrupt) {
+            back_input();
+            check_interrupt();
+            continue;
+        }
+        if (tracing_commands_par > 0)
+            show_cur_cmd_chr();
+
+        (jump_table[(abs(mode) + cur_cmd)])(); /* run the command */
+
+        if (main_control_state == goto_return) {
+            return;
+        }
+    }
+    return; /* not reached */
+}
+
+@ @c
+void app_space(void)
+{                               /* handle spaces when |space_factor<>1000| */
+    halfword q;                 /* glue node */
+    if ((space_factor_par >= 2000) && (! glue_is_zero(xspace_skip_par))) {
+        q = new_param_glue(xspace_skip_code);
+        /* so from now we have a subtype with spaces: */
+        subtype(q) = xspace_skip_code + 1;
+    } else {
+        if (!glue_is_zero(space_skip_par)) {
+            q = new_glue(space_skip_par);
+        } else {
+            q = new_glue(zero_glue);
+            width(q) = space(cur_font_par);
+            stretch(q) = space_stretch(cur_font_par);
+            shrink(q) = space_shrink(cur_font_par);
+        }
+        /* Modify the glue specification in |q| according to the space factor */
+        if (space_factor_par >= 2000)
+            width(q) = width(q) + extra_space(cur_font_par);
+        stretch(q) = xn_over_d(stretch(q), space_factor_par, 1000);
+        shrink(q) = xn_over_d(shrink(q), 1000, space_factor_par);
+
+        /* so from now we have a subtype with spaces: */
+        subtype(q) = space_skip_code + 1;
+    }
+    couple_nodes(tail, q);
+    tail = q;
+}
+
+@ @c
+void insert_dollar_sign(void)
+{
+    back_input();
+    cur_tok = math_shift_token + '$';
+    print_err("Missing $ inserted");
+    help2("I've inserted a begin-math/end-math symbol since I think",
+          "you left one out. Proceed, with fingers crossed.");
+    ins_error();
+}
+
+@  We can silently ignore  \.{\\par}s in a math formula.
+
+ at c
+void insert_dollar_sign_par_end(void)
+{
+    if (!suppress_mathpar_error_par) {
+        insert_dollar_sign() ;
+    }
+}
+
+@ The `|you_cant|' procedure prints a line saying that the current command
+is illegal in the current mode; it identifies these things symbolically.
+
+ at c
+void you_cant(void)
+{
+    print_err("You can't use `");
+    print_cmd_chr((quarterword) cur_cmd, cur_chr);
+    print_in_mode(mode);
+}
+
+@
+When erroneous situations arise, \TeX\ usually issues an error message
+specific to the particular error. For example, `\.{\\noalign}' should
+not appear in any mode, since it is recognized by the |align_peek| routine
+in all of its legitimate appearances; a special error message is given
+when `\.{\\noalign}' occurs elsewhere. But sometimes the most appropriate
+error message is simply that the user is not allowed to do what he or she
+has attempted. For example, `\.{\\moveleft}' is allowed only in vertical mode,
+and `\.{\\lower}' only in non-vertical modes.  Such cases are enumerated
+here and in the other sections referred to under `See also \dots.'
+
+ at c
+void report_illegal_case(void)
+{
+    you_cant();
+    help4("Sorry, but I'm not programmed to handle this case;",
+          "I'll just pretend that you didn''t ask for it.",
+          "If you're in the wrong mode, you might be able to",
+          "return to the right one by typing `I}' or `I$' or `I\\par'.");
+    error();
+}
+
+@ Some operations are allowed only in privileged modes, i.e., in cases
+that |mode>0|. The |privileged| function is used to detect violations
+of this rule; it issues an error message and returns |false| if the
+current |mode| is negative.
+
+ at c
+boolean privileged(void)
+{
+    if (mode > 0) {
+        return true;
+    } else {
+        report_illegal_case();
+        return false;
+    }
+}
+
+@ We don't want to leave |main_control| immediately when a |stop| command
+is sensed, because it may be necessary to invoke an \.{\\output} routine
+several times before things really grind to a halt. (The output routine
+might even say `\.{\\gdef\\end\{...\}}', to prolong the life of the job.)
+Therefore |its_all_over| is |true| only when the current page
+and contribution list are empty, and when the last output was not a
+``dead cycle.''
+
+ at c
+boolean its_all_over(void)
+{                               /* do this when \.{\\end} or \.{\\dump} occurs */
+    if (privileged()) {
+        if ((page_head == page_tail) && (head == tail) && (dead_cycles == 0)) {
+            return true;
+        }
+        back_input();           /* we will try to end again after ejecting residual material */
+        tail_append(new_null_box());
+        width(tail) = hsize_par;
+        tail_append(new_glue(fill_glue));
+        tail_append(new_penalty(-010000000000,final_penalty));
+        normal_page_filter(end);
+        build_page();           /* append \.{\\hbox to \\hsize\{\}\\vfill\\penalty-'10000000000} */
+    }
+    return false;
+}
+
+
+@ The |hskip| and |vskip| command codes are used for control sequences
+like \.{\\hss} and \.{\\vfil} as well as for \.{\\hskip} and \.{\\vskip}.
+The difference is in the value of |cur_chr|.
+
+All the work relating to glue creation has been relegated to the
+following subroutine. It does not call |build_page|, because it is
+used in at least one place where that would be a mistake.
+
+ at c
+void append_glue(void)
+{
+    int s = cur_chr;
+    switch (s) {
+        case fil_code:
+            cur_val = new_glue(fil_glue);
+            break;
+        case fill_code:
+            cur_val = new_glue(fill_glue);
+            break;
+        case ss_code:
+            cur_val = new_glue(ss_glue);
+            break;
+        case fil_neg_code:
+            cur_val = new_glue(fil_neg_glue);
+            break;
+        case skip_code:
+            scan_glue(glue_val_level);
+            break;
+        case mskip_code:
+            scan_glue(mu_val_level);
+            break;
+    }
+    /* now |cur_val| points to the glue specification */
+    tail_append(new_glue(cur_val));
+    flush_node(cur_val);
+    if (s > skip_code) {
+        subtype(tail) = mu_glue;
+    }
+}
+
+@ @c
+void append_kern(void)
+{
+    int s;                      /* |subtype| of the kern node */
+    s = cur_chr;
+    scan_dimen((s == mu_glue), false, false);
+    tail_append(new_kern(cur_val));
+    subtype(tail) = (quarterword) s;
+}
+
+@ We have to deal with errors in which braces and such things are not
+properly nested. Sometimes the user makes an error of commission by
+inserting an extra symbol, but sometimes the user makes an error of omission.
+\TeX\ can't always tell one from the other, so it makes a guess and tries
+to avoid getting into a loop.
+
+The |off_save| routine is called when the current group code is wrong. It tries
+to insert something into the user's input that will help clean off
+the top level.
+
+ at c
+void off_save(void)
+{
+    halfword p, q;              /* inserted token */
+    if (cur_group == bottom_level) {
+        /* Drop current token and complain that it was unmatched */
+        print_err("Extra ");
+        print_cmd_chr((quarterword) cur_cmd, cur_chr);
+        help1("Things are pretty mixed up, but I think the worst is over.");
+        error();
+
+    } else {
+        back_input();
+        p = get_avail();
+        set_token_link(temp_token_head, p);
+        print_err("Missing ");
+        /* Prepare to insert a token that matches |cur_group|, and print what it is */
+        /* At this point, |link(temp_token_head)=p|, a pointer to an empty one-word node. */
+        switch (cur_group) {
+            case semi_simple_group:
+                set_token_info(p, cs_token_flag + frozen_end_group);
+                tprint_esc("endgroup");
+                break;
+            case math_shift_group:
+                set_token_info(p, math_shift_token + '$');
+                print_char('$');
+                break;
+            case math_left_group:
+                set_token_info(p, cs_token_flag + frozen_right);
+                q = get_avail();
+                set_token_link(p, q);
+                p = token_link(p);
+                set_token_info(p, other_token + '.');
+                tprint_esc("right.");
+                break;
+            default:
+                set_token_info(p, right_brace_token + '}');
+                print_char('}');
+                break;
+        }
+        tprint(" inserted");
+        ins_list(token_link(temp_token_head));
+        help5("I've inserted something that you may have forgotten.",
+              "(See the <inserted text> above.)",
+              "With luck, this will get me unwedged. But if you",
+              "really didn't forget anything, try typing `2' now; then",
+              "my insertion and my current dilemma will both disappear.");
+        error();
+    }
+}
+
+@ The routine for a |right_brace| character branches into many subcases,
+since a variety of things may happen, depending on |cur_group|. Some
+types of groups are not supposed to be ended by a right brace; error
+messages are given in hopes of pinpointing the problem. Most branches
+of this routine will be filled in later, when we are ready to understand
+them; meanwhile, we must prepare ourselves to deal with such errors.
+
+ at c
+void handle_right_brace(void)
+{
+    halfword p, q;              /* for short-term use */
+    scaled d;                   /* holds |split_max_depth| in |insert_group| */
+    int f;                      /* holds |floating_penalty| in |insert_group| */
+    p = null;
+    switch (cur_group) {
+        case simple_group:
+            fixup_directions();
+            break;
+        case bottom_level:
+            print_err("Too many }'s");
+            help2("You've closed more groups than you opened.",
+                  "Such booboos are generally harmless, so keep going.");
+            error();
+            break;
+        case semi_simple_group:
+        case math_shift_group:
+        case math_left_group:
+            extra_right_brace();
+            break;
+        case hbox_group:
+            /* When the right brace occurs at the end of an \.{\\hbox} or \.{\\vbox} or
+               \.{\\vtop} construction, the |package| routine comes into action. We might
+               also have to finish a paragraph that hasn't ended. */
+            package(0);
+            break;
+        case adjusted_hbox_group:
+            adjust_tail = adjust_head;
+            pre_adjust_tail = pre_adjust_head;
+            package(0);
+            break;
+        case vbox_group:
+            end_graf(vbox_group);
+            package(0);
+            break;
+        case vtop_group:
+            end_graf(vtop_group);
+            package(vtop_code);
+            break;
+        case insert_group:
+            end_graf(insert_group);
+            q = new_glue(split_top_skip_par);
+            d = split_max_depth_par;
+            f = floating_penalty_par;
+            unsave();
+            save_ptr--;
+            /* now |saved_value(0)| is the insertion number, or the |vadjust| subtype */
+            p = vpack(vlink(head), 0, additional, -1);
+            pop_nest();
+            if (saved_type(0) == saved_insert) {
+                tail_append(new_node(ins_node, saved_value(0)));
+                height(tail) = height(p) + depth(p);
+                ins_ptr(tail) = list_ptr(p);
+                split_top_ptr(tail) = q;
+                depth(tail) = d;
+                float_cost(tail) = f;
+            } else if (saved_type(0) == saved_adjust) {
+                tail_append(new_node(adjust_node, saved_value(0)));
+                adjust_ptr(tail) = list_ptr(p);
+                flush_node(q);
+            } else {
+                confusion("insert_group");
+            }
+            list_ptr(p) = null;
+            flush_node(p);
+            if (nest_ptr == 0) {
+                checked_page_filter(insert);
+                build_page();
+            }
+            break;
+        case output_group:
+            /* this is needed in case the \.{\\output} executes a \.{\\textdir} command. */
+            if (dir_level(text_dir_ptr) == cur_level) {
+                /* DIR: Remove from |text_dir_ptr| */
+                halfword text_dir_tmp = vlink(text_dir_ptr);
+                flush_node(text_dir_ptr);
+                text_dir_ptr = text_dir_tmp;
+            }
+            resume_after_output();
+            break;
+        case disc_group:
+            build_discretionary();
+            break;
+        case local_box_group:
+            build_local_box();
+            break;
+        case align_group:
+            back_input();
+            cur_tok = cs_token_flag + frozen_cr;
+            print_err("Missing \\cr inserted");
+            help1("I'm guessing that you meant to end an alignment here.");
+            ins_error();
+            break;
+        case no_align_group:
+            end_graf(no_align_group);
+            unsave();
+            align_peek();
+            break;
+        case vcenter_group:
+            end_graf(vcenter_group);
+            finish_vcenter();
+            break;
+        case math_choice_group:
+            build_choices();
+            break;
+        case math_group:
+            close_math_group(p);
+            break;
+        default:
+            confusion("rightbrace");
+            break;
+    }
+}
+
+@ @c
+void extra_right_brace(void)
+{
+    print_err("Extra }, or forgotten ");
+    switch (cur_group) {
+        case semi_simple_group:
+            tprint_esc("endgroup");
+            break;
+        case math_shift_group:
+            print_char('$');
+            break;
+        case math_left_group:
+            tprint_esc("right");
+            break;
+    }
+    help5("I've deleted a group-closing symbol because it seems to be",
+          "spurious, as in `$x}$'. But perhaps the } is legitimate and",
+          "you forgot something else, as in `\\hbox{$x}'. In such cases",
+          "the way to recover is to insert both the forgotten and the",
+          "deleted material, e.g., by typing `I$}'.");
+    error();
+    incr(align_state);
+}
+
+@ Here is where we clear the parameters that are supposed to revert to their
+default values after every paragraph and when internal vertical mode is entered.
+
+ at c
+void normal_paragraph(void)
+{
+    if (looseness_par != 0)
+        eq_word_define(int_base + looseness_code, 0);
+    if (hang_indent_par != 0)
+        eq_word_define(dimen_base + hang_indent_code, 0);
+    if (hang_after_par != 1)
+        eq_word_define(int_base + hang_after_code, 1);
+    if (par_shape_par_ptr != null)
+        eq_define(par_shape_loc, shape_ref_cmd, null);
+    if (inter_line_penalties_par_ptr != null)
+        eq_define(inter_line_penalties_loc, shape_ref_cmd, null);
+    if (shape_mode_par > 0)
+        eq_word_define(dimen_base + shape_mode_code, 0);
+}
+
+@ The global variable |cur_box| will point to a newly-made box. If the box
+is void, we will have |cur_box=null|. Otherwise we will have
+|type(cur_box)=hlist_node| or |vlist_node| or |rule_node|; the |rule_node|
+case can occur only with leaders.
+
+ at c
+halfword cur_box;               /* box to be placed into its context */
+
+@ The |box_end| procedure does the right thing with |cur_box|, if
+|box_context| represents the context as explained above.
+
+ at c
+void box_end(int box_context)
+{
+    if (box_context < box_flag) {
+        /* Append box |cur_box| to the current list, shifted by |box_context| */
+        /*
+           The global variable |adjust_tail| will be non-null if and only if the
+           current box might include adjustments that should be appended to the
+           current vertical list.
+         */
+        if (cur_box != null) {
+            shift_amount(cur_box) = box_context;
+            if (abs(mode) == vmode) {
+                if (pre_adjust_tail != null) {
+                    if (pre_adjust_head != pre_adjust_tail)
+                        append_list(pre_adjust_head, pre_adjust_tail);
+                    pre_adjust_tail = null;
+                }
+                append_to_vlist(cur_box,lua_key_index(box));
+                if (adjust_tail != null) {
+                    if (adjust_head != adjust_tail)
+                        append_list(adjust_head, adjust_tail);
+                    adjust_tail = null;
+                }
+                if (mode > 0) {
+                    checked_page_filter(box);
+                    build_page();
+                }
+            } else {
+                if (abs(mode) == hmode)
+                    space_factor_par = 1000;
+                else
+                    cur_box = new_sub_box(cur_box);
+                couple_nodes(tail, cur_box);
+                tail = cur_box;
+            }
+        }
+    } else if (box_context < ship_out_flag) {
+        /* Store |cur_box| in a box register */
+        if (box_context < global_box_flag)
+            eq_define(box_base + box_context - box_flag, box_ref_cmd, cur_box);
+        else
+            geq_define(box_base + box_context - global_box_flag, box_ref_cmd, cur_box);
+    } else if (cur_box != null) {
+        if (box_context > ship_out_flag) {
+            /* Append a new leader node that uses |cur_box| */
+            /* Get the next non-blank non-relax... */
+            do {
+                get_x_token();
+            } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));
+            if (((cur_cmd == hskip_cmd) && (abs(mode) != vmode)) ||
+                ((cur_cmd == vskip_cmd) && (abs(mode) == vmode))) {
+                append_glue();
+                subtype(tail) = (quarterword) (box_context - (leader_flag - a_leaders));
+                leader_ptr(tail) = cur_box;
+            } else {
+                print_err("Leaders not followed by proper glue");
+                help3
+                    ("You should say `\\leaders <box or rule><hskip or vskip>'.",
+                     "I found the <box or rule>, but there's no suitable",
+                     "<hskip or vskip>, so I'm ignoring these leaders.");
+                back_error();
+                flush_node_list(cur_box);
+            }
+        } else {
+            ship_out(static_pdf, cur_box, SHIPPING_PAGE);
+        }
+    }
+}
+
+@ the next input should specify a box or perhaps a rule
+
+ at c
+void scan_box(int box_context)
+{
+    /* Get the next non-blank non-relax... */
+    do {
+        get_x_token();
+    } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));
+    if (cur_cmd == make_box_cmd) {
+        begin_box(box_context);
+    } else if ((box_context >= leader_flag) &&
+            ((cur_cmd == hrule_cmd) || (cur_cmd == vrule_cmd) ||
+             (cur_cmd == no_hrule_cmd) || (cur_cmd == no_vrule_cmd))) {
+        cur_box = scan_rule_spec();
+        box_end(box_context);
+    } else {
+        print_err("A <box> was supposed to be here");
+        help3("I was expecting to see \\hbox or \\vbox or \\copy or \\box or",
+              "something like that. So you might find something missing in",
+              "your output. But keep trying; you can fix this later.");
+        back_error();
+    }
+}
+
+@ @c
+void new_graf(boolean indented)
+{
+    halfword p, q, dir_graf_tmp;
+    halfword dir_rover;
+    prev_graf_par = 0;
+    if ((mode == vmode) || (head != tail)) {
+        tail_append(new_param_glue(par_skip_code));
+    }
+    push_nest();
+    mode = hmode;
+    space_factor_par = 1000;
+    /* LOCAL: Add local paragraph node */
+    tail_append(make_local_par_node(new_graf_par_code));
+    if (indented) {
+        p = new_null_box();
+        box_dir(p) = par_direction_par;
+        width(p) = par_indent_par;
+        subtype(p) = indent_list;
+        q = tail;
+        tail_append(p);
+    } else {
+        q = tail;
+    }
+    dir_rover = text_dir_ptr;
+    while (dir_rover != null) {
+        if ((vlink(dir_rover) != null) || (dir_dir(dir_rover) != par_direction_par)) {
+            dir_graf_tmp = new_dir(dir_dir(dir_rover));
+            try_couple_nodes(dir_graf_tmp,vlink(q));
+            couple_nodes(q,dir_graf_tmp);
+        }
+        dir_rover = vlink(dir_rover);
+    }
+    q = head;
+    while (vlink(q) != null)
+        q = vlink(q);
+    tail = q;
+    if (every_par_par != null)
+        begin_token_list(every_par_par, every_par_text);
+    if (nest_ptr == 1) {
+        checked_page_filter(new_graf);
+        build_page();           /* put |par_skip| glue on current page */
+    }
+}
+
+@ @c
+void indent_in_hmode(void)
+{
+    halfword p;
+    if (cur_chr > 0) {          /* \.{\\indent} */
+        p = new_null_box();
+        width(p) = par_indent_par;
+        if (abs(mode) == hmode)
+            space_factor_par = 1000;
+        else
+            p = new_sub_box(p);
+        tail_append(p);
+    }
+}
+
+@ @c
+void head_for_vmode(void)
+{
+    if (mode < 0) {
+        if ((cur_cmd != hrule_cmd) && (cur_cmd != no_hrule_cmd)) {
+            off_save();
+        } else {
+            print_err("You can't use `\\hrule' here except with leaders");
+            help2("To put a horizontal rule in an hbox or an alignment,",
+                  "you should use \\leaders or \\hrulefill (see The TeXbook).");
+            error();
+        }
+    } else {
+        back_input();
+        cur_tok = par_token;
+        back_input();
+        token_type = inserted;
+    }
+}
+
+@ TODO (BUG?): |dir_save| would have been set by |line_break| by means
+of |post_line_break|, but this is not done right now, as it introduces
+pretty heavy memory leaks. This means the current code is probably
+wrong in some way that relates to in-paragraph displays.
+
+ at c
+void end_graf(int line_break_context)
+{
+    if (mode == hmode) {
+        if ((head == tail) || (vlink(head) == tail)) {
+            if (vlink(head) == tail)
+                flush_node(vlink(head));
+            pop_nest();         /* null paragraphs are ignored, all contain a |local_paragraph| node */
+        } else {
+            line_break(false, line_break_context);
+        }
+        if (dir_save != null) {
+            flush_node_list(dir_save);
+            dir_save = null;
+        }
+        normal_paragraph();
+        error_count = 0;
+    }
+}
+
+@ @c
+void begin_insert_or_adjust(void)
+{
+    if (cur_cmd != vadjust_cmd) {
+        scan_register_num();
+        if (cur_val == output_box_par) {
+            print_err("You can't \\insert");
+            print_int(output_box_par);
+            help1("I'm changing to \\insert0; box \\outputbox is special.");
+            error();
+            cur_val = 0;
+        }
+        set_saved_record(0, saved_insert, 0, cur_val);
+    } else if (scan_keyword("pre")) {
+        set_saved_record(0, saved_adjust, 0, 1);
+    } else {
+        set_saved_record(0, saved_adjust, 0, 0);
+    }
+    save_ptr++;
+    new_save_level(insert_group);
+    scan_left_brace();
+    normal_paragraph();
+    push_nest();
+    mode = -vmode;
+    prev_depth_par = ignore_depth;
+}
+
+@ I (TH)'ve renamed the |make_mark| procedure to this, because if the
+current chr code is 1, then the actual command was \.{\\clearmarks},
+which does not generate a mark node but instead destroys the current
+mark tokenlists.
+
+ at c
+void handle_mark(void)
+{
+    halfword p;                 /* new node */
+    halfword c;                 /* the mark class */
+    if (cur_chr == clear_marks_code) {
+        scan_mark_num();
+        c = cur_val;
+        delete_top_mark(c);
+        delete_bot_mark(c);
+        delete_first_mark(c);
+        delete_split_first_mark(c);
+        delete_split_bot_mark(c);
+    } else {
+        if (cur_chr == 0) {
+            c = 0;
+        } else {
+            scan_mark_num();
+            c = cur_val;
+            if (c > biggest_used_mark)
+                biggest_used_mark = c;
+        }
+        p = scan_toks(false, true);
+        p = new_node(mark_node, 0);     /* the |subtype| is not used */
+        mark_class(p) = c;
+        mark_ptr(p) = def_ref;
+        couple_nodes(tail, p);
+        tail = p;
+    }
+}
+
+@ @c
+void append_penalty(void)
+{
+    scan_int();
+    tail_append(new_penalty(cur_val,user_penalty));
+    if (mode == vmode) {
+        checked_page_filter(penalty);
+        build_page();
+    }
+}
+
+@ When |delete_last| is called, |cur_chr| is the |type| of node that
+will be deleted, if present.
+
+The |remove_item| command removes a penalty, kern, or glue node if it
+appears at the tail of the current list, using a brute-force linear scan.
+Like \.{\\lastbox}, this command is not allowed in vertical mode (except
+internal vertical mode), since the current list in vertical mode is sent
+to the page builder.  But if we happen to be able to implement it in
+vertical mode, we do.
+
+ at c
+void delete_last(void)
+{
+    halfword p, q;              /* run through the current list */
+    if ((mode == vmode) && (tail == head)) {
+        /* Apologize for inability to do the operation now,
+           unless \.{\\unskip} follows non-glue */
+        if ((cur_chr != glue_node) || (last_glue != max_halfword)) {
+            you_cant();
+            if (cur_chr == kern_node) {
+                help2
+                    ("Sorry...I usually can't take things from the current page.",
+                     "Try `I\\kern-\\lastkern' instead.");
+            } else if (cur_chr != glue_node) {
+                help2
+                    ("Sorry...I usually can't take things from the current page.",
+                     "Perhaps you can make the output routine do it.");
+            } else {
+                help2
+                    ("Sorry...I usually can't take things from the current page.",
+                     "Try `I\\vskip-\\lastskip' instead.");
+            }
+            error();
+        }
+    } else {
+        /* todo: clean this up */
+        if (!is_char_node(tail)) {
+            if (type(tail) == cur_chr) {
+                q = head;
+                do {
+                    p = q;
+                    if (!is_char_node(q)) {
+                        if (type(q) == disc_node) {
+                            if (p == tail)
+                                return;
+                        }
+                    }
+                    q = vlink(p);
+                } while (q != tail);
+                vlink(p) = null;
+                flush_node_list(tail);
+                tail = p;
+            }
+        }
+    }
+}
+
+@ @c
+void unpackage(void)
+{
+    halfword p;                 /* the box */
+    halfword r;                 /* to remove marginal kern nodes */
+    int c;                      /* should we copy? */
+    halfword s;                 /* for varmem assignment */
+    if (cur_chr > copy_code) {
+        /* Handle saved items and |goto done| */
+        try_couple_nodes(tail, disc_ptr[cur_chr]);
+        disc_ptr[cur_chr] = null;
+        goto DONE;
+    }
+    c = cur_chr;
+    scan_register_num();
+    p = box(cur_val);
+    if (p == null)
+        return;
+    if ((abs(mode) == mmode)
+        || ((abs(mode) == vmode) && (type(p) != vlist_node))
+        || ((abs(mode) == hmode) && (type(p) != hlist_node))) {
+        print_err("Incompatible list can't be unboxed");
+        help3("Sorry, Pandora. (You sneaky devil.)",
+              "I refuse to unbox an \\hbox in vertical mode or vice versa.",
+              "And I can't open any boxes in math mode.");
+        error();
+        return;
+    }
+    if (c == copy_code) {
+        s = copy_node_list(list_ptr(p));
+        try_couple_nodes(tail,s);
+    } else {
+        try_couple_nodes(tail,list_ptr(p));
+        box(cur_val) = null;
+        list_ptr(p) = null;
+        flush_node(p);
+    }
+  DONE:
+    while (vlink(tail) != null) {
+        r = vlink(tail);
+        if (!is_char_node(r) && (type(r) == margin_kern_node)) {
+            try_couple_nodes(tail,vlink(r));
+            flush_node(r);
+        }
+        tail = vlink(tail);
+    }
+}
+
+@
+Italic corrections are converted to kern nodes when the |ital_corr| command
+follows a character. In math mode the same effect is achieved by appending
+a kern of zero here, since italic corrections are supplied later.
+
+ at c
+void append_italic_correction(void)
+{
+    halfword p;                 /* |char_node| at the tail of the current list */
+    internal_font_number f;     /* the font in the |char_node| */
+    if (tail != head) {
+        if (is_char_node(tail))
+            p = tail;
+        else
+            return;
+        f = font(p);
+        tail_append(new_kern(char_italic(f, character(p))));
+        subtype(tail) = italic_kern;
+    }
+}
+
+@ @c
+void append_local_box(int kind)
+{
+    incr(save_ptr);
+    set_saved_record(-1, saved_boxtype, 0, kind);
+    new_save_level(local_box_group);
+    scan_left_brace();
+    push_nest();
+    mode = -hmode;
+    space_factor_par = 1000;
+}
+
+@ Discretionary nodes are easy in the common case `\.{\\-}', but in the
+general case we must process three braces full of items.
+
+The space factor does not change when we append a discretionary node,
+but it starts out as 1000 in the subsidiary lists.
+
+ at c
+void append_discretionary(void)
+{
+    int c;
+    tail_append(new_disc());
+    subtype(tail) = (quarterword) cur_chr;
+    if (cur_chr == explicit_disc) {
+        /* \- */
+        c = get_pre_hyphen_char(cur_lang_par);
+        if (c != 0) {
+            vlink(pre_break(tail)) = new_char(equiv(cur_font_loc), c);
+            alink(vlink(pre_break(tail))) = pre_break(tail);
+            tlink(pre_break(tail)) = vlink(pre_break(tail));
+        }
+        c = get_post_hyphen_char(cur_lang_par);
+        if (c != 0) {
+            vlink(post_break(tail)) = new_char(equiv(cur_font_loc), c);
+            alink(vlink(post_break(tail))) = post_break(tail);
+            tlink(post_break(tail)) = vlink(post_break(tail));
+        }
+        set_explicit_disc_penalty(tail);
+    } else {
+        /* \discretionary */
+        if (scan_keyword("penalty")) {
+            scan_int();
+            disc_penalty(tail) = cur_val;
+        }
+        incr(save_ptr);
+        set_saved_record(-1, saved_disc, 0, 0);
+        new_save_level(disc_group);
+        scan_left_brace();
+        push_nest();
+        mode = -hmode;
+        space_factor_par = 1000;
+        /* already preset: disc_penalty(tail) = hyphen_penalty_par; */
+    }
+}
+
+@ The test for |p != null| ensures that empty \.{\\localleftbox} and
+    \.{\\localrightbox} commands are not applied.
+
+ at c
+void build_local_box(void)
+{
+    halfword p;
+    int kind;
+    unsave();
+    assert(saved_type(-1) == saved_boxtype);
+    kind = saved_value(-1);
+    decr(save_ptr);
+    p = vlink(head);
+    pop_nest();
+    if (p != null)
+        p = hpack(p, 0, additional, -1);
+    if (kind == 0)
+        eq_define(local_left_box_base, box_ref_cmd, p);
+    else
+        eq_define(local_right_box_base, box_ref_cmd, p);
+    if (abs(mode) == hmode) {
+        /* LOCAL: Add local paragraph node */
+        tail_append(make_local_par_node(local_box_par_code));
+    }
+    eq_word_define(int_base + no_local_whatsits_code, no_local_whatsits_par + 1);
+}
+
+@ The three discretionary lists are constructed somewhat as if they were
+hboxes. A~subroutine called |build_discretionary| handles the transitions.
+(This is sort of fun.)
+
+ at c
+void build_discretionary(void)
+{
+    halfword p, q;              /* for link manipulation */
+    int n;                      /* length of discretionary list */
+    unsave();
+    /* Prune the current list, if necessary, until it contains only
+       |char_node|, |kern_node|, |hlist_node|, |vlist_node| and
+       |rule_node| items; set |n| to the length of the list,
+       and set |q| to the lists tail */
+    /* During this loop, |p=vlink(q)| and there are |n| items preceding |p|. */
+    q = head;
+    p = vlink(q);
+    n = 0;
+    while (p != null) {
+        if (!is_char_node(p) && type(p) > rule_node && type(p) != kern_node) {
+            print_err("Improper discretionary list");
+            help1("Discretionary lists must contain only boxes and kerns.");
+            error();
+            begin_diagnostic();
+            tprint_nl("The following discretionary sublist has been deleted:");
+            show_box(p);
+            end_diagnostic(true);
+            flush_node_list(p);
+            vlink(q) = null;
+            break;
+        }
+        alink(p) = q;
+        q = p;
+        p = vlink(q);
+        incr(n);
+    }
+
+    p = vlink(head);
+    pop_nest();
+    assert(saved_type(-1) == saved_disc);
+    switch (saved_value(-1)) {
+    case 0:
+        if (n > 0) {
+            vlink(pre_break(tail)) = p;
+            alink(p) = pre_break(tail);
+            tlink(pre_break(tail)) = q;
+        }
+        break;
+    case 1:
+        if (n > 0) {
+            vlink(post_break(tail)) = p;
+            alink(p) = post_break(tail);
+            tlink(post_break(tail)) = q;
+        }
+        break;
+    case 2:
+        /* Attach list |p| to the current list, and record its length;
+           then finish up and |return| */
+        if ((n > 0) && (abs(mode) == mmode)) {
+            print_err("Illegal math \\discretionary");
+            help2("Sorry: The third part of a discretionary break must be",
+                  "empty, in math formulas. I had to delete your third part.");
+            flush_node_list(p);
+            error();
+        } else {
+            if (n > 0) {
+                vlink(no_break(tail)) = p;
+                alink(p) = no_break(tail);
+                tlink(no_break(tail)) = q;
+            }
+        }
+        decr(save_ptr);
+        return;
+        break;
+    }                           /* there are no other cases */
+    set_saved_record(-1, saved_disc, 0, (saved_value(-1) + 1));
+    new_save_level(disc_group);
+    scan_left_brace();
+    push_nest();
+    mode = -hmode;
+    space_factor_par = 1000;
+}
+
+@ The positioning of accents is straightforward but tedious. Given an accent
+of width |a|, designed for characters of height |x| and slant |s|;
+and given a character of width |w|, height |h|, and slant |t|: We will shift
+the accent down by |x-h|, and we will insert kern nodes that have the effect of
+centering the accent over the character and shifting the accent to the
+right by $\delta={1\over2}(w-a)+h\cdot t-x\cdot s$.  If either character is
+absent from the font, we will simply use the other, without shifting.
+
+ at c
+void make_accent(void)
+{
+    double s, t;                /* amount of slant */
+    halfword p, q, r;           /* character, box, and kern nodes */
+    internal_font_number f;     /* relevant font */
+    scaled a, h, x, w, delta;   /* heights and widths, as explained above */
+    scan_char_num();
+    f = equiv(cur_font_loc);
+    p = new_glyph(f, cur_val);
+    if (p != null) {
+        x = x_height(f);
+        s = float_cast(slant(f)) / float_constant(65536);       /* real division */
+        a = glyph_width(p);
+        do_assignments();
+        /* Create a character node |q| for the next character,
+           but set |q:=null| if problems arise */
+        q = null;
+        f = equiv(cur_font_loc);
+        if ((cur_cmd == letter_cmd) ||
+            (cur_cmd == other_char_cmd) || (cur_cmd == char_given_cmd)) {
+            q = new_glyph(f, cur_chr);
+        } else if (cur_cmd == char_num_cmd) {
+            scan_char_num();
+            q = new_glyph(f, cur_val);
+        } else {
+            back_input();
+        }
+
+        if (q != null) {
+            /* Append the accent with appropriate kerns, then set |p:=q| */
+            /* The kern nodes appended here must be distinguished from other kerns, lest
+               they be wiped away by the hyphenation algorithm or by a previous line break.
+
+               The two kerns are computed with (machine-dependent) |real| arithmetic, but
+               their sum is machine-independent; the net effect is machine-independent,
+               because the user cannot remove these nodes nor access them via \.{\\lastkern}.
+             */
+            t = float_cast(slant(f)) / float_constant(65536);   /* real division */
+            w = glyph_width(q);
+            h = glyph_height(q);
+            if (h != x) {       /* the accent must be shifted up or down */
+                p = hpack(p, 0, additional, -1);
+                shift_amount(p) = x - h;
+            }
+            delta = round(float_cast(w - a) / float_constant(2) + h * t - x * s);       /* real multiplication */
+            r = new_kern(delta);
+            subtype(r) = accent_kern;
+            couple_nodes(tail, r);
+            couple_nodes(r, p);
+            tail = new_kern(-a - delta);
+            subtype(tail) = accent_kern;
+            couple_nodes(p, tail);
+            p = q;
+
+        }
+        couple_nodes(tail, p);
+        tail = p;
+        space_factor_par = 1000;
+    }
+}
+
+@ 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.
+
+ at c
+void align_error(void)
+{
+    if (abs(align_state) > 2) {
+        /* Express consternation over the fact that no alignment is in progress */
+        print_err("Misplaced ");
+        print_cmd_chr((quarterword) cur_cmd, cur_chr);
+        if (cur_tok == tab_token + '&') {
+            help6("I can't figure out why you would want to use a tab mark",
+                  "here. If you just want an ampersand, the remedy is",
+                  "simple: Just type `I\\&' now. But if some right brace",
+                  "up above has ended a previous alignment prematurely,",
+                  "you're probably due for more error messages, and you",
+                  "might try typing `S' now just to see what is salvageable.");
+        } else {
+            help5("I can't figure out why you would want to use a tab mark",
+                  "or \\cr or \\span just now. If something like a right brace",
+                  "up above has ended a previous alignment prematurely,",
+                  "you're probably due for more error messages, and you",
+                  "might try typing `S' now just to see what is salvageable.");
+        }
+        error();
+
+    } else {
+        back_input();
+        if (align_state < 0) {
+            print_err("Missing { inserted");
+            incr(align_state);
+            cur_tok = left_brace_token + '{';
+        } else {
+            print_err("Missing } inserted");
+            decr(align_state);
+            cur_tok = right_brace_token + '}';
+        }
+        help3("I've put in what seems to be necessary to fix",
+              "the current column of the current alignment.",
+              "Try to go on, since this might almost work.");
+        ins_error();
+    }
+}
+
+@ The help messages here contain a little white lie, since \.{\\noalign}
+and \.{\\omit} are allowed also after `\.{\\noalign\{...\}}'.
+
+ at c
+void no_align_error(void)
+{
+    print_err("Misplaced \\noalign");
+    help2("I expect to see \\noalign only after the \\cr of",
+          "an alignment. Proceed, and I'll ignore this case.");
+    error();
+}
+
+void omit_error(void)
+{
+    print_err("Misplaced \\omit");
+    help2("I expect to see \\omit only after tab marks or the \\cr of",
+          "an alignment. Proceed, and I'll ignore this case.");
+    error();
+}
+
+@ 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 |fin_align| removes it.
+
+A devious user might force an |endv| command to occur just about anywhere;
+we must defeat such hacks.
+
+ at c
+void do_endv(void)
+{
+    base_ptr = input_ptr;
+    input_stack[base_ptr] = cur_input;
+    while ((input_stack[base_ptr].index_field != v_template) &&
+           (input_stack[base_ptr].loc_field == null) &&
+           (input_stack[base_ptr].state_field == token_list))
+        decr(base_ptr);
+    if ((input_stack[base_ptr].index_field != v_template) ||
+        (input_stack[base_ptr].loc_field != null) ||
+        (input_stack[base_ptr].state_field != token_list))
+        fatal_error("(interwoven alignment preambles are not allowed)");
+    /*.interwoven alignment preambles... */
+    if (cur_group == align_group) {
+        end_graf(align_group);
+        if (fin_col())
+            fin_row();
+    } else {
+        off_save();
+    }
+}
+
+@ Finally, \.{\\endcsname} is not supposed to get through to |main_control|.
+
+ at c
+void cs_error(void)
+{
+    print_err("Extra \\endcsname");
+    help1("I'm ignoring this, since I wasn't doing a \\csname.");
+    error();
+}
+
+@
+  Assignments to values in |eqtb| can be global or local. Furthermore, a
+  control sequence can be defined to be `\.{\\long}', `\.{\\protected}',
+  or `\.{\\outer}', and it might or might not be expanded. The prefixes
+  `\.{\\global}', `\.{\\long}', `\.{\\protected}',
+  and `\.{\\outer}' can occur in any order. Therefore we assign binary numeric
+  codes, making it possible to accumulate the union of all specified prefixes
+  by adding the corresponding codes.  (PASCAL's |set| operations could also
+  have been used.)
+
+  Every prefix, and every command code that might or might not be prefixed,
+  calls the action procedure |prefixed_command|. This routine accumulates
+  a sequence of prefixes until coming to a non-prefix, then it carries out
+  the command.
+
+@ If the user says, e.g., `\.{\\global\\global}', the redundancy is
+silently accepted.
+
+
+@ The different types of code values have different legal ranges; the
+following program is careful to check each case properly.
+
+ at c
+#define check_def_code(A) do {						\
+	if (((cur_val<0)&&(p<(A)))||(cur_val>n)) {			\
+	    print_err("Invalid code (");				\
+	    print_int(cur_val);						\
+	    if (p<(A))							\
+		tprint("), should be in the range 0..");		\
+	    else							\
+		tprint("), should be at most ");			\
+	    print_int(n);						\
+	    help1("I'm going to use 0 instead of that illegal code value."); \
+	    error();							\
+	    cur_val=0;							\
+	}								\
+} while (0)
+
+@ @c
+/*
+halfword swap_hang_indent(halfword indentation, halfword shape_mode) {
+    if (shape_mode == 1 || shape_mode == 3 || shape_mode == -1 || shape_mode == -3) {
+        return negate(indentation);
+    } else {
+        return indentation;
+    }
+}
+
+halfword swap_parshape_indent(halfword indentation, halfword width, halfword shape_mode) {
+    if (shape_mode == 2 || shape_mode == 3 || shape_mode == -2 || shape_mode == -3) {
+        return hsize_par - width - indentation;
+    } else {
+        return indentation;
+    }
+}
+
+*/
+
+void prefixed_command(void)
+{
+    int a;                      /* accumulated prefix codes so far */
+    internal_font_number f;     /* identifies a font */
+    halfword j;                 /* index into a \.{\\parshape} specification */
+    halfword p, q;              /* for temporary short-term use */
+    int n;                      /* ditto */
+    boolean e, check_glue;      /* should a definition be expanded? or was \.{\\let} not done? */
+    mathcodeval mval;           /* for handling of \.{\\mathchardef}s */
+    a = 0;
+    while (cur_cmd == prefix_cmd) {
+        if (!odd(a / cur_chr))
+            a = a + cur_chr;
+        /* Get the next non-blank non-relax... */
+        do {
+            get_x_token();
+        } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));
+
+        if (cur_cmd <= max_non_prefixed_command) {
+            /* Discard erroneous prefixes and |return| */
+            print_err("You can't use a prefix with `");
+            print_cmd_chr((quarterword) cur_cmd, cur_chr);
+            print_char('\'');
+            help2
+                ("I'll pretend you didn't say \\long or \\outer or \\global or",
+                 "\\protected.");
+            back_error();
+            return;
+        }
+        if (tracing_commands_par > 2)
+            show_cur_cmd_chr();
+    }
+    /* Discard the prefixes \.{\\long} and \.{\\outer} if they are irrelevant */
+    if (a >= 8) {
+        j = protected_token;
+        a = a - 8;
+    } else {
+        j = 0;
+    }
+    if ((cur_cmd != def_cmd) && ((a % 4 != 0) || (j != 0))) {
+        print_err("You can't use `\\long' or `\\outer' or `\\protected' with `");
+        print_cmd_chr((quarterword) cur_cmd, cur_chr);
+        print_char('\'');
+        help1("I'll pretend you didn't say \\long or \\outer or \\protected here.");
+        error();
+    }
+    /* Adjust for the setting of \.{\\globaldefs} */
+    if (global_defs_par != 0) {
+        if (global_defs_par < 0) {
+            if (is_global(a))
+                a = a - 4;
+        } else {
+            if (!is_global(a))
+                a = a + 4;
+        }
+    }
+    switch (cur_cmd) {
+        case set_font_cmd:
+            /* Here's an example of the way many of the following routines operate.
+               (Unfortunately, they aren't all as simple as this.) */
+            define(cur_font_loc, data_cmd, cur_chr);
+            break;
+        case def_cmd:
+            /* When a |def| command has been scanned,
+               |cur_chr| is odd if the definition is supposed to be global, and
+               |cur_chr>=2| if the definition is supposed to be expanded. */
+
+            if (odd(cur_chr) && !is_global(a) && (global_defs_par >= 0))
+                a = a + 4;
+            e = (cur_chr >= 2);
+            get_r_token();
+            p = cur_cs;
+            q = scan_toks(true, e);
+            if (j != 0) {
+                q = get_avail();
+                set_token_info(q, j);
+                set_token_link(q, token_link(def_ref));
+                set_token_link(def_ref, q);
+            }
+            define(p, call_cmd + (a % 4), def_ref);
+            break;
+        case let_cmd:
+            n = cur_chr;
+            if (n == normal) {
+                get_r_token();
+                p = cur_cs;
+                do {
+                    get_token();
+                } while (cur_cmd == spacer_cmd);
+                if (cur_tok == other_token + '=') {
+                    get_token();
+                    if (cur_cmd == spacer_cmd)
+                        get_token();
+                }
+            } else if (n == normal + 1) {
+                /* futurelet */
+                get_r_token();
+                p = cur_cs;
+                get_token();
+                q = cur_tok;
+                get_token();
+                back_input();
+                cur_tok = q;
+                /* look ahead, then back up */
+                /* note that |back_input| doesn't affect |cur_cmd|, |cur_chr| */
+                back_input();
+            } else {
+                /* letcharcode */
+                scan_int();
+                if (cur_val > 0) {
+                    cur_cs = active_to_cs(cur_val, true);
+                    set_token_info(cur_cs, cur_cs + cs_token_flag);
+                    p = cur_cs;
+                    do {
+                        get_token();
+                    } while (cur_cmd == spacer_cmd);
+                    if (cur_tok == other_token + '=') {
+                        get_token();
+                        if (cur_cmd == spacer_cmd)
+                            get_token();
+                    }
+                } else {
+                    p = null;
+                    tex_error("invalid number for \\letcharcode",NULL);
+                }
+            }
+            if (cur_cmd >= call_cmd)
+                add_token_ref(cur_chr);
+            define(p, cur_cmd, cur_chr);
+            break;
+        case shorthand_def_cmd:
+            /* We temporarily define |p| to be |relax|, so that an occurrence of |p|
+               while scanning the definition will simply stop the scanning instead of
+               producing an ``undefined control sequence'' error or expanding the
+               previous meaning.  This allows, for instance, `\.{\\chardef\\foo=123\\foo}'.
+             */
+            n = cur_chr;
+            get_r_token();
+            p = cur_cs;
+            define(p, relax_cmd, too_big_char);
+            scan_optional_equals();
+            switch (n) {
+            case char_def_code:
+                scan_char_num();
+                define(p, char_given_cmd, cur_val);
+                break;
+            case math_char_def_code:
+                mval = scan_mathchar(tex_mathcode);
+                if (math_umathcode_meaning_par == 1) {
+                    cur_val = (mval.class_value + (8 * mval.family_value)) * (65536 * 32) + mval.character_value;
+                    define(p, xmath_given_cmd, cur_val);
+                } else {
+                    cur_val = (mval.class_value * 16 + mval.family_value) * 256 + mval.character_value;
+                    define(p, math_given_cmd, cur_val);
+                }
+                break;
+            case xmath_char_def_code:
+                mval = scan_mathchar(umath_mathcode);
+                cur_val = (mval.class_value + (8 * mval.family_value)) * (65536 * 32) + mval.character_value;
+                define(p, xmath_given_cmd, cur_val);
+                break;
+            case umath_char_def_code:
+                mval = scan_mathchar(umathnum_mathcode);
+                cur_val = (mval.class_value + (8 * mval.family_value)) * (65536 * 32) + mval.character_value;
+                define(p, xmath_given_cmd, cur_val);
+                break;
+            default:
+                scan_register_num();
+                switch (n) {
+                case count_def_code:
+                    define(p, assign_int_cmd, count_base + cur_val);
+                    break;
+                case attribute_def_code:
+                    define(p, assign_attr_cmd, attribute_base + cur_val);
+                    break;
+                case dimen_def_code:
+                    define(p, assign_dimen_cmd, scaled_base + cur_val);
+                    break;
+                case skip_def_code:
+                    define(p, assign_glue_cmd, skip_base + cur_val);
+                    break;
+                case mu_skip_def_code:
+                    define(p, assign_mu_glue_cmd, mu_skip_base + cur_val);
+                    break;
+                case toks_def_code:
+                    define(p, assign_toks_cmd, toks_base + cur_val);
+                    break;
+                default:
+                    confusion("shorthand_def");
+                    break;
+                }
+                break;
+            }
+            break;
+        case read_to_cs_cmd:
+            j = cur_chr;
+            scan_int();
+            n = cur_val;
+            if (!scan_keyword("to")) {
+                print_err("Missing `to' inserted");
+                help2("You should have said `\\read<number> to \\cs'.",
+                      "I'm going to look for the \\cs now.");
+                error();
+            }
+            get_r_token();
+            p = cur_cs;
+            read_toks(n, p, j);
+            define(p, call_cmd, cur_val);
+            break;
+        case toks_register_cmd:
+        case assign_toks_cmd:
+            /* The token-list parameters, \.{\\output} and \.{\\everypar}, etc., receive
+               their values in the following way. (For safety's sake, we place an
+               enclosing pair of braces around an \.{\\output} list.) */
+            q = cur_cs;
+            if (cur_cmd == toks_register_cmd) {
+                scan_register_num();
+                p = toks_base + cur_val;
+            } else {
+                p = cur_chr;        /* |p=every_par_loc| or |output_routine_loc| or \dots */
+            }
+            scan_optional_equals();
+            /* Get the next non-blank non-relax non-call token */
+            do {
+                get_x_token();
+            } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));
+
+            if (cur_cmd != left_brace_cmd) {
+                /* If the right-hand side is a token parameter
+                   or token register, finish the assignment and |goto done| */
+                if (cur_cmd == toks_register_cmd) {
+                    scan_register_num();
+                    cur_cmd = assign_toks_cmd;
+                    cur_chr = toks_base + cur_val;
+                }
+                if (cur_cmd == assign_toks_cmd) {
+                    q = equiv(cur_chr);
+                    if (q == null) {
+                        define(p, undefined_cs_cmd, null);
+                    } else {
+                        add_token_ref(q);
+                        define(p, call_cmd, q);
+                    }
+                    goto DONE;
+                }
+            }
+            back_input();
+            cur_cs = q;
+            q = scan_toks(false, false);
+            if (token_link(def_ref) == null) {      /* empty list: revert to the default */
+                define(p, undefined_cs_cmd, null);
+                free_avail(def_ref);
+            } else {
+                if (p == output_routine_loc) {      /* enclose in curlies */
+                    p = get_avail();
+                    set_token_link(q, p);
+                    p = output_routine_loc;
+                    q = token_link(q);
+                    set_token_info(q, right_brace_token + '}');
+                    q = get_avail();
+                    set_token_info(q, left_brace_token + '{');
+                    set_token_link(q, token_link(def_ref));
+                    set_token_link(def_ref, q);
+                }
+                define(p, call_cmd, def_ref);
+            }
+            break;
+        case assign_int_cmd:
+            /* Similar routines are used to assign values to the numeric parameters. */
+            p = cur_chr;
+            scan_optional_equals();
+            scan_int();
+            assign_internal_value(a, p, cur_val);
+            break;
+        case assign_attr_cmd:
+            p = cur_chr;
+            scan_optional_equals();
+            scan_int();
+            if ((p - attribute_base) > max_used_attr)
+                max_used_attr = (p - attribute_base);
+            attr_list_cache = cache_disabled;
+            word_define(p, cur_val);
+            break;
+        case assign_dir_cmd:
+            /* DIR: Assign direction codes */
+            scan_direction();
+            switch (cur_chr) {
+                case int_base + page_direction_code:
+                    eq_word_define(int_base + page_direction_code, cur_val);
+                    break;
+                case int_base + body_direction_code:
+                    eq_word_define(int_base + body_direction_code, cur_val);
+                    break;
+                case int_base + par_direction_code:
+                    eq_word_define(int_base + par_direction_code, cur_val);
+                    break;
+                case int_base + math_direction_code:
+                    eq_word_define(int_base + math_direction_code, cur_val);
+                    break;
+                case int_base + text_direction_code:
+                case int_base + line_direction_code:
+                    /*
+                        pre version 0.97 this was a commented section because various tests hint that this
+                        is unnecessary and sometimes even produces weird results, like:
+
+                            (\hbox{\textdir TRT ABC\textdir TLT DEF}))
+
+                        becomes
+
+                            (DEFCBA)
+
+                        in the output when we use
+
+                            tail_append(new_dir(text_direction_par)
+
+                        but when we append the reverse of the current it goes better
+
+                    */
+                    check_glue = (cur_chr == (int_base + line_direction_code));
+                    if (check_glue) {
+                        cur_chr = int_base + text_direction_code ;
+                    }
+                    if (abs(mode) == hmode) {
+                        if (no_local_dirs_par > 0) {
+                            /* tail is non zero but we test anyway */
+                            if (check_glue && (tail != null && type(tail) == glue_node))  {
+                                halfword prev = alink(tail);
+                                halfword dirn = new_dir(text_direction_par - dir_swap);
+                                couple_nodes(prev,dirn);
+                                couple_nodes(dirn,tail);
+                            } else {
+                                tail_append(new_dir(text_direction_par - dir_swap));
+                            }
+                        } else {
+                            /* what is the use of nolocaldirs .. maybe we should get rid of it */
+                        }
+                        update_text_dir_ptr(cur_val);
+                        tail_append(new_dir(cur_val));
+                        dir_level(tail) = cur_level;
+                    } else {
+                        update_text_dir_ptr(cur_val);
+                    }
+                    /*  original:
+
+                        // if ((no_local_dirs_par > 0) && (abs(mode) == hmode)) {
+                        //  // tail_append(new_dir(text_direction_par)              // kind of wrong
+                        //     tail_append(new_dir(text_direction_par - dir_swap)); // better
+                        // }
+
+                        update_text_dir_ptr(cur_val);
+                        if (abs(mode) == hmode) {
+                            tail_append(new_dir(cur_val));
+                            dir_level(tail) = cur_level;
+                        }
+                    */
+                    eq_word_define(int_base + text_direction_code, cur_val);
+                    eq_word_define(int_base + no_local_dirs_code, no_local_dirs_par + 1);
+                    break;
+                }
+            break;
+        case assign_dimen_cmd:
+            p = cur_chr;
+            scan_optional_equals();
+            scan_normal_dimen();
+            assign_internal_value(a, p, cur_val);
+            break;
+        case assign_glue_cmd:
+        case assign_mu_glue_cmd:
+            p = cur_chr;
+            n = cur_cmd;
+            scan_optional_equals();
+            if (n == assign_mu_glue_cmd)
+                scan_glue(mu_val_level);
+            else
+                scan_glue(glue_val_level);
+            define(p, glue_ref_cmd, cur_val);
+            break;
+        case def_char_code_cmd:
+        case def_del_code_cmd:
+            /* Let |n| be the largest legal code value, based on |cur_chr| */
+            if (cur_chr == cat_code_base)
+                n = max_char_code;
+            else if (cur_chr == sf_code_base)
+                n = 077777;
+            else
+                n = biggest_char;
+
+            p = cur_chr;
+            if (cur_chr == math_code_base) {
+                if (is_global(a))
+                    cur_val1 = level_one;
+                else
+                    cur_val1 = cur_level;
+                scan_extdef_math_code(cur_val1, tex_mathcode);
+            } else if (cur_chr == lc_code_base) {
+                scan_char_num();
+                p = cur_val;
+                scan_optional_equals();
+                scan_int();
+                check_def_code(lc_code_base);
+                define_lc_code(p, cur_val);
+            } else if (cur_chr == uc_code_base) {
+                scan_char_num();
+                p = cur_val;
+                scan_optional_equals();
+                scan_int();
+                check_def_code(uc_code_base);
+                define_uc_code(p, cur_val);
+            } else if (cur_chr == sf_code_base) {
+                scan_char_num();
+                p = cur_val;
+                scan_optional_equals();
+                scan_int();
+                check_def_code(sf_code_base);
+                define_sf_code(p, cur_val);
+            } else if (cur_chr == cat_code_base) {
+                scan_char_num();
+                p = cur_val;
+                scan_optional_equals();
+                scan_int();
+                check_def_code(cat_code_base);
+                define_cat_code(p, cur_val);
+            } else if (cur_chr == del_code_base) {
+                if (is_global(a))
+                    cur_val1 = level_one;
+                else
+                    cur_val1 = cur_level;
+                scan_extdef_del_code(cur_val1, tex_mathcode);
+            }
+            break;
+        case extdef_math_code_cmd:
+        case extdef_del_code_cmd:
+            if (is_global(a))
+                cur_val1 = level_one;
+            else
+                cur_val1 = cur_level;
+            if (cur_chr == math_code_base)
+                scan_extdef_math_code(cur_val1, umath_mathcode);
+            else if (cur_chr == math_code_base + 1)
+                scan_extdef_math_code(cur_val1, umathnum_mathcode);
+            else if (cur_chr == del_code_base)
+                scan_extdef_del_code(cur_val1, umath_mathcode);
+            else if (cur_chr == del_code_base + 1)
+                scan_extdef_del_code(cur_val1, umathnum_mathcode);
+            break;
+        case def_family_cmd:
+            p = cur_chr;
+            scan_math_family_int();
+            cur_val1 = cur_val;
+            scan_optional_equals();
+            scan_font_ident();
+            define_fam_fnt(cur_val1, p, cur_val);
+            break;
+        case set_math_param_cmd:
+            p = cur_chr;
+            get_token();
+            if (cur_cmd != math_style_cmd) {
+                print_err("Missing math style, treated as \\displaystyle");
+                help1
+                    ("A style should have been here; I inserted `\\displaystyle'.");
+                cur_val1 = display_style;
+                back_error();
+            } else {
+                cur_val1 = cur_chr;
+            }
+            scan_optional_equals();
+            if (p < math_param_first_mu_glue) {
+                if (p == math_param_radical_degree_raise)
+                    scan_int();
+                else
+                    scan_dimen(false, false, false);
+            } else {
+                scan_glue(mu_val_level);
+                if (cur_val == thin_mu_skip_par)
+                    cur_val = thin_mu_skip_code;
+                else if (cur_val == med_mu_skip_par)
+                    cur_val = med_mu_skip_code;
+                else if (cur_val == thick_mu_skip_par)
+                    cur_val = thick_mu_skip_code;
+            }
+            define_math_param(p, cur_val1, cur_val);
+            break;
+        case register_cmd:
+        case advance_cmd:
+        case multiply_cmd:
+        case divide_cmd:
+            do_register_command(a);
+            break;
+        case set_box_cmd:
+            /* The processing of boxes is somewhat different, because we may need
+               to scan and create an entire box before we actually change the value
+               of the old one. */
+            scan_register_num();
+            if (is_global(a))
+                n = global_box_flag + cur_val;
+            else
+                n = box_flag + cur_val;
+            scan_optional_equals();
+            if (set_box_allowed) {
+                scan_box(n);
+            } else {
+                print_err("Improper \\setbox");
+                help2("Sorry, \\setbox is not allowed after \\halign in a display,",
+                      "or between \\accent and an accented character.");
+                error();
+            }
+            break;
+        case set_aux_cmd:
+            /* The |space_factor| or |prev_depth| settings are changed when a |set_aux|
+               command is sensed. Similarly, |prev_graf| is changed in the presence of
+               |set_prev_graf|, and |dead_cycles| or |insert_penalties| in the presence of
+               |set_page_int|. These definitions are always global. */
+            alter_aux();
+            break;
+        case set_prev_graf_cmd:
+            alter_prev_graf();
+            break;
+        case set_page_dimen_cmd:
+            alter_page_so_far();
+            break;
+        case set_page_int_cmd:
+            alter_integer();
+            break;
+        case set_box_dimen_cmd:
+            /* When some dimension of a box register is changed, the change isn't exactly
+               global; but \TeX\ does not look at the \.{\\global} switch. */
+            alter_box_dimen();
+            break;
+        case set_tex_shape_cmd:
+            q = cur_chr;
+            scan_optional_equals();
+            scan_int();
+            n = cur_val;
+            if (n <= 0) {
+                p = null;
+            } else {
+                p = new_node(shape_node, 2 * (n + 1) + 1);
+                vinfo(p + 1) = n;
+                for (j = 1; j <= n; j++) {
+                    scan_normal_dimen();
+                    varmem[p + 2 * j].cint = cur_val;       /* indentation */
+                    scan_normal_dimen();
+                    varmem[p + 2 * j + 1].cint = cur_val;   /* width */
+                }
+            }
+            define(q, shape_ref_cmd, p);
+            break;
+        case set_etex_shape_cmd:
+            q = cur_chr;
+            scan_optional_equals();
+            scan_int();
+            n = cur_val;
+            if (n <= 0) {
+                p = null;
+            } else {
+                n = (cur_val / 2) + 1;
+                p = new_node(shape_node, 2 * n + 1 + 1);
+                vinfo(p + 1) = n;
+                n = cur_val;
+                varmem[p + 2].cint = n;     /* number of penalties */
+                for (j = p + 3; j <= p + n + 2; j++) {
+                    scan_int();
+                    varmem[j].cint = cur_val;       /* penalty values */
+                }
+                if (!odd(n))
+                    varmem[p + n + 3].cint = 0;     /* unused */
+            }
+            define(q, shape_ref_cmd, p);
+            break;
+        case hyph_data_cmd:
+            /* All of \TeX's parameters are kept in |eqtb| except the font information,
+               the interaction mode, and the hyphenation tables; these are strictly global.
+             */
+            switch (cur_chr) {
+                case 0:
+                    new_hyph_exceptions();
+                    break;
+                case 1:
+                    new_patterns();
+                    break;
+                case 2:
+                    new_pre_hyphen_char();
+                    break;
+                case 3:
+                    new_post_hyphen_char();
+                    break;
+                case 4:
+                    new_pre_exhyphen_char();
+                    break;
+                case 5:
+                    new_post_exhyphen_char();
+                    break;
+                case 6:
+                    new_hyphenation_min();
+                    break;
+                case 7:
+                    new_hj_code();
+                    break;
+            }
+            break;
+        case assign_font_dimen_cmd:
+            set_font_dimen();
+            break;
+        case assign_font_int_cmd:
+            n = cur_chr;
+            scan_font_ident();
+            f = cur_val;
+            if (n == no_lig_code) {
+                set_no_ligatures(f);
+            } else if (n < lp_code_base) {
+                scan_optional_equals();
+                scan_int();
+                if (n == 0)
+                    set_hyphen_char(f, cur_val);
+                else
+                    set_skew_char(f, cur_val);
+            } else {
+                scan_char_num();
+                p = cur_val;
+                scan_optional_equals();
+                scan_int();
+                switch (n) {
+                    case lp_code_base:
+                        set_lp_code(f, p, cur_val);
+                        break;
+                    case rp_code_base:
+                        set_rp_code(f, p, cur_val);
+                        break;
+                    case ef_code_base:
+                        set_ef_code(f, p, cur_val);
+                        break;
+                    case tag_code:
+                        set_tag_code(f, p, cur_val);
+                        break;
+                }
+            }
+            break;
+        case def_font_cmd:
+            /* Here is where the information for a new font gets loaded. */
+            tex_def_font((small_number) a);
+            break;
+        case letterspace_font_cmd:
+            new_letterspaced_font((small_number) a);
+            break;
+        case copy_font_cmd:
+            make_font_copy((small_number) a);
+            break;
+        case set_font_id_cmd:
+            scan_int();
+            if (is_valid_font(cur_val))
+                zset_cur_font(cur_val);
+            break ;
+        case set_interaction_cmd:
+            new_interaction();
+            break;
+        default:
+            confusion("prefix");
+            break;
+    }                           /* end of Assignments cases */
+  DONE:
+    /* Insert a token saved by \.{\\afterassignment}, if any */
+    if (after_token != 0) {
+        cur_tok = after_token;
+        back_input();
+        after_token = 0;
+    }
+}
+
+@ @c
+void fixup_directions(void)
+{
+    int temp_no_whatsits = no_local_whatsits_par;
+    int temp_no_dirs = no_local_dirs_par;
+    int temporary_dir = text_direction_par;
+    if (dir_level(text_dir_ptr) == cur_level) {
+        /* DIR: Remove from |text_dir_ptr| */
+        halfword text_dir_tmp = vlink(text_dir_ptr);
+        flush_node(text_dir_ptr);
+        text_dir_ptr = text_dir_tmp;
+    }
+    unsave();
+    if (abs(mode) == hmode) {
+        if (temp_no_dirs != 0) {
+            /* DIR: Add local dir node */
+            tail_append(new_dir(text_direction_par));
+            dir_dir(tail) = temporary_dir - dir_swap;
+        }
+        if (temp_no_whatsits != 0) {
+            /* LOCAL: Add local paragraph node */
+            tail_append(make_local_par_node(hmode_par_par_code));
+        }
+    }
+}
+
+@ When a control sequence is to be defined, by \.{\\def} or \.{\\let} or
+something similar, the |get_r_token| routine will substitute a special
+control sequence for a token that is not redefinable.
+
+ at c
+void get_r_token(void)
+{
+  RESTART:
+    do {
+        get_token();
+    } while (cur_tok == space_token);
+    if ((cur_cs == 0) || (cur_cs > eqtb_top) ||
+        ((cur_cs > frozen_control_sequence) && (cur_cs <= eqtb_size))) {
+        print_err("Missing control sequence inserted");
+        help5("Please don't say `\\def cs{...}', say `\\def\\cs{...}'.",
+              "I've inserted an inaccessible control sequence so that your",
+              "definition will be completed without mixing me up too badly.",
+              "You can recover graciously from this error, if you're",
+              "careful; see exercise 27.2 in The TeXbook.");
+        if (cur_cs == 0)
+            back_input();
+        cur_tok = cs_token_flag + frozen_protection;
+        ins_error();
+        goto RESTART;
+    }
+}
+
+@ @c
+void assign_internal_value(int a, halfword p, int val)
+{
+    halfword n;
+    if ((p >= int_base) && (p < attribute_base)) {
+        switch ((p - int_base)) {
+        case cat_code_table_code:
+            if (valid_catcode_table(val)) {
+                if (val != cat_code_table_par)
+                    word_define(p, val);
+            } else {
+                print_err("Invalid \\catcode table");
+                help2
+                    ("You can only switch to a \\catcode table that is initialized",
+                     "using \\savecatcodetable or \\initcatcodetable, or to table 0");
+                error();
+            }
+            break;
+        case output_box_code:
+            if ((val > 65535) | (val < 0)) {
+                print_err("Invalid \\outputbox");
+                help1
+                    ("The value for \\outputbox has to be between 0 and 65535.");
+                error();
+            } else {
+                word_define(p, val);
+            }
+            break;
+        case new_line_char_code:
+            if (val > 127) {
+                print_err("Invalid \\newlinechar");
+                help2
+                    ("The value for \\newlinechar has to be no higher than 127.",
+                     "Your invalid assignment will be ignored.");
+                error();
+            } else {
+                word_define(p, val);
+            }
+            break;
+        case end_line_char_code:
+            if (val > 127) {
+                print_err("Invalid \\endlinechar");
+                help2
+                    ("The value for \\endlinechar has to be no higher than 127.",
+                     "Your invalid assignment will be ignored.");
+                error();
+            } else {
+                word_define(p, val);
+            }
+            break;
+        case language_code:
+            if (val < 0) {
+                word_define(int_base + cur_lang_code, -1);
+                word_define(p, -1);
+            } else if (val > 16383) {
+                print_err("Invalid \\language");
+                help2
+                    ("The absolute value for \\language has to be no higher than 16383.",
+                     "Your invalid assignment will be ignored.");
+                error();
+            } else {
+                word_define(int_base + cur_lang_code, val);
+                word_define(p, val);
+            }
+            break;
+        default:
+            word_define(p, val);
+            break;
+        }
+        /* If we are defining subparagraph penalty levels while we are
+           in hmode, then we put out a whatsit immediately, otherwise
+           we leave it alone.  This mechanism might not be sufficiently
+           powerful, and some other algorithm, searching down the stack,
+           might be necessary.  Good first step. */
+        if ((abs(mode) == hmode) &&
+            ((p == (int_base + local_inter_line_penalty_code)) ||
+             (p == (int_base + local_broken_penalty_code)))) {
+            /* LOCAL: Add local paragraph node */
+            tail_append(make_local_par_node(penalty_par_code));
+            eq_word_define(int_base + no_local_whatsits_code, no_local_whatsits_par + 1);
+        }
+    } else if ((p >= dimen_base) && (p <= eqtb_size)) {
+        if (p == (dimen_base + page_left_offset_code)) {
+            n = val - one_true_inch;
+            word_define(dimen_base + h_offset_code, n);
+        } else if (p == (dimen_base + h_offset_code)) {
+            n = val + one_true_inch;
+            word_define(dimen_base + page_left_offset_code, n);
+        } else if (p == (dimen_base + page_top_offset_code)) {
+            n = val - one_true_inch;
+            word_define(dimen_base + v_offset_code, n);
+        } else if (p == (dimen_base + v_offset_code)) {
+            n = val + one_true_inch;
+            word_define(dimen_base + page_top_offset_code, n);
+        }
+        word_define(p, val);
+    } else if ((p >= local_base) && (p < toks_base)) {  /* internal locals  */
+        define(p, call_cmd, val);
+    } else {
+        confusion("assign internal value");
+    }
+}
+
+@ We use the fact that |register<advance<multiply<divide|
+
+Compute the register location |l| and its type |p|; but |return| if invalid
+Here we use the fact that the consecutive codes |int_val..mu_val| and
+|assign_int..assign_mu_glue| correspond to each other nicely.
+
+ at c
+void do_register_command(int a)
+{
+    int p;
+    halfword q = cur_cmd;
+    halfword l = 0;
+    if (q != register_cmd) {
+        get_x_token();
+        if ((cur_cmd >= assign_int_cmd) && (cur_cmd <= assign_mu_glue_cmd)) {
+            l = cur_chr;
+            p = cur_cmd - assign_int_cmd;
+            goto FOUND;
+        }
+        if (cur_cmd != register_cmd) {
+            print_err("You can't use `");
+            print_cmd_chr((quarterword) cur_cmd, cur_chr);
+            tprint("' after ");
+            print_cmd_chr((quarterword) q, 0);
+            help1("I'm forgetting what you said and not changing anything.");
+            error();
+            return;
+        }
+    }
+    p = cur_chr;
+    scan_register_num();
+    if (p == int_val_level)
+        l = cur_val + count_base;
+    else if (p == attr_val_level)
+        l = cur_val + attribute_base;
+    else if (p == dimen_val_level)
+        l = cur_val + scaled_base;
+    else if (p == glue_val_level)
+        l = cur_val + skip_base;
+    else if (p == mu_val_level)
+        l = cur_val + mu_skip_base;
+  FOUND:
+    if (q == register_cmd) {
+        scan_optional_equals();
+    } else if (scan_keyword("by")) {
+        /* optional `\.{by}' */
+    }
+    arith_error = false;
+    if (q < multiply_cmd) {
+        /* Compute result of |register| or |advance|, put it in |cur_val| */
+        if (p < glue_val_level) {
+            if ((p == int_val_level) || (p == attr_val_level))
+                scan_int();
+            else
+                scan_normal_dimen();
+            if (q == advance_cmd)
+                cur_val = cur_val + eqtb[l].cint;
+        } else {
+            /* we can probably save a copy */
+            scan_glue(p);
+            if (q == advance_cmd) {
+                /* Compute the sum of two glue specs */
+                halfword r = equiv(l);
+                q = new_spec(cur_val);
+                flush_node(cur_val);
+                width(q) = width(q) + width(r);
+                if (stretch(q) == 0) {
+                    stretch_order(q) = normal;
+                }
+                if (stretch_order(q) == stretch_order(r)) {
+                    stretch(q) = stretch(q) + stretch(r);
+                } else if ((stretch_order(q) < stretch_order(r)) && (stretch(r) != 0)) {
+                    stretch(q) = stretch(r);
+                    stretch_order(q) = stretch_order(r);
+                }
+                if (shrink(q) == 0) {
+                    shrink_order(q) = normal;
+                }
+                if (shrink_order(q) == shrink_order(r)) {
+                    shrink(q) = shrink(q) + shrink(r);
+                } else if ((shrink_order(q) < shrink_order(r)) && (shrink(r) != 0)) {
+                    shrink(q) = shrink(r);
+                    shrink_order(q) = shrink_order(r);
+                }
+                cur_val = q;
+            }
+        }
+    } else {
+        /* Compute result of |multiply| or |divide|, put it in |cur_val| */
+        scan_int();
+        if (p < glue_val_level) {
+            if (q == multiply_cmd) {
+                if ((p == int_val_level) || (p == attr_val_level)) {
+                    cur_val = mult_integers(eqtb[l].cint, cur_val);
+                } else {
+                    cur_val = nx_plus_y(eqtb[l].cint, cur_val, 0);
+                }
+            } else {
+                cur_val = x_over_n(eqtb[l].cint, cur_val);
+            }
+        } else {
+            halfword s = equiv(l);
+            halfword r = new_spec(s);
+            if (q == multiply_cmd) {
+                width(r) = nx_plus_y(width(s), cur_val, 0);
+                stretch(r) = nx_plus_y(stretch(s), cur_val, 0);
+                shrink(r) = nx_plus_y(shrink(s), cur_val, 0);
+            } else {
+                width(r) = x_over_n(width(s), cur_val);
+                stretch(r) = x_over_n(stretch(s), cur_val);
+                shrink(r) = x_over_n(shrink(s), cur_val);
+            }
+            cur_val = r;
+        }
+    }
+    if (arith_error) {
+        print_err("Arithmetic overflow");
+        help2("I can't carry out that multiplication or division,",
+              "since the result is out of range.");
+        if (p >= glue_val_level)
+            flush_node(cur_val);
+        error();
+        return;
+    }
+    if (p < glue_val_level) {
+        if (p == attr_val_level) {
+            if ((l - attribute_base) > max_used_attr)
+                max_used_attr = (l - attribute_base);
+            attr_list_cache = cache_disabled;
+        }
+        if ((p == int_val_level) || (p == dimen_val_level))
+            assign_internal_value(a, l, cur_val);
+        else
+            word_define(l, cur_val);
+    } else {
+        define(l, glue_ref_cmd, cur_val);
+    }
+}
+
+@ @c
+void alter_aux(void)
+{
+    halfword c;                 /* |hmode| or |vmode| */
+    if (cur_chr != abs(mode)) {
+        report_illegal_case();
+    } else {
+        c = cur_chr;
+        scan_optional_equals();
+        if (c == vmode) {
+            scan_normal_dimen();
+            prev_depth_par = cur_val;
+        } else {
+            scan_int();
+            if ((cur_val <= 0) || (cur_val > 32767)) {
+                print_err("Bad space factor");
+                help1("I allow only values in the range 1..32767 here.");
+                int_error(cur_val);
+            } else {
+                space_factor_par = cur_val;
+            }
+        }
+    }
+}
+
+@ @c
+void alter_prev_graf(void)
+{
+    int p;                      /* index into |nest| */
+    p = nest_ptr;
+    while (abs(nest[p].mode_field) != vmode)
+        decr(p);
+    scan_optional_equals();
+    scan_int();
+    if (cur_val < 0) {
+        print_err("Bad \\prevgraf");
+        help1("I allow only nonnegative values here.");
+        int_error(cur_val);
+    } else {
+        nest[p].pg_field = cur_val;
+    }
+}
+
+@ @c
+void alter_page_so_far(void)
+{
+    int c;                      /* index into |page_so_far| */
+    c = cur_chr;
+    scan_optional_equals();
+    scan_normal_dimen();
+    page_so_far[c] = cur_val;
+}
+
+@ @c
+void alter_integer(void)
+{
+    int c;                      /* 0 for \.{\\deadcycles}, 1 for \.{\\insertpenalties}, etc. */
+    c = cur_chr;
+    scan_optional_equals();
+    scan_int();
+    if (c == 0) {
+        dead_cycles = cur_val;
+    } else if (c == 2) {
+        if ((cur_val < batch_mode) || (cur_val > error_stop_mode)) {
+            print_err("Bad interaction mode");
+            help2("Modes are 0=batch, 1=nonstop, 2=scroll, and",
+                  "3=errorstop. Proceed, and I'll ignore this case.");
+            int_error(cur_val);
+        } else {
+            cur_chr = cur_val;
+            new_interaction();
+        }
+    } else {
+        insert_penalties = cur_val;
+    }
+}
+
+@ @c
+void alter_box_dimen(void)
+{
+    int c;                      /* |width_offset| or |height_offset| or |depth_offset| */
+    int b;                      /* box number */
+    c = cur_chr;
+    scan_register_num();
+    b = cur_val;
+    scan_optional_equals();
+    scan_normal_dimen();
+    if (box(b) != null)
+        varmem[box(b) + c].cint = cur_val;
+}
+
+@ @c
+void new_interaction(void)
+{
+    print_ln();
+    interaction = cur_chr;
+    if (interaction == batch_mode)
+        kpse_make_tex_discard_errors = 1;
+    else
+        kpse_make_tex_discard_errors = 0;
+    fixup_selector(log_opened_global);
+}
+
+@ The \.{\\afterassignment} command puts a token into the global
+variable |after_token|. This global variable is examined just after
+every assignment has been performed.
+
+ at c
+halfword after_token;           /* zero, or a saved token */
+
+@ Here is a procedure that might be called `Get the next non-blank non-relax
+non-call non-assignment token'.
+
+ at c
+void do_assignments(void)
+{
+    while (true) {
+        /* Get the next non-blank non-relax... */
+        do {
+            get_x_token();
+        } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));
+        if (cur_cmd <= max_non_prefixed_command)
+            return;
+        set_box_allowed = false;
+        prefixed_command();
+        set_box_allowed = true;
+    }
+}
+
+@ @c
+void open_or_close_in(void)
+{
+    int c;                      /* 1 for \.{\\openin}, 0 for \.{\\closein} */
+    int n;                      /* stream number */
+    char *fn;
+    c = cur_chr;
+    scan_four_bit_int();
+    n = cur_val;
+    if (read_open[n] != closed) {
+        lua_a_close_in(read_file[n], (n + 1));
+        read_open[n] = closed;
+    }
+    if (c != 0) {
+        scan_optional_equals();
+        do {
+            get_x_token();
+        } while ((cur_cmd == spacer_cmd) || (cur_cmd == relax_cmd));
+        back_input();
+        if (cur_cmd != left_brace_cmd) {
+            scan_file_name();   /* set |cur_name| to desired file name */
+            if (cur_ext == get_nullstr())
+                cur_ext = maketexstring(".tex");
+        } else {
+            scan_file_name_toks();
+        }
+        fn = pack_file_name(cur_name, cur_area, cur_ext);
+        if (lua_a_open_in(&(read_file[n]), fn, (n + 1))) {
+            read_open[n] = just_open;
+        }
+    }
+}
+
+@ @c
+boolean long_help_seen;         /* has the long \.{\\errmessage} help been used? */
+
+void issue_message(void)
+{
+    int old_setting;            /* holds |selector| setting */
+    int c;                      /* identifies \.{\\message} and \.{\\errmessage} */
+    str_number s;               /* the message */
+    c = cur_chr;
+    (void) scan_toks(false, true);
+    old_setting = selector;
+    selector = new_string;
+    token_show(def_ref);
+    selector = old_setting;
+    flush_list(def_ref);
+    str_room(1);
+    s = make_string();
+    if (c == 0) {
+        /* Print string |s| on the terminal */
+        if (term_offset + (int) str_length(s) > max_print_line - 2)
+            print_ln();
+        else if ((term_offset > 0) || (file_offset > 0))
+            print_char(' ');
+        print(s);
+        update_terminal();
+
+    } else {
+        /* Print string |s| as an error message */
+        /* If \.{\\errmessage} occurs often in |scroll_mode|, without user-defined
+           \.{\\errhelp}, we don't want to give a long help message each time. So we
+           give a verbose explanation only once. */
+        print_err("");
+        print(s);
+        if (err_help_par != null) {
+            use_err_help = true;
+        } else if (long_help_seen) {
+            help1("(That was another \\errmessage.)");
+        } else {
+            if (interaction < error_stop_mode)
+                long_help_seen = true;
+            help4("This error message was generated by an \\errmessage",
+                  "command, so I can't give any explicit help.",
+                  "Pretend that you're Hercule Poirot: Examine all clues,",
+                  "and deduce the truth by order and method.");
+        }
+        error();
+        use_err_help = false;
+
+    }
+    flush_str(s);
+}
+
+@ The |error| routine calls on |give_err_help| if help is requested from
+the |err_help| parameter.
+
+ at c
+void give_err_help(void)
+{
+    token_show(err_help_par);
+}
+
+@ The \.{\\uppercase} and \.{\\lowercase} commands are implemented by
+building a token list and then changing the cases of the letters in it.
+
+ at c
+void shift_case(void)
+{
+    halfword b;                 /* |lc_code_base| or |uc_code_base| */
+    halfword p;                 /* runs through the token list */
+    halfword t;                 /* token */
+    halfword c;                 /* character code */
+    halfword i;                 /* inbetween */
+    b = cur_chr;
+    p = scan_toks(false, false);
+    p = token_link(def_ref);
+    while (p != null) {
+        /* Change the case of the token in |p|, if a change is appropriate */
+        /*
+           When the case of a |chr_code| changes, we don't change the |cmd|.
+           We also change active characters.
+         */
+        t = token_info(p);
+        if (t < cs_token_flag) {
+            c = t % STRING_OFFSET;
+            if (b == uc_code_base)
+                i = get_uc_code(c);
+            else
+                i = get_lc_code(c);
+            if (i != 0)
+                set_token_info(p, t - c + i);
+        } else if (is_active_cs(cs_text(t - cs_token_flag))) {
+            c = active_cs_value(cs_text(t - cs_token_flag));
+            if (b == uc_code_base)
+                i = get_uc_code(c);
+            else
+                i = get_lc_code(c);
+            if (i != 0)
+                set_token_info(p, active_to_cs(i, true) + cs_token_flag);
+        }
+        p = token_link(p);
+    }
+    back_list(token_link(def_ref));
+    free_avail(def_ref);        /* omit reference count */
+}
+
+@ We come finally to the last pieces missing from |main_control|, namely the
+`\.{\\show}' commands that are useful when debugging.
+
+ at c
+void show_whatever(void)
+{
+    halfword p;                 /* tail of a token list to show */
+    int t;                      /* type of conditional being shown */
+    int m;                      /* upper bound on |fi_or_else| codes */
+    int l;                      /* line where that conditional began */
+    int n;                      /* level of \.{\\if...\\fi} nesting */
+    switch (cur_chr) {
+    case show_lists:
+        begin_diagnostic();
+        show_activities();
+        break;
+    case show_box_code:
+        /* Show the current contents of a box */
+        scan_register_num();
+        begin_diagnostic();
+        tprint_nl("> \\box");
+        print_int(cur_val);
+        print_char('=');
+        if (box(cur_val) == null)
+            tprint("void");
+        else
+            show_box(box(cur_val));
+        break;
+    case show_code:
+        /* Show the current meaning of a token, then |goto common_ending| */
+        get_token();
+        if (interaction == error_stop_mode)
+            wake_up_terminal();
+        tprint_nl("> ");
+        if (cur_cs != 0) {
+            sprint_cs(cur_cs);
+            print_char('=');
+        }
+        print_meaning();
+        goto COMMON_ENDING;
+        break;
+        /* Cases for |show_whatever| */
+    case show_groups:
+        begin_diagnostic();
+        show_save_groups();
+        break;
+    case show_ifs:
+        begin_diagnostic();
+        tprint_nl("");
+        print_ln();
+        if (cond_ptr == null) {
+            tprint_nl("### ");
+            tprint("no active conditionals");
+        } else {
+            p = cond_ptr;
+            n = 0;
+            do {
+                incr(n);
+                p = vlink(p);
+            } while (p != null);
+            p = cond_ptr;
+            t = cur_if;
+            l = if_line;
+            m = if_limit;
+            do {
+                tprint_nl("### level ");
+                print_int(n);
+                tprint(": ");
+                print_cmd_chr(if_test_cmd, t);
+                if (m == fi_code)
+                    tprint_esc("else");
+                print_if_line(l);
+                decr(n);
+                t = if_limit_subtype(p);
+                l = if_line_field(p);
+                m = if_limit_type(p);
+                p = vlink(p);
+            } while (p != null);
+        }
+        break;
+    default:
+        /* Show the current value of some parameter or register,
+           then |goto common_ending| */
+        p = the_toks();
+        if (interaction == error_stop_mode)
+            wake_up_terminal();
+        tprint_nl("> ");
+        token_show(temp_token_head);
+        flush_list(token_link(temp_token_head));
+        goto COMMON_ENDING;
+        break;
+    }
+    /* Complete a potentially long \.{\\show} command */
+    end_diagnostic(true);
+    print_err("OK");
+    if (selector == term_and_log) {
+        if (tracing_online_par <= 0) {
+            selector = term_only;
+            tprint(" (see the transcript file)");
+            selector = term_and_log;
+        }
+    }
+  COMMON_ENDING:
+    if (interaction < error_stop_mode) {
+        help0();
+        decr(error_count);
+    } else if (tracing_online_par > 0) {
+        help3("This isn't an error message; I'm just \\showing something.",
+              "Type `I\\show...' to show more (e.g., \\show\\cs,",
+              "\\showthe\\count10, \\showbox255, \\showlists).");
+    } else {
+        help5("This isn't an error message; I'm just \\showing something.",
+              "Type `I\\show...' to show more (e.g., \\show\\cs,",
+              "\\showthe\\count10, \\showbox255, \\showlists).",
+              "And type `I\\tracingonline=1\\show...' to show boxes and",
+              "lists on your terminal as well as in the transcript file.");
+    }
+    error();
+}
+
+@ @c
+void initialize(void)
+{                               /* this procedure gets things started properly */
+    int k;                      /* index into |mem|, |eqtb|, etc. */
+    /* Initialize whatever \TeX\ might access */
+    /* Set initial values of key variables */
+    initialize_errors();
+    initialize_arithmetic();
+    max_used_attr = -1;
+    attr_list_cache = cache_disabled;
+    initialize_nesting();
+
+    /* Start a new current page */
+    page_contents = empty;
+    page_tail = page_head;
+#if 0
+    vlink(page_head) = null;
+#endif
+    last_glue = max_halfword;
+    last_penalty = 0;
+    last_kern = 0;
+    last_node_type = -1;
+    page_depth = 0;
+    page_max_depth = 0;
+
+    initialize_equivalents();
+    no_new_control_sequence = true;     /* new identifiers are usually forbidden */
+    init_primitives();
+
+    mag_set = 0;
+    initialize_marks();
+    initialize_read();
+
+    static_pdf = init_pdf_struct(static_pdf); /* should be init_backend() */
+
+    format_ident = 0;
+    format_name = get_nullstr();
+    initialize_directions();
+    initialize_write_files();
+    seconds_and_micros(epochseconds, microseconds);
+    initialize_start_time(static_pdf);
+
+    edit_name_start = 0;
+    stop_at_space = true;
+
+    if (ini_version) {
+        /* Initialize table entries (done by \.{INITEX} only) */
+
+        init_node_mem(500);
+        initialize_tokens();
+        /* Initialize the special list heads and constant nodes */
+        initialize_alignments();
+        initialize_buildpage();
+
+        initialize_active();
+
+        set_eq_type(undefined_control_sequence, undefined_cs_cmd);
+        set_equiv(undefined_control_sequence, null);
+        set_eq_level(undefined_control_sequence, level_zero);
+        for (k = null_cs; k <= (eqtb_top - 1); k++)
+            eqtb[k] = eqtb[undefined_control_sequence];
+        set_equiv(glue_base, zero_glue);
+        set_eq_level(glue_base, level_one);
+        set_eq_type(glue_base, glue_ref_cmd);
+        for (k = glue_base + 1; k <= local_base - 1; k++) {
+            eqtb[k] = eqtb[glue_base];
+        }
+        par_shape_par_ptr = null;
+        set_eq_type(par_shape_loc, shape_ref_cmd);
+        set_eq_level(par_shape_loc, level_one);
+        for (k = etex_pen_base; k <= (etex_pens - 1); k++)
+            eqtb[k] = eqtb[par_shape_loc];
+        for (k = output_routine_loc; k <= toks_base + biggest_reg; k++)
+            eqtb[k] = eqtb[undefined_control_sequence];
+        box(0) = null;
+        set_eq_type(box_base, box_ref_cmd);
+        set_eq_level(box_base, level_one);
+        for (k = box_base + 1; k <= (box_base + biggest_reg); k++)
+            eqtb[k] = eqtb[box_base];
+        cur_font_par = null_font;
+        set_eq_type(cur_font_loc, data_cmd);
+        set_eq_level(cur_font_loc, level_one);
+        set_equiv(cat_code_base, 0);
+        set_eq_type(cat_code_base, data_cmd);
+        set_eq_level(cat_code_base, level_one);
+        eqtb[internal_math_param_base] = eqtb[cat_code_base];
+        eqtb[lc_code_base] = eqtb[cat_code_base];
+        eqtb[uc_code_base] = eqtb[cat_code_base];
+        eqtb[sf_code_base] = eqtb[cat_code_base];
+        eqtb[math_code_base] = eqtb[cat_code_base];
+        cat_code_table_par = 0;
+        initialize_math_codes();
+        initialize_text_codes();
+        initex_cat_codes(0);
+        for (k = '0'; k <= '9'; k++)
+            set_math_code(k, math_use_current_family_code, 0, k, level_one);
+        for (k = 'A'; k <= 'Z'; k++) {
+            set_math_code(k, math_use_current_family_code, 1, k, level_one);
+            set_math_code((k + 32), math_use_current_family_code, 1, (k + 32), level_one);
+            set_lc_code(k, k + 32, level_one);
+            set_lc_code(k + 32, k + 32, level_one);
+            set_uc_code(k, k, level_one);
+            set_uc_code(k + 32, k, level_one);
+            set_sf_code(k, 999, level_one);
+        }
+        for (k = int_base; k <= attribute_base - 1; k++)
+            eqtb[k].cint = 0;
+        for (k = attribute_base; k <= del_code_base - 1; k++)
+            eqtb[k].cint = UNUSED_ATTRIBUTE;
+        mag_par = 1000;
+        tolerance_par = 10000;
+        hang_after_par = 1;
+        max_dead_cycles_par = 25;
+        math_pre_display_gap_factor_par = 2000;
+        escape_char_par = '\\';
+        end_line_char_par = carriage_return;
+        set_del_code('.', 0, 0, 0, 0, level_one); /* this null delimiter is used in error recovery */
+        ex_hyphen_char_par = '-';
+        output_box_par = 255;
+        for (k = dimen_base; k <= eqtb_size; k++)
+            eqtb[k].cint = 0;
+        page_left_offset_par = one_inch;
+        page_top_offset_par = one_inch;
+        page_right_offset_par = one_inch;
+        page_bottom_offset_par = one_inch;
+        ini_init_primitives();
+        hash_used = frozen_control_sequence;    /* nothing is used */
+        hash_high = 0;
+        cs_count = 0;
+        set_eq_type(frozen_dont_expand, dont_expand_cmd);
+        cs_text(frozen_dont_expand) = maketexstring("notexpanded:");
+        set_eq_type(frozen_primitive, ignore_spaces_cmd);
+        set_equiv(frozen_primitive, 1);
+        set_eq_level(frozen_primitive, level_one);
+        cs_text(frozen_primitive) = maketexstring("primitive");
+        create_null_font();
+        font_bytes = 0;
+        px_dimen_par = one_bp;
+        math_eqno_gap_step_par = 1000 ;
+        cs_text(frozen_protection) = maketexstring("inaccessible");
+        format_ident = maketexstring(" (INITEX)");
+        cs_text(end_write) = maketexstring("endwrite");
+        set_eq_level(end_write, level_one);
+        set_eq_type(end_write, outer_call_cmd);
+        set_equiv(end_write, null);
+
+    }
+    synctexoffset = int_base + synctex_code;
+
+}

Modified: trunk/Build/source/texk/web2c/luatexdir/tex/mlist.w
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/tex/mlist.w	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/tex/mlist.w	2017-03-11 00:31:06 UTC (rev 43454)
@@ -2645,11 +2645,13 @@
                 small_fam(y) = math_fam(nucleus(q));
                 small_char(y) = math_character(nucleus(q));
                 x = do_delimiter(q, y, text_size, ok_size, false, cur_style, true, NULL, &delta);
-                if (do_new_math(cur_f)) {
-                    /* we never added italic correction */
-                } else if ((subscr(q) != null) && (subtype(q) != op_noad_type_limits)) {
-                    /* remove italic correction */
-                    width(x) -= delta;
+                if (delta != 0) {
+                    if (do_new_math(cur_f)) {
+                        /* we never added italic correction */
+                    } else if ((subscr(q) != null) && (subtype(q) != op_noad_type_limits)) {
+                        /* remove italic correction */
+                        width(x) -= delta;
+                    }
                 }
             } else {
                 ok_size = height_plus_depth(cur_f, cur_c) + 1;
@@ -2667,7 +2669,7 @@
                         /* we never added italic correction */
                     } else if ((subscr(q) != null) && (subtype(q) != op_noad_type_limits)) {
                         /* remove italic correction */
-                        width(x) = width(x) - delta;
+                        width(x) -= delta;
                     }
                 }
                 axis_shift = true;
@@ -2681,7 +2683,7 @@
                     /* we never added italic correction */
                 } else if ((subscr(q) != null) && (subtype(q) != op_noad_type_limits)) {
                     /* remove italic correction */
-                    width(x) = width(x) - delta;
+                    width(x) -= delta;
                 }
             }
             axis_shift = true;
@@ -2710,7 +2712,7 @@
                 /*
                     make_scripts(q, p, 0, cur_style, delta, -delta);
                 */
-                int mode = nolimits_mode_par; /* wins */
+                int mode = math_nolimits_mode_par; /* wins */
                 /*
                     for easy configuration ... fonts are somewhat inconsistent and the
                     values for italic correction run from 30 to 60% of the width
@@ -3296,7 +3298,7 @@
         */
         x = clean_box(subscr(q), sub_style(cur_style), cur_style);
         width(x) = width(x) + space_after_script(cur_style);
-        switch (scripts_mode_par) {
+        switch (math_scripts_mode_par) {
             case 1:
                 shift_down = sub_shift_down(cur_style) ;
                 break;
@@ -3346,7 +3348,7 @@
         */
         x = clean_box(supscr(q), sup_style(cur_style), cur_style);
         width(x) = width(x) + space_after_script(cur_style);
-        switch (scripts_mode_par) {
+        switch (math_scripts_mode_par) {
             case 1:
                 shift_up = sup_shift_up(cur_style);
                 break;
@@ -3401,7 +3403,7 @@
             */
             y = clean_box(subscr(q), sub_style(cur_style), cur_style);
             width(y) = width(y) + space_after_script(cur_style);
-            switch (scripts_mode_par) {
+            switch (math_scripts_mode_par) {
                 case 1:
                     shift_down = sub_shift_down(cur_style) ;
                     break;
@@ -3866,6 +3868,7 @@
     int t_subtype;                        /* the effective |subtype| of noad |q| during the second pass */
     pointer p = null;
     pointer z = null;
+    halfword nxt ;
     int pen;                              /* a penalty to be inserted */
     scaled max_hl = 0;                    /* maximum height of the list translated so far */
     scaled max_d = 0;                     /* maximum depth of the list translated so far */
@@ -3886,6 +3889,7 @@
         */
       RESWITCH:
         delta = 0;
+        nxt = vlink(q);
         switch (type(q)) {
         case simple_noad:
             switch (subtype(q)) {
@@ -4074,8 +4078,42 @@
 
         */
         p = check_nucleus_complexity(q, &delta, cur_style);
-
         if ((subscr(q) == null) && (supscr(q) == null)) {
+            /*
+                Adding italic correction here is kind of fuzzy because some
+                characters already have that built in. However, we also add
+                it in the scripts so if it's optional here it also should
+                be there.
+            */
+            if (nxt && (math_italics_mode_par > 0) && (delta != 0)) {
+                if (type(nxt) == simple_noad) {
+                    switch (subtype(nxt)) {
+                        case ord_noad_type:
+                        case bin_noad_type:
+                        case rel_noad_type:
+                        case open_noad_type:
+                        case close_noad_type:
+                        case punct_noad_type:
+                        case inner_noad_type:
+                            delta = 0;
+                            break;
+                        case op_noad_type_normal:
+                        case op_noad_type_limits:
+                        case op_noad_type_no_limits:
+                        case under_noad_type:
+                        case over_noad_type:
+                        case vcenter_noad_type:
+                            break;
+                        default:
+                            break;
+                    }
+                }
+                if (delta != 0) {
+                    pointer d = new_kern(delta);
+                    reset_attributes(d, node_attr(q));
+                    couple_nodes(p,d);
+                }
+            }
             assign_new_hlist(q, p);
         } else {
             /* top, bottom */
@@ -4219,7 +4257,7 @@
             r_type = type(vlink(q));
             r_subtype = subtype(vlink(q));
             if (r_type != penalty_node && (r_type != simple_noad || r_subtype != rel_noad_type)) {
-                z = new_penalty(pen);
+                z = new_penalty(pen,noad_penalty);
                 reset_attributes(z, node_attr(q));
                 couple_nodes(p,z);
                 p = z;

Modified: trunk/Build/source/texk/web2c/luatexdir/tex/postlinebreak.w
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/tex/postlinebreak.w	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/tex/postlinebreak.w	2017-03-11 00:31:06 UTC (rev 43454)
@@ -505,7 +505,7 @@
                 }
             }
             if (pen != 0) {
-                r = new_penalty(pen);
+                r = new_penalty(pen,linebreak_penalty);
                 couple_nodes(cur_list.tail_field, r);
                 cur_list.tail_field = r;
             }

Modified: trunk/Build/source/texk/web2c/luatexdir/tex/texmath.w
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/tex/texmath.w	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/tex/texmath.w	2017-03-11 00:31:06 UTC (rev 43454)
@@ -1,2569 +1,2569 @@
-% texmath.w
-%
-% Copyright 2008-2010 Taco Hoekwater <taco@@luatex.org>
-%
-% 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 <http://www.gnu.org/licenses/>.
-
-@ @c
-#include "ptexlib.h"
-
-@ @c
-#define mode     mode_par
-#define tail     tail_par
-#define head     head_par
-#define dir_save dirs_par
-
-/*
-
-    \mathdisplayskipmode
-
-    tex normally always inserts before and only after when larger than zero
-
-    0 = normal tex
-    1 = always
-    2 = non-zero
-    3 = ignore
-
-*/
-
-@ TODO: not sure if this is the right order
- at c
-#define back_error(A,B) do {                    \
-    OK_to_interrupt=false;                      \
-    back_input();                               \
-    OK_to_interrupt=true;                       \
-    tex_error(A,B);                             \
-  } while (0)
-
-@ @c
-int scan_math(pointer, int);
-int scan_math_style(pointer, int);
-pointer fin_mlist(pointer);
-
-@ When \TeX\ reads a formula that is enclosed between \.\$'s, it constructs an
-{\sl mlist}, which is essentially a tree structure representing that
-formula.  An mlist is a linear sequence of items, but we can regard it as
-a tree structure because mlists can appear within mlists. For example, many
-of the entries can be subscripted or superscripted, and such ``scripts''
-are mlists in their own right.
-
-An entire formula is parsed into such a tree before any of the actual
-typesetting is done, because the current style of type is usually not
-known until the formula has been fully scanned. For example, when the
-formula `\.{\$a+b \\over c+d\$}' is being read, there is no way to tell
-that `\.{a+b}' will be in script size until `\.{\\over}' has appeared.
-
-During the scanning process, each element of the mlist being built is
-classified as a relation, a binary operator, an open parenthesis, etc.,
-or as a construct like `\.{\\sqrt}' that must be built up. This classification
-appears in the mlist data structure.
-
-After a formula has been fully scanned, the mlist is converted to an hlist
-so that it can be incorporated into the surrounding text. This conversion is
-controlled by a recursive procedure that decides all of the appropriate
-styles by a ``top-down'' process starting at the outermost level and working
-in towards the subformulas. The formula is ultimately pasted together using
-combinations of horizontal and vertical boxes, with glue and penalty nodes
-inserted as necessary.
-
-An mlist is represented internally as a linked list consisting chiefly
-of ``noads'' (pronounced ``no-adds''), to distinguish them from the somewhat
-similar ``nodes'' in hlists and vlists. Certain kinds of ordinary nodes are
-allowed to appear in mlists together with the noads; \TeX\ tells the difference
-by means of the |type| field, since a noad's |type| is always greater than
-that of a node. An mlist does not contain character nodes, hlist nodes, vlist
-nodes, math nodes or unset nodes; in particular, each mlist item appears in the
-variable-size part of |mem|, so the |type| field is always present.
-
-Each noad is five or more words long. The first word contains the
-|type| and |subtype| and |link| fields that are already so familiar to
-us; the second contains the attribute list pointer, and the third,
-fourth an fifth words are called the noad's |nucleus|, |subscr|, and
-|supscr| fields. (This use of a combined attribute list is temporary.
-Eventually, each of fields need their own list)
-
-Consider, for example, the simple formula `\.{\$x\^2\$}', which would be
-parsed into an mlist containing a single element called an |ord_noad|.
-The |nucleus| of this noad is a representation of `\.x', the |subscr| is
-empty, and the |supscr| is a representation of `\.2'.
-
-The |nucleus|, |subscr|, and |supscr| fields are further broken into
-subfields. If |p| points to a noad, and if |q| is one of its principal
-fields (e.g., |q=subscr(p)|), |q=null| indicates a field with no value (the
-corresponding attribute of noad |p| is not present). Otherwise, there are
-several possibilities for the subfields, depending on the |type| of |q|.
-
-\yskip\hang|type(q)=math_char_node| means that |math_fam(q)| refers to one of
-the sixteen font families, and |character(q)| is the number of a character
-within a font of that family, as in a character node.
-
-\yskip\hang|type(q)=math_text_char_node| is similar, but the character is
-unsubscripted and unsuperscripted and it is followed immediately by another
-character from the same font. (This |type| setting appears only
-briefly during the processing; it is used to suppress unwanted italic
-corrections.)
-
-\yskip\hang|type(q)=sub_box_node| means that |math_list(q)| points to a box
-node (either an |hlist_node| or a |vlist_node|) that should be used as the
-value of the field.  The |shift_amount| in the subsidiary box node is the
-amount by which that box will be shifted downward.
-
-\yskip\hang|type(q)=sub_mlist_node| means that |math_list(q)| points to
-an mlist; the mlist must be converted to an hlist in order to obtain
-the value of this field.
-
-\yskip\noindent In the latter case, we might have |math_list(q)=null|. This
-is not the same as |q=null|; for example, `\.{\$P\_\{\}\$}'
-and `\.{\$P\$}' produce different results (the former will not have the
-``italic correction'' added to the width of |P|, but the ``script skip''
-will be added).
-
- at c
-static void unsave_math(void)
-{
-    unsave();
-    decr(save_ptr);
-    flush_node_list(text_dir_ptr);
-    assert(saved_type(0) == saved_textdir);
-    text_dir_ptr = saved_value(0);
-}
-
-@ Sometimes it is necessary to destroy an mlist. The following
-subroutine empties the current list, assuming that |abs(mode)=mmode|.
-
- at c
-void flush_math(void)
-{
-    flush_node_list(vlink(head));
-    flush_node_list(incompleat_noad_par);
-    vlink(head) = null;
-    tail = head;
-    incompleat_noad_par = null;
-}
-
-@ Before we can do anything in math mode, we need fonts.
-
- at c
-#define MATHFONTSTACK  8
-#define MATHFONTDEFAULT 0       /* == nullfont */
-
-static sa_tree math_fam_head = NULL;
-
-@ @c
-int fam_fnt(int fam_id, int size_id)
-{
-    int n = fam_id + (256 * size_id);
-    return (int) get_sa_item(math_fam_head, n).int_value;
-}
-
-void def_fam_fnt(int fam_id, int size_id, int f, int lvl)
-{
-    int n = fam_id + (256 * size_id);
-    sa_tree_item sa_value = { 0 };
-    sa_value.int_value = f;
-    set_sa_item(math_fam_head, n, sa_value, lvl);
-    fixup_math_parameters(fam_id, size_id, f, lvl);
-    if (tracing_assigns_par > 1) {
-        begin_diagnostic();
-        tprint("{assigning");
-        print_char(' ');
-        print_cmd_chr(def_family_cmd, size_id);
-        print_int(fam_id);
-        print_char('=');
-        print_font_identifier(fam_fnt(fam_id, size_id));
-        print_char('}');
-        end_diagnostic(false);
-    }
-}
-
-@ @c
-static void unsave_math_fam_data(int gl)
-{
-    sa_stack_item st;
-    if (math_fam_head->stack == NULL)
-        return;
-    while (math_fam_head->stack_ptr > 0 &&
-           abs(math_fam_head->stack[math_fam_head->stack_ptr].level)
-           >= (int) gl) {
-        st = math_fam_head->stack[math_fam_head->stack_ptr];
-        if (st.level > 0) {
-            rawset_sa_item(math_fam_head, st.code, st.value);
-            /* now do a trace message, if requested */
-            if (tracing_restores_par > 1) {
-                int size_id = st.code / 256;
-                int fam_id = st.code % 256;
-                begin_diagnostic();
-                tprint("{restoring");
-                print_char(' ');
-                print_cmd_chr(def_family_cmd, size_id);
-                print_int(fam_id);
-                print_char('=');
-                print_font_identifier(fam_fnt(fam_id, size_id));
-                print_char('}');
-                end_diagnostic(false);
-            }
-        }
-        (math_fam_head->stack_ptr)--;
-    }
-}
-
-@ and parameters
-
- at c
-#define MATHPARAMSTACK  8
-#define MATHPARAMDEFAULT undefined_math_parameter
-
-static sa_tree math_param_head = NULL;
-
-@ @c
-void def_math_param(int param_id, int style_id, scaled value, int lvl)
-{
-    int n = param_id + (256 * style_id);
-    sa_tree_item sa_value = { 0 };
-    sa_value.int_value = (int) value;
-    set_sa_item(math_param_head, n, sa_value, lvl);
-    if (tracing_assigns_par > 1) {
-        begin_diagnostic();
-        tprint("{assigning");
-        print_char(' ');
-        print_cmd_chr(set_math_param_cmd, param_id);
-        print_cmd_chr(math_style_cmd, style_id);
-        print_char('=');
-        print_int(value);
-        print_char('}');
-        end_diagnostic(false);
-    }
-}
-
-scaled get_math_param(int param_id, int style_id)
-{
-    int n = param_id + (256 * style_id);
-    return (scaled) get_sa_item(math_param_head, n).int_value;
-}
-
-@ @c
-static void unsave_math_param_data(int gl)
-{
-    sa_stack_item st;
-    if (math_param_head->stack == NULL)
-        return;
-    while (math_param_head->stack_ptr > 0 &&
-           abs(math_param_head->stack[math_param_head->stack_ptr].level)
-           >= (int) gl) {
-        st = math_param_head->stack[math_param_head->stack_ptr];
-        if (st.level > 0) {
-            rawset_sa_item(math_param_head, st.code, st.value);
-            /* now do a trace message, if requested */
-            if (tracing_restores_par > 1) {
-                int param_id = st.code % 256;
-                int style_id = st.code / 256;
-                begin_diagnostic();
-                tprint("{restoring");
-                print_char(' ');
-                print_cmd_chr(set_math_param_cmd, param_id);
-                print_cmd_chr(math_style_cmd, style_id);
-                print_char('=');
-                print_int(get_math_param(param_id, style_id));
-                print_char('}');
-                end_diagnostic(false);
-            }
-        }
-        (math_param_head->stack_ptr)--;
-    }
-}
-
-@ saving and unsaving of both
-
- at c
-void unsave_math_data(int gl)
-{
-    unsave_math_fam_data(gl);
-    unsave_math_param_data(gl);
-}
-
-@ Dumping and undumping
- at c
-void dump_math_data(void)
-{
-    sa_tree_item sa_value = { 0 };
-    if (math_fam_head == NULL) {
-        sa_value.int_value = MATHFONTDEFAULT;
-        math_fam_head = new_sa_tree(MATHFONTSTACK, 1, sa_value);
-    }
-    dump_sa_tree(math_fam_head, "mathfonts");
-    if (math_param_head == NULL) {
-        sa_value.int_value = MATHPARAMDEFAULT;
-        math_param_head = new_sa_tree(MATHPARAMSTACK, 1, sa_value);
-    }
-    dump_sa_tree(math_param_head, "mathparameters");
-}
-
-void undump_math_data(void)
-{
-    math_fam_head = undump_sa_tree("mathfonts");
-    math_param_head = undump_sa_tree("mathparameters");
-}
-
-@ @c
-void initialize_math(void)
-{
-    sa_tree_item sa_value = { 0 };
-    if (math_fam_head == NULL) {
-        sa_value.int_value = MATHFONTDEFAULT;
-        math_fam_head = new_sa_tree(MATHFONTSTACK, 1, sa_value);
-    }
-    if (math_param_head == NULL) {
-        sa_value.int_value = MATHPARAMDEFAULT;
-        math_param_head = new_sa_tree(MATHPARAMSTACK, 1, sa_value);
-        initialize_math_spacing();
-    }
-    return;
-}
-
-@ Each portion of a formula is classified as Ord, Op, Bin, Rel, Ope,
-Clo, Pun, or Inn, for purposes of spacing and line breaking. An
-|ord_noad|, |op_noad|, |bin_noad|, |rel_noad|, |open_noad|, |close_noad|,
-|punct_noad|, or |inner_noad| is used to represent portions of the various
-types. For example, an `\.=' sign in a formula leads to the creation of a
-|rel_noad| whose |nucleus| field is a representation of an equals sign
-(usually |fam=0|, |character=075|).  A formula preceded by \.{\\mathrel}
-also results in a |rel_noad|.  When a |rel_noad| is followed by an
-|op_noad|, say, and possibly separated by one or more ordinary nodes (not
-noads), \TeX\ will insert a penalty node (with the current |rel_penalty|)
-just after the formula that corresponds to the |rel_noad|, unless there
-already was a penalty immediately following; and a ``thick space'' will be
-inserted just before the formula that corresponds to the |op_noad|.
-
-A noad of type |ord_noad|, |op_noad|, \dots, |inner_noad| usually
-has a |subtype=normal|. The only exception is that an |op_noad| might
-have |subtype=limits| or |no_limits|, if the normal positioning of
-limits has been overridden for this operator.
-
-A |radical_noad| also has a |left_delimiter| field, which usually
-represents a square root sign.
-
-A |fraction_noad| has a |right_delimiter| field as well as a |left_delimiter|.
-
-Delimiter fields have four subfields
-called |small_fam|, |small_char|, |large_fam|, |large_char|. These subfields
-represent variable-size delimiters by giving the ``small'' and ``large''
-starting characters, as explained in Chapter~17 of {\sl The \TeX book}.
-@:TeXbook}{\sl The \TeX book@>
-
-A |fraction_noad| is actually quite different from all other noads.
-It has |thickness|, |denominator|, and |numerator| fields instead of
-|nucleus|, |subscr|, and |supscr|. The |thickness| is a scaled value
-that tells how thick to make a fraction rule; however, the special
-value |default_code| is used to stand for the
-|default_rule_thickness| of the current size. The |numerator| and
-|denominator| point to mlists that define a fraction; we always have
-$$\hbox{|type(numerator)=type(denominator)=sub_mlist|}.$$ The
-|left_delimiter| and |right_delimiter| fields specify delimiters that will
-be placed at the left and right of the fraction. In this way, a
-|fraction_noad| is able to represent all of \TeX's operators \.{\\over},
-\.{\\atop}, \.{\\above}, \.{\\overwithdelims}, \.{\\atopwithdelims}, and
- \.{\\abovewithdelims}.
-
-@ The |new_noad| function creates an |ord_noad| that is completely null
-
- at c
-pointer new_noad(void)
-{
-    pointer p;
-    p = new_node(simple_noad, ord_noad_type);
-    /* all noad fields are zero after this */
-    return p;
-}
-
-@ @c
-pointer new_sub_box(pointer curbox)
-{
-    pointer p, q;
-    p = new_noad();
-    q = new_node(sub_box_node, 0);
-    nucleus(p) = q;
-    math_list(nucleus(p)) = curbox;
-    return p;
-}
-
-@ A few more kinds of noads will complete the set: An |under_noad| has its
-nucleus underlined; an |over_noad| has it overlined. An |accent_noad| places
-an accent over its nucleus; the accent character appears as
-|math_fam(accent_chr(p))| and |math_character(accent_chr(p))|. A |vcenter_noad|
-centers its nucleus vertically with respect to the axis of the formula;
-in such noads we always have |type(nucleus(p))=sub_box|.
-
-And finally, we have the |fence_noad| type, to implement
-\TeX's \.{\\left} and \.{\\right} as well as eTeX's \.{\\middle}.
-The |nucleus| of such noads is
-replaced by a |delimiter| field; thus, for example, `\.{\\left(}' produces
-a |fence_noad| such that |delimiter(p)| holds the family and character
-codes for all left parentheses. A |fence_noad| of subtype |left_noad_side|
-never appears in an mlist except as the first element, and a |fence_noad|
-with subtype |right_noad_side| never appears in an mlist
-except as the last element; furthermore, we either have both a |left_noad_side|
-and a |right_noad_side|, or neither one is present.
-
-@ Math formulas can also contain instructions like \.{\\textstyle} that
-override \TeX's normal style rules. A |style_node| is inserted into the
-data structure to record such instructions; it is three words long, so it
-is considered a node instead of a noad. The |subtype| is either |display_style|
-or |text_style| or |script_style| or |script_script_style|. The
-second and third words of a |style_node| are not used, but they are
-present because a |choice_node| is converted to a |style_node|.
-
-\TeX\ uses even numbers 0, 2, 4, 6 to encode the basic styles
-|display_style|, \dots, |script_script_style|, and adds~1 to get the
-``cramped'' versions of these styles. This gives a numerical order that
-is backwards from the convention of Appendix~G in {\sl The \TeX book\/};
-i.e., a smaller style has a larger numerical value.
-@:TeXbook}{\sl The \TeX book@>
-
- at c
-const char *math_style_names[] = {
-    "display", "crampeddisplay",
-    "text", "crampedtext",
-    "script", "crampedscript",
-    "scriptscript", "crampedscriptscript",
-    NULL
-};
-
-const char *math_param_names[] = {
-    "quad", "axis", "operatorsize",
-    "overbarkern", "overbarrule", "overbarvgap",
-    "underbarkern", "underbarrule", "underbarvgap",
-    "radicalkern", "radicalrule", "radicalvgap",
-    "radicaldegreebefore", "radicaldegreeafter", "radicaldegreeraise",
-    "stackvgap", "stacknumup", "stackdenomdown",
-    "fractionrule", "fractionnumvgap", "fractionnumup",
-    "fractiondenomvgap", "fractiondenomdown", "fractiondelsize",
-    "limitabovevgap", "limitabovebgap", "limitabovekern",
-    "limitbelowvgap", "limitbelowbgap", "limitbelowkern",
-    "nolimitsubfactor", "nolimitsupfactor", /* bonus */
-    "underdelimitervgap", "underdelimiterbgap",
-    "overdelimitervgap", "overdelimiterbgap",
-    "subshiftdrop", "supshiftdrop", "subshiftdown",
-    "subsupshiftdown", "subtopmax", "supshiftup",
-    "supbottommin", "supsubbottommax", "subsupvgap",
-    "spaceafterscript", "connectoroverlapmin",
-    "ordordspacing", "ordopspacing", "ordbinspacing", "ordrelspacing",
-    "ordopenspacing", "ordclosespacing", "ordpunctspacing", "ordinnerspacing",
-    "opordspacing", "opopspacing", "opbinspacing", "oprelspacing",
-    "opopenspacing", "opclosespacing", "oppunctspacing", "opinnerspacing",
-    "binordspacing", "binopspacing", "binbinspacing", "binrelspacing",
-    "binopenspacing", "binclosespacing", "binpunctspacing", "bininnerspacing",
-    "relordspacing", "relopspacing", "relbinspacing", "relrelspacing",
-    "relopenspacing", "relclosespacing", "relpunctspacing", "relinnerspacing",
-    "openordspacing", "openopspacing", "openbinspacing", "openrelspacing",
-    "openopenspacing", "openclosespacing", "openpunctspacing",
-    "openinnerspacing",
-    "closeordspacing", "closeopspacing", "closebinspacing", "closerelspacing",
-    "closeopenspacing", "closeclosespacing", "closepunctspacing",
-    "closeinnerspacing",
-    "punctordspacing", "punctopspacing", "punctbinspacing", "punctrelspacing",
-    "punctopenspacing", "punctclosespacing", "punctpunctspacing",
-    "punctinnerspacing",
-    "innerordspacing", "inneropspacing", "innerbinspacing", "innerrelspacing",
-    "inneropenspacing", "innerclosespacing", "innerpunctspacing",
-    "innerinnerspacing",
-    NULL
-};
-
-@ @c
-pointer new_style(small_number s)
-{                               /* create a style node */
-    m_style = s;
-    return new_node(style_node, s);
-}
-
-@ Finally, the \.{\\mathchoice} primitive creates a |choice_node|, which
-has special subfields |display_mlist|, |text_mlist|, |script_mlist|,
-and |script_script_mlist| pointing to the mlists for each style.
-
- at c
-static pointer new_choice(void)
-{                               /* create a choice node */
-    return new_node(choice_node, 0);    /* the |subtype| is not used */
-}
-
-@ Let's consider now the previously unwritten part of |show_node_list|
-that displays the things that can only be present in mlists; this
-program illustrates how to access the data structures just defined.
-
-In the context of the following program, |p| points to a node or noad that
-should be displayed, and the current string contains the ``recursion history''
-that leads to this point. The recursion history consists of a dot for each
-outer level in which |p| is subsidiary to some node, or in which |p| is
-subsidiary to the |nucleus| field of some noad; the dot is replaced by
-`\.\_' or `\.\^' or `\./' or `\.\\' if |p| is descended from the |subscr|
-or |supscr| or |denominator| or |numerator| fields of noads. For example,
-the current string would be `\.{.\^.\_/}' if |p| points to the |ord_noad| for
-|x| in the (ridiculous) formula
-`\.{\$\\sqrt\{a\^\{\\mathinner\{b\_\{c\\over x+y\}\}\}\}\$}'.
-
- at c
-void display_normal_noad(pointer p);    /* forward */
-void display_fence_noad(pointer p);     /* forward */
-void display_fraction_noad(pointer p);  /* forward */
-
-void show_math_node(pointer p)
-{
-    switch (type(p)) {
-    case style_node:
-        print_cmd_chr(math_style_cmd, subtype(p));
-        break;
-    case choice_node:
-        tprint_esc("mathchoice");
-        append_char('D');
-        show_node_list(display_mlist(p));
-        flush_char();
-        append_char('T');
-        show_node_list(text_mlist(p));
-        flush_char();
-        append_char('S');
-        show_node_list(script_mlist(p));
-        flush_char();
-        append_char('s');
-        show_node_list(script_script_mlist(p));
-        flush_char();
-        break;
-    case simple_noad:
-    case radical_noad:
-    case accent_noad:
-        display_normal_noad(p);
-        break;
-    case fence_noad:
-        display_fence_noad(p);
-        break;
-    case fraction_noad:
-        display_fraction_noad(p);
-        break;
-    default:
-        tprint("Unknown node type!");
-        break;
-    }
-}
-
-@ Here are some simple routines used in the display of noads.
-
- at c
-static void print_fam_and_char(pointer p)
-{                               /* prints family and character */
-    tprint_esc("fam");
-    print_int(math_fam(p));
-    print_char(' ');
-    print(math_character(p));
-}
-
-@ @c
-static void print_delimiter(pointer p)
-{
-    int a;
-    if (delimiteroptionset(p)) {
-        tprint(" [ ");
-        if (delimiteraxis(p))
-            tprint("axis ");
-        if (delimiternoaxis(p))
-            tprint("noaxis ");
-        if (delimiterexact(p))
-            tprint("exact ");
-        tprint("]");
-    }
-    if (delimiterheight(p)) {
-        tprint("height=");
-        print_scaled(delimiterheight(p));
-        tprint(" ");
-    }
-    if (delimiterdepth(p)) {
-        tprint("depth=");
-        print_scaled(delimiterdepth(p));
-        tprint(" ");
-    }
-    if (delimiterclass(p)) {
-        tprint("class=");
-        print_int(delimiterclass(p));
-        tprint(" ");
-    }
-    if (small_fam(p) < 0) {
-        print_int(-1);          /* this should never happen */
-    } else if (small_fam(p) < 16 && large_fam(p) < 16 &&
-               small_char(p) < 256 && large_char(p) < 256) {
-        /* traditional tex style */
-        a = small_fam(p) * 256 + small_char(p);
-        a = a * 0x1000 + large_fam(p) * 256 + large_char(p);
-        print_hex(a);
-    } else if ((large_fam(p) == 0 && large_char(p) == 0) ||
-               small_char(p) > 65535 || large_char(p) > 65535) {
-        /* modern xetex/luatex style */
-        print_hex(small_fam(p));
-        print_hex(small_char(p));
-    }
-}
-
-@ The next subroutine will descend to another level of recursion when a
-subsidiary mlist needs to be displayed. The parameter |c| indicates what
-character is to become part of the recursion history. An empty mlist is
-distinguished from a missing field, because these are not equivalent
-(as explained above).
-@^recursion@>
-
- at c
-static void print_subsidiary_data(pointer p, ASCII_code c)
-{                               /* display a noad field */
-    if ((int) cur_length >= depth_threshold) {
-        if (p != null)
-            tprint(" []");
-    } else {
-        append_char(c);         /* include |c| in the recursion history */
-        if (p != null) {
-            switch (type(p)) {
-            case math_char_node:
-                print_ln();
-                print_current_string();
-                print_fam_and_char(p);
-                break;
-            case sub_box_node:
-                show_node_list(math_list(p));
-                break;
-            case sub_mlist_node:
-                if (math_list(p) == null) {
-                    print_ln();
-                    print_current_string();
-                    tprint("{}");
-                } else {
-                    show_node_list(math_list(p));
-                }
-                break;
-            }
-        }
-        flush_char();           /* remove |c| from the recursion history */
-    }
-}
-
-@ @c
-void display_normal_noad(pointer p)
-{
-    switch (type(p)) {
-    case simple_noad:
-        switch (subtype(p)) {
-        case ord_noad_type:
-            tprint_esc("mathord");
-            break;
-        case op_noad_type_normal:
-        case op_noad_type_limits:
-        case op_noad_type_no_limits:
-            tprint_esc("mathop");
-            if (subtype(p) == op_noad_type_limits)
-                tprint_esc("limits");
-            else if (subtype(p) == op_noad_type_no_limits)
-                tprint_esc("nolimits");
-            break;
-        case bin_noad_type:
-            tprint_esc("mathbin");
-            break;
-        case rel_noad_type:
-            tprint_esc("mathrel");
-            break;
-        case open_noad_type:
-            tprint_esc("mathopen");
-            break;
-        case close_noad_type:
-            tprint_esc("mathclose");
-            break;
-        case punct_noad_type:
-            tprint_esc("mathpunct");
-            break;
-        case inner_noad_type:
-            tprint_esc("mathinner");
-            break;
-        case over_noad_type:
-            tprint_esc("overline");
-            break;
-        case under_noad_type:
-            tprint_esc("underline");
-            break;
-        case vcenter_noad_type:
-            tprint_esc("vcenter");
-            break;
-        default:
-            tprint("<unknown noad type!>");
-            break;
-        }
-        break;
-    case radical_noad:
-        if (subtype(p) == 6)
-            tprint_esc("Udelimiterover");
-        else if (subtype(p) == 5)
-            tprint_esc("Udelimiterunder");
-        else if (subtype(p) == 4)
-            tprint_esc("Uoverdelimiter");
-        else if (subtype(p) == 3)
-            tprint_esc("Uunderdelimiter");
-        else if (subtype(p) == 2)
-            tprint_esc("Uroot");
-        else
-            tprint_esc("radical");
-        print_delimiter(left_delimiter(p));
-        if (degree(p) != null) {
-            print_subsidiary_data(degree(p), '/');
-        }
-        if (radicalwidth(p)) {
-            tprint("width=");
-            print_scaled(radicalwidth(p));
-            tprint(" ");
-        }
-        if (radicaloptionset(p)) {
-            tprint(" [ ");
-            if (radicalexact(p))
-                tprint("exact ");
-            if (radicalleft(p))
-                tprint("left ");
-            if (radicalmiddle(p))
-                tprint("middle ");
-            if (radicalright(p))
-                tprint("right ");
-            tprint("]");
-        }
-        break;
-    case accent_noad:
-       if (top_accent_chr(p) != null) {
-           if (bot_accent_chr(p) != null) {
-               tprint_esc("Umathaccent both");
-           } else {
-               tprint_esc("Umathaccent");
-           }
-        } else if (bot_accent_chr(p) != null) {
-            tprint_esc("Umathaccent bottom");
-        } else {
-            tprint_esc("Umathaccent overlay");
-        }
-        if (accentfraction(p)) {
-            tprint(" fraction=");
-            print_int(accentfraction(p));
-            tprint(" ");
-        }
-        switch (subtype(p)) {
-            case 0:
-                if (top_accent_chr(p) != null) {
-                    if (bot_accent_chr(p) != null) {
-                        print_fam_and_char(top_accent_chr(p));
-                        print_fam_and_char(bot_accent_chr(p));
-                    } else {
-                        print_fam_and_char(top_accent_chr(p));
-                    }
-                } else if (bot_accent_chr(p) != null) {
-                    print_fam_and_char(bot_accent_chr(p));
-                } else {
-                    print_fam_and_char(overlay_accent_chr(p));
-                }
-                break;
-            case 1:
-                if (top_accent_chr(p) != null) {
-                    tprint(" fixed ");
-                    print_fam_and_char(top_accent_chr(p));
-                    if (bot_accent_chr(p) != null) {
-                        print_fam_and_char(bot_accent_chr(p));
-                    }
-                } else {
-                    confusion("display_accent_noad");
-                }
-                break;
-            case 2:
-                if (bot_accent_chr(p) != null) {
-                    if (top_accent_chr(p) != null) {
-                        print_fam_and_char(top_accent_chr(p));
-                    }
-                    tprint(" fixed ");
-                    print_fam_and_char(bot_accent_chr(p));
-                } else{
-                    confusion("display_accent_noad");
-                }
-                break;
-            case 3:
-                if (top_accent_chr(p) != null && bot_accent_chr(p) != null) {
-                    tprint(" fixed ");
-                    print_fam_and_char(top_accent_chr(p));
-                    tprint(" fixed ");
-                    print_fam_and_char(bot_accent_chr(p));
-                } else {
-                    confusion("display_accent_noad");
-                }
-                break;
-            }
-        break;
-    }
-    print_subsidiary_data(nucleus(p), '.');
-    print_subsidiary_data(supscr(p), '^');
-    print_subsidiary_data(subscr(p), '_');
-}
-
-@ @c
-void display_fence_noad(pointer p)
-{
-    if (subtype(p) == right_noad_side)
-        tprint_esc("right");
-    else if (subtype(p) == left_noad_side)
-        tprint_esc("left");
-    else
-        tprint_esc("middle");
-    print_delimiter(delimiter(p));
-}
-
-@ @c
-void display_fraction_noad(pointer p)
-{
-    tprint_esc("fraction, thickness ");
-    if (thickness(p) == default_code)
-        tprint("= default");
-    else
-        print_scaled(thickness(p));
-    if ((left_delimiter(p) != null) &&
-        ((small_fam(left_delimiter(p)) != 0) ||
-         (small_char(left_delimiter(p)) != 0) ||
-         (large_fam(left_delimiter(p)) != 0) ||
-         (large_char(left_delimiter(p)) != 0))) {
-        tprint(", left-delimiter ");
-        print_delimiter(left_delimiter(p));
-    }
-    if ((right_delimiter(p) != null) &&
-        ((small_fam(right_delimiter(p)) != 0) ||
-         (small_char(right_delimiter(p)) != 0) ||
-         (large_fam(right_delimiter(p)) != 0) ||
-         (large_char(right_delimiter(p)) != 0))) {
-        tprint(", right-delimiter ");
-        print_delimiter(right_delimiter(p));
-    }
-    print_subsidiary_data(numerator(p), '\\');
-    print_subsidiary_data(denominator(p), '/');
-}
-
-@ The routines that \TeX\ uses to create mlists are similar to those we have
-just seen for the generation of hlists and vlists. But it is necessary to
-make ``noads'' as well as nodes, so the reader should review the
-discussion of math mode data structures before trying to make sense out of
-the following program.
-
-Here is a little routine that needs to be done whenever a subformula
-is about to be processed. The parameter is a code like |math_group|.
-
- at c
-static void new_save_level_math(group_code c)
-{
-    set_saved_record(0, saved_textdir, 0, text_dir_ptr);
-    text_dir_ptr = new_dir(math_direction_par);
-    incr(save_ptr);
-    new_save_level(c);
-    eq_word_define(int_base + body_direction_code, math_direction_par);
-    eq_word_define(int_base + par_direction_code, math_direction_par);
-    eq_word_define(int_base + text_direction_code, math_direction_par);
-}
-
-@ @c
-static void push_math(group_code c, int mstyle)
-{
-    if (math_direction_par != text_direction_par)
-        dir_math_save = true;
-    push_nest();
-    mode = -mmode;
-    incompleat_noad_par = null;
-    m_style = mstyle;
-    new_save_level_math(c);
-}
-
-@ @c
-static void enter_ordinary_math(void)
-{
-    push_math(math_shift_group, text_style);
-    eq_word_define(int_base + cur_fam_code, -1);
-    if (every_math_par != null)
-        begin_token_list(every_math_par, every_math_text);
-}
-
-@ @c
-void enter_display_math(void);
-
-@ We get into math mode from horizontal mode when a `\.\$' (i.e., a
-|math_shift| character) is scanned. We must check to see whether this
-`\.\$' is immediately followed by another, in case display math mode is
-called for.
-
- at c
-void init_math(void)
-{
-    if (cur_cmd == math_shift_cmd) {
-        get_token();            /* |get_x_token| would fail on \.{\\ifmmode}\thinspace! */
-        if ((cur_cmd == math_shift_cmd) && (mode > 0)) {
-            enter_display_math();
-        } else {
-            back_input();
-            enter_ordinary_math();
-        }
-    } else if (cur_cmd == math_shift_cs_cmd && cur_chr == display_style && (mode > 0)) {
-        enter_display_math();
-    } else if (cur_cmd == math_shift_cs_cmd && cur_chr == text_style) {
-        enter_ordinary_math();
-    } else {
-        you_cant();
-    }
-}
-
-@ We get into ordinary math mode from display math mode when `\.{\\eqno}' or
-`\.{\\leqno}' appears. In such cases |cur_chr| will be 0 or~1, respectively;
-the value of |cur_chr| is placed onto |save_stack| for safe keeping.
-
-@ When \TeX\ is in display math mode, |cur_group=math_shift_group|,
-so it is not necessary for the |start_eq_no| procedure to test for
-this condition.
-
- at c
-void start_eq_no(void)
-{
-    set_saved_record(0, saved_eqno, 0, cur_chr);
-    incr(save_ptr);
-    enter_ordinary_math();
-}
-
-@ Subformulas of math formulas cause a new level of math mode to be entered,
-on the semantic nest as well as the save stack. These subformulas arise in
-several ways: (1)~A left brace by itself indicates the beginning of a
-subformula that will be put into a box, thereby freezing its glue and
-preventing line breaks. (2)~A subscript or superscript is treated as a
-subformula if it is not a single character; the same applies to
-the nucleus of things like \.{\\underline}. (3)~The \.{\\left} primitive
-initiates a subformula that will be terminated by a matching \.{\\right}.
-The group codes placed on |save_stack| in these three cases are
-|math_group|, |math_group|, and |math_left_group|, respectively.
-
-Here is the code that handles case (1); the other cases are not quite as
-trivial, so we shall consider them later.
-
- at c
-void math_left_brace(void)
-{
-    pointer q;
-    tail_append(new_noad());
-    q = new_node(math_char_node, 0);
-    nucleus(tail) = q;
-    back_input();
-    (void) scan_math(nucleus(tail), m_style);
-}
-
-@ If the inline directions of \.{\\pardir} and \.{\\mathdir} are
-opposite, then this function will return true. Discovering that fact
-is somewhat odd because it needs traversal of the |save_stack|.
-The occurance of displayed equations is weird enough that this is
-probably still better than having yet another field in the |input_stack|
-structures.
-
-None of this makes much sense if the inline direction of either one of
-\.{\\pardir} or \.{\\mathdir} is vertical, but in that case the current
-math machinery is ill suited anyway so I do not bother to test that.
-
- at c
-static boolean math_and_text_reversed_p(void)
-{
-    int i = save_ptr - 1;
-    while (save_type(i) != level_boundary)
-        i--;
-    while (i < save_ptr) {
-        if (save_type(i) == restore_old_value &&
-            save_value(i) == int_base + par_direction_code) {
-            if (textdir_opposite(math_direction_par, save_value(i - 1)))
-                return true;
-        }
-        i++;
-    }
-    return false;
-}
-
-@ When we enter display math mode, we need to call |line_break| to
-process the partial paragraph that has just been interrupted by the
-display. Then we can set the proper values of |display_width| and
-|display_indent| and |pre_display_size|.
-
- at c
-void enter_display_math(void)
-{
-    scaled w;                   /* new or partial |pre_display_size| */
-    scaled l;                   /* new |display_width| */
-    scaled s;                   /* new |display_indent| */
-    pointer p;
-    int n;                      /* scope of paragraph shape specification */
-    if (head == tail ||         /* `\.{\\noindent\$\$}' or `\.{\$\${ }\$\$}' */
-        (vlink(head) == tail && /* the 2nd of \.{\$\${ }\$\$} \.{\$\${ }\$\$} */
-         type(tail) == local_par_node && vlink(tail) == null)) {
-        if (vlink(head) == tail) {
-            /* bug \#270: |resume_after_display| inserts a |local_par_node|, but if
-               there is another display immediately following, we have to get rid
-               of that node */
-            flush_node(tail);
-        }
-        pop_nest();
-        w = -max_dimen;
-    } else {
-        line_break(true, math_shift_group);
-        w = actual_box_width(just_box, x_over_n(quad(get_cur_font()),1000) * math_pre_display_gap_factor_par);
-    }
-    /* now we are in vertical mode, working on the list that will contain the display */
-    /* A displayed equation is considered to be three lines long, so we
-       calculate the length and offset of line number |prev_graf+2|. */
-    if (par_shape_par_ptr == null) {
-        if ((hang_indent_par != 0) && (((hang_after_par >= 0) && (prev_graf_par + 2 > hang_after_par)) || (prev_graf_par + 1 < -hang_after_par))) {
-            halfword used_hang_indent = swap_hang_indent(hang_indent_par);
-            l = hsize_par - abs(used_hang_indent);
-            if (used_hang_indent > 0)
-                s = used_hang_indent;
-            else
-                s = 0;
-        } else {
-            l = hsize_par;
-            s = 0;
-        }
-    } else {
-        n = vinfo(par_shape_par_ptr + 1);
-        if (prev_graf_par + 2 >= n)
-            p = par_shape_par_ptr + 2 * n + 1;
-        else
-            p = par_shape_par_ptr + 2 * (prev_graf_par + 2) + 1;
-        s = varmem[(p - 1)].cint;
-        l = varmem[p].cint;
-        s = swap_parshape_indent(s,l);
-    }
-
-    push_math(math_shift_group, display_style);
-    mode = mmode;
-    eq_word_define(int_base + cur_fam_code, -1);
-    eq_word_define(dimen_base + pre_display_size_code, w);
-    eq_word_define(dimen_base + display_width_code, l);
-    eq_word_define(dimen_base + display_indent_code, s);
-    eq_word_define(int_base + pre_display_direction_code, (math_and_text_reversed_p() ? -1 : 0));
-    if (every_display_par != null)
-        begin_token_list(every_display_par, every_display_text);
-    if (nest_ptr == 1) {
-        checked_page_filter(before_display);
-        build_page();
-    }
-}
-
-@ The next routine parses all variations of a delimiter code. The |extcode|
- tells what syntax form to use (\TeX, XeTeX, XeTeXnum, ...) , the
- |doclass| tells whether or not read a math class also (for \.{\\delimiter} c.s.).
- (the class is passed on for conversion to \.{\\mathchar}).
-
- at c
-static delcodeval do_scan_extdef_del_code(int extcode, boolean doclass)
-{
-    const char *hlp[] = {
-        "I'm going to use 0 instead of that illegal code value.",
-        NULL
-    };
-    delcodeval d;
-    int mcls = 0, msfam = 0, mschr = 0, mlfam = 0, mlchr = 0;
-    if (extcode == tex_mathcode) {      /* \.{\\delcode}, this is the easiest */
-        scan_int();
-        /*  "MFCCFCC or "FCCFCC */
-        if (doclass) {
-            mcls = (cur_val / 0x1000000);
-            cur_val = (cur_val & 0xFFFFFF);
-        }
-        if (cur_val > 0xFFFFFF) {
-            tex_error("Invalid delimiter code", hlp);
-            cur_val = 0;
-        }
-        msfam = (cur_val / 0x100000);
-        mschr = (cur_val % 0x100000) / 0x1000;
-        mlfam = (cur_val & 0xFFF) / 0x100;
-        mlchr = (cur_val % 0x100);
-    } else if (extcode == umath_mathcode) {     /* \.{\\Udelcode} */
-        /* <0-7>,<0-0xFF>,<0-0x10FFFF>  or <0-0xFF>,<0-0x10FFFF> */
-        if (doclass) {
-            scan_int();
-            mcls = cur_val;
-        }
-        scan_int();
-        msfam = cur_val;
-        scan_char_num();
-        mschr = cur_val;
-        if (msfam < 0 || msfam > 255) {
-            tex_error("Invalid delimiter code", hlp);
-            msfam = 0;
-            mschr = 0;
-        }
-        mlfam = 0;
-        mlchr = 0;
-    } else if (extcode == umathnum_mathcode) {  /* \.{\\Udelcodenum} */
-        /* "FF<21bits> */
-        /* the largest numeric value is $2^29-1$, but
-           the top of bit 21 can't be used as it contains invalid USV's
-         */
-        if (doclass) {          /* such a primitive doesn't exist */
-            confusion("umathnum_mathcode");
-        }
-        scan_int();
-        msfam = (cur_val / 0x200000);
-        mschr = cur_val & 0x1FFFFF;
-        if (msfam < 0 || msfam > 255 || mschr > 0x10FFFF) {
-            tex_error("Invalid delimiter code", hlp);
-            msfam = 0;
-            mschr = 0;
-        }
-        mlfam = 0;
-        mlchr = 0;
-    } else {
-        /* something's gone wrong */
-        confusion("unknown_extcode");
-    }
-    d.class_value = mcls;
-    d.small_family_value = msfam;
-    d.small_character_value = mschr;
-    d.large_family_value = mlfam;
-    d.large_character_value = mlchr;
-    return d;
-}
-
-@ @c
-void scan_extdef_del_code(int level, int extcode)
-{
-    delcodeval d;
-    int p;
-    scan_char_num();
-    p = cur_val;
-    scan_optional_equals();
-    d = do_scan_extdef_del_code(extcode, false);
-    set_del_code(p, d.small_family_value, d.small_character_value,
-                 d.large_family_value, d.large_character_value,
-                 (quarterword) (level));
-}
-
-@ @c
-mathcodeval scan_mathchar(int extcode)
-{
-    char errstr[255] = { 0 };
-    const char *hlp[] = {
-        "I'm going to use 0 instead of that illegal code value.",
-        NULL
-    };
-    mathcodeval d;
-    int mcls = 0, mfam = 0, mchr = 0;
-    if (extcode == tex_mathcode) {      /* \.{\\mathcode} */
-        /* "TFCC */
-        scan_int();
-        if (cur_val > 0x8000) {
-            /*
-                tex_error("Invalid math code", hlp);
-                cur_val = 0;
-            */
-            /* needed for latex: fallback to umathnum_mathcode */
-            mfam = (cur_val / 0x200000) & 0x7FF;
-            mcls = mfam % 0x08;
-            mfam = mfam / 0x08;
-            mchr = cur_val & 0x1FFFFF;
-            if (mchr > 0x10FFFF) {
-                tex_error("Invalid math code during > 0x8000 mathcode fallback", hlp);
-                mcls = 0;
-                mfam = 0;
-                mchr = 0;
-            }
-        } else {
-            if (cur_val < 0) {
-                snprintf(errstr, 255, "Bad mathchar (%d)", (int)cur_val);
-                tex_error(errstr, hlp);
-                cur_val = 0;
-            }
-            mcls = (cur_val / 0x1000);
-            mfam = ((cur_val % 0x1000) / 0x100);
-            mchr = (cur_val % 0x100);
-        }
-    } else if (extcode == umath_mathcode) {
-        /* <0-0x7> <0-0xFF> <0-0x10FFFF> */
-        scan_int();
-        mcls = cur_val;
-        scan_int();
-        mfam = cur_val;
-        scan_char_num();
-        mchr = cur_val;
-        if (mcls < 0 || mcls > 7 || mfam > 255) {
-            tex_error("Invalid math code", hlp);
-            mchr = 0;
-            mfam = 0;
-            mcls = 0;
-        }
-    } else if (extcode == umathnum_mathcode) {
-        /* "FFT<21bits> */
-        /* the largest numeric value is $2^32-1$, but
-           the top of bit 21 can't be used as it contains invalid USV's
-         */
-        /* Note: |scan_int| won't accept families 128-255 because these use bit 32 */
-        scan_int();
-        mfam = (cur_val / 0x200000) & 0x7FF;
-        mcls = mfam % 0x08;
-        mfam = mfam / 0x08;
-        mchr = cur_val & 0x1FFFFF;
-        if (mchr > 0x10FFFF) {
-            tex_error("Invalid math code", hlp);
-            mcls = 0;
-            mfam = 0;
-            mchr = 0;
-        }
-    } else {
-        /* something's gone wrong */
-        confusion("unknown_extcode");
-    }
-    d.class_value = mcls;
-    d.family_value = mfam;
-    d.character_value = mchr;
-    return d;
-}
-
-@ @c
-void scan_extdef_math_code(int level, int extcode)
-{
-    mathcodeval d;
-    int p;
-    scan_char_num();
-    p = cur_val;
-    scan_optional_equals();
-    d = scan_mathchar(extcode);
-    set_math_code(p, d.class_value,
-                  d.family_value, d.character_value, (quarterword) (level));
-}
-
-@ this reads in a delcode when actually a mathcode is needed
- at c
-mathcodeval scan_delimiter_as_mathchar(int extcode)
-{
-    delcodeval dval;
-    mathcodeval mval;
-    dval = do_scan_extdef_del_code(extcode, true);
-    mval.class_value = dval.class_value;
-    mval.family_value = dval.small_family_value;
-    mval.character_value = dval.small_character_value;
-    return mval;
-}
-
-@ Recall that the |nucleus|, |subscr|, and |supscr| fields in a noad
-are broken down into subfields called |type| and either |math_list| or
-|(math_fam,math_character)|. The job of |scan_math| is to figure out
-what to place in one of these principal fields; it looks at the
-subformula that comes next in the input, and places an encoding of
-that subformula into a given word of |mem|.
-
- at c
-#define get_next_nb_nr() do { get_x_token(); } while (cur_cmd==spacer_cmd||cur_cmd==relax_cmd)
-
-int scan_math_style(pointer p, int mstyle)
-{
-    get_next_nb_nr();
-    back_input();
-    scan_left_brace();
-    set_saved_record(0, saved_math, 0, p);
-    incr(save_ptr);
-    push_math(math_group, mstyle);
-    return 1;
-}
-
-int scan_math(pointer p, int mstyle)
-{
-    /* label restart,reswitch,exit; */
-    mathcodeval mval = { 0, 0, 0 };
-    assert(p != null);
-  RESTART:
-    get_next_nb_nr();
-  RESWITCH:
-    switch (cur_cmd) {
-    case letter_cmd:
-    case other_char_cmd:
-    case char_given_cmd:
-        mval = get_math_code(cur_chr);
-        if (mval.class_value == 8) {
-            /* An active character that is an |outer_call| is allowed here */
-            cur_cs = active_to_cs(cur_chr, true);
-            cur_cmd = eq_type(cur_cs);
-            cur_chr = equiv(cur_cs);
-            x_token();
-            back_input();
-            goto RESTART;
-        }
-        break;
-    case char_num_cmd:
-        scan_char_num();
-        cur_chr = cur_val;
-        cur_cmd = char_given_cmd;
-        goto RESWITCH;
-        break;
-    case math_char_num_cmd:
-        if (cur_chr == 0)
-            mval = scan_mathchar(tex_mathcode);
-        else if (cur_chr == 1)
-            mval = scan_mathchar(umath_mathcode);
-        else if (cur_chr == 2)
-            mval = scan_mathchar(umathnum_mathcode);
-        else
-            confusion("scan_math");
-        break;
-    case math_given_cmd:
-        mval = mathchar_from_integer(cur_chr, tex_mathcode);
-        break;
-    case xmath_given_cmd:
-        mval = mathchar_from_integer(cur_chr, umath_mathcode);
-        break;
-    case delim_num_cmd:
-        if (cur_chr == 0)
-            mval = scan_delimiter_as_mathchar(tex_mathcode);
-        else if (cur_chr == 1)
-            mval = scan_delimiter_as_mathchar(umath_mathcode);
-        else
-            confusion("scan_math");
-        break;
-    default:
-        /* The pointer |p| is placed on |save_stack| while a complex subformula
-           is being scanned. */
-        back_input();
-        scan_left_brace();
-        set_saved_record(0, saved_math, 0, p);
-        incr(save_ptr);
-        push_math(math_group, mstyle);
-        return 1;
-    }
-    type(p) = math_char_node;
-    math_character(p) = mval.character_value;
-    if ((mval.class_value == math_use_current_family_code) && cur_fam_par_in_range)
-        math_fam(p) = cur_fam_par;
-    else
-        math_fam(p) = mval.family_value;
-    return 0;
-}
-
-@ The |set_math_char| procedure creates a new noad appropriate to a given
-math code, and appends it to the current mlist. However, if the math code
-is sufficiently large, the |cur_chr| is treated as an active character and
-nothing is appended.
-
- at c
-void set_math_char(mathcodeval mval)
-{
-    pointer p;                  /* the new noad */
-    if (mval.class_value == 8) {
-        /* An active character that is an |outer_call| is allowed here */
-        cur_cs = active_to_cs(cur_chr, true);
-        cur_cmd = eq_type(cur_cs);
-        cur_chr = equiv(cur_cs);
-        x_token();
-        back_input();
-    } else {
-        pointer q;
-        p = new_noad();
-        q = new_node(math_char_node, 0);
-        nucleus(p) = q;
-        math_character(nucleus(p)) = mval.character_value;
-        math_fam(nucleus(p)) = mval.family_value;
-        if (mval.class_value == math_use_current_family_code) {
-            if (cur_fam_par_in_range)
-                math_fam(nucleus(p)) = cur_fam_par;
-            subtype(p) = ord_noad_type;
-        } else {
-            switch (mval.class_value) {
-                  /* *INDENT-OFF* */
-                case 0: subtype(p) = ord_noad_type; break;
-                case 1: subtype(p) = op_noad_type_normal; break;
-                case 2: subtype(p) = bin_noad_type; break;
-                case 3: subtype(p) = rel_noad_type; break;
-                case 4: subtype(p) = open_noad_type; break;
-                case 5: subtype(p) = close_noad_type; break;
-                case 6: subtype(p) = punct_noad_type; break;
-                  /* *INDENT-ON* */
-            }
-        }
-        vlink(tail) = p;
-        tail = p;
-    }
-}
-
-@ The |math_char_in_text| procedure creates a new node representing a math char
-in text code, and appends it to the current list. However, if the math code
-is sufficiently large, the |cur_chr| is treated as an active character and
-nothing is appended.
-
- at c
-void math_char_in_text(mathcodeval mval)
-{
-    pointer p;                  /* the new node */
-    if (mval.class_value == 8) {
-        /* An active character that is an |outer_call| is allowed here */
-        cur_cs = active_to_cs(cur_chr, true);
-        cur_cmd = eq_type(cur_cs);
-        cur_chr = equiv(cur_cs);
-        x_token();
-        back_input();
-    } else {
-        p = new_char(fam_fnt(mval.family_value, text_size), mval.character_value);
-        vlink(tail) = p;
-        tail = p;
-    }
-}
-
-@ @c
-void math_math_comp(void)
-{
-    pointer q;
-    tail_append(new_noad());
-    subtype(tail) = (quarterword) cur_chr;
-    q = new_node(math_char_node, 0);
-    nucleus(tail) = q;
-    if (cur_chr == over_noad_type)
-        (void) scan_math(nucleus(tail), cramped_style(m_style));
-    else
-        (void) scan_math(nucleus(tail), m_style);
-}
-
-@ @c
-void math_limit_switch(void)
-{
-    const char *hlp[] = {
-        "I'm ignoring this misplaced \\limits or \\nolimits command.",
-        NULL
-    };
-    if (head != tail) {
-         if (type(tail) == simple_noad &&
-             (subtype(tail) == op_noad_type_normal ||
-              subtype(tail) == op_noad_type_limits ||
-              subtype(tail) == op_noad_type_no_limits)) {
-            subtype(tail) = (quarterword) cur_chr;
-            return;
-        }
-    }
-    tex_error("Limit controls must follow a math operator", hlp);
-}
-
-@ Delimiter fields of noads are filled in by the |scan_delimiter| routine.
-The first parameter of this procedure is the |mem| address where the
-delimiter is to be placed; the second tells if this delimiter follows
-\.{\\radical} or not.
-
- at c
-static void scan_delimiter(pointer p, int r)
-{
-    delcodeval dval = { 0, 0, 0, 0, 0 };
-    if (r == tex_mathcode) {    /* \.{\\radical} */
-        dval = do_scan_extdef_del_code(tex_mathcode, true);
-    } else if (r == umath_mathcode) {   /* \.{\\Uradical} */
-        dval = do_scan_extdef_del_code(umath_mathcode, false);
-    } else if (r == no_mathcode) {
-        get_next_nb_nr();
-        switch (cur_cmd) {
-        case letter_cmd:
-        case other_char_cmd:
-            dval = get_del_code(cur_chr);
-            break;
-        case delim_num_cmd:
-            if (cur_chr == 0)   /* \.{\\delimiter} */
-                dval = do_scan_extdef_del_code(tex_mathcode, true);
-            else if (cur_chr == 1)      /* \.{\\Udelimiter} */
-                dval = do_scan_extdef_del_code(umath_mathcode, true);
-            else
-                confusion("scan_delimiter1");
-            break;
-        default:
-            dval.small_family_value = -1;
-            break;
-        }
-    } else {
-        confusion("scan_delimiter2");
-    }
-    if (p == null)
-        return;
-    if (dval.small_family_value < 0) {
-        const char *hlp[] = {
-            "I was expecting to see something like `(' or `\\{' or",
-            "`\\}' here. If you typed, e.g., `{' instead of `\\{', you",
-            "should probably delete the `{' by typing `1' now, so that",
-            "braces don't get unbalanced. Otherwise just proceed",
-            "Acceptable delimiters are characters whose \\delcode is",
-            "nonnegative, or you can use `\\delimiter <delimiter code>'.",
-            NULL
-        };
-        back_error("Missing delimiter (. inserted)", hlp);
-        small_fam(p) = 0;
-        small_char(p) = 0;
-        large_fam(p) = 0;
-        large_char(p) = 0;
-    } else {
-        small_fam(p) = dval.small_family_value;
-        small_char(p) = dval.small_character_value;
-        large_fam(p) = dval.large_family_value;
-        large_char(p) = dval.large_character_value;
-    }
-    return;
-}
-
-@ @c
-void math_radical(void)
-{
-    halfword q;
-    int chr_code = cur_chr;
-    halfword options = 0;
-    tail_append(new_node(radical_noad, chr_code));
-    q = new_node(delim_node, 0);
-    left_delimiter(tail) = q;
-    while (1) {
-        if (scan_keyword("width")) {
-            scan_dimen(false,false,false);
-            radicalwidth(tail) = cur_val ;
-        } else if (scan_keyword("left")) {
-            options = options | noad_option_left ;
-        } else if (scan_keyword("middle")) {
-            options = options | noad_option_middle ;
-        } else if (scan_keyword("right")) {
-            options = options | noad_option_right ;
-        } else {
-            break;
-        }
-    }
-    radicaloptions(tail) = options;
-    if (chr_code == 0)          /* \.{\\radical} */
-        scan_delimiter(left_delimiter(tail), tex_mathcode);
-    else if (chr_code == 1)     /* \.{\\Uradical} */
-        scan_delimiter(left_delimiter(tail), umath_mathcode);
-    else if (chr_code == 2)     /* \.{\\Uroot} */
-        scan_delimiter(left_delimiter(tail), umath_mathcode);
-    else if (chr_code == 3)     /* \.{\\Uunderdelimiter} */
-        scan_delimiter(left_delimiter(tail), umath_mathcode);
-    else if (chr_code == 4)     /* \.{\\Uoverdelimiter} */
-        scan_delimiter(left_delimiter(tail), umath_mathcode);
-    else if (chr_code == 5)     /* \.{\\Udelimiterunder} */
-        scan_delimiter(left_delimiter(tail), umath_mathcode);
-    else if (chr_code == 6)     /* \.{\\Udelimiterover} */
-        scan_delimiter(left_delimiter(tail), umath_mathcode);
-    else if (chr_code == 7)     /* \.{\\Uhextensible} */
-        scan_delimiter(left_delimiter(tail), umath_mathcode);
-    else
-        confusion("math_radical");
-    if (chr_code == 7) {
-        q = new_node(sub_box_node, 0); /* type will change */
-        nucleus(tail) = q;
-        return;
-    } else if (chr_code == 2) {
-        /* the trick with the |vlink(q)| is used by |scan_math|
-           to decide whether it needs to go on */
-        q = new_node(math_char_node, 0);
-        vlink(q) = tail;
-        degree(tail) = q;
-        if (!scan_math(degree(tail), sup_sup_style(m_style))) {
-            vlink(degree(tail)) = null;
-            q = new_node(math_char_node, 0);
-            nucleus(tail) = q;
-            (void) scan_math(nucleus(tail), cramped_style(m_style));
-        }
-    } else {
-        q = new_node(math_char_node, 0);
-        nucleus(tail) = q;
-        (void) scan_math(nucleus(tail), cramped_style(m_style));
-    }
-}
-
-@ @c
-void math_ac(void)
-{
-    halfword q;
-    mathcodeval t = { 0, 0, 0 };
-    mathcodeval b = { 0, 0, 0 };
-    mathcodeval o = { 0, 0, 0 };
-    if (cur_cmd == accent_cmd) {
-        const char *hlp[] = {
-            "I'm changing \\accent to \\mathaccent here; wish me luck.",
-            "(Accents are not the same in formulas as they are in text.)",
-            NULL
-        };
-        tex_error("Please use \\mathaccent for accents in math mode", hlp);
-    }
-    tail_append(new_node(accent_noad, 0));
-    if (cur_chr == 0) {         /* \.{\\mathaccent} */
-        t = scan_mathchar(tex_mathcode);
-    } else if (cur_chr == 1) {  /* \.{\\Umathaccent} */
-        if (scan_keyword("fixed")) {
-            /* top */
-            subtype(tail) = 1;
-            t = scan_mathchar(umath_mathcode);
-        } else if (scan_keyword("both")) {
-            /* top bottom */
-            if (scan_keyword("fixed")) {
-                subtype(tail) = 1;
-            }
-            t = scan_mathchar(umath_mathcode);
-            if (scan_keyword("fixed")) {
-                subtype(tail) += 2;
-            }
-            b = scan_mathchar(umath_mathcode);
-        } else if (scan_keyword("bottom")) {
-            /* bottom */
-            if (scan_keyword("fixed")) {
-                subtype(tail) = 2;
-            }
-            b = scan_mathchar(umath_mathcode);
-        } else if (scan_keyword("top")) {
-            /* top */
-            if (scan_keyword("fixed")) {
-                subtype(tail) = 1;
-            }
-            t = scan_mathchar(umath_mathcode);
-        } else if (scan_keyword("overlay")) {
-            /* overlay */
-            if (scan_keyword("fixed")) {
-                subtype(tail) = 1;
-            }
-            o = scan_mathchar(umath_mathcode);
-        } else {
-            /* top */
-            t = scan_mathchar(umath_mathcode);
-        }
-        if (scan_keyword("fraction")) {
-            scan_int();
-            accentfraction(tail) = cur_val;
-        }
-    } else {
-        confusion("mathaccent");
-    }
-    if (!(t.character_value == 0 && t.family_value == 0)) {
-        q = new_node(math_char_node, 0);
-        top_accent_chr(tail) = q;
-        math_character(top_accent_chr(tail)) = t.character_value;
-        if ((t.class_value == math_use_current_family_code) && cur_fam_par_in_range)
-            math_fam(top_accent_chr(tail)) = cur_fam_par;
-        else
-            math_fam(top_accent_chr(tail)) = t.family_value;
-    }
-    if (!(b.character_value == 0 && b.family_value == 0)) {
-        q = new_node(math_char_node, 0);
-        bot_accent_chr(tail) = q;
-        math_character(bot_accent_chr(tail)) = b.character_value;
-        if ((b.class_value == math_use_current_family_code) && cur_fam_par_in_range)
-            math_fam(bot_accent_chr(tail)) = cur_fam_par;
-        else
-            math_fam(bot_accent_chr(tail)) = b.family_value;
-    }
-    if (!(o.character_value == 0 && o.family_value == 0)) {
-        q = new_node(math_char_node, 0);
-        overlay_accent_chr(tail) = q;
-        math_character(overlay_accent_chr(tail)) = o.character_value;
-        if ((o.class_value == math_use_current_family_code) && cur_fam_par_in_range)
-            math_fam(overlay_accent_chr(tail)) = cur_fam_par;
-        else
-            math_fam(overlay_accent_chr(tail)) = o.family_value;
-    }
-    q = new_node(math_char_node, 0);
-    nucleus(tail) = q;
-    (void) scan_math(nucleus(tail), cramped_style(m_style));
-}
-
-@ @c
-pointer math_vcenter_group(pointer p)
-{
-    pointer q, r;
-    q = new_noad();
-    subtype(q) = vcenter_noad_type;
-    r = new_node(sub_box_node, 0);
-    nucleus(q) = r;
-    math_list(nucleus(q)) = p;
-    return q;
-}
-
-@ The routine that scans the four mlists of a \.{\\mathchoice} is very
-much like the routine that builds discretionary nodes.
-
- at c
-void append_choices(void)
-{
-    tail_append(new_choice());
-    incr(save_ptr);
-    set_saved_record(-1, saved_choices, 0, 0);
-    push_math(math_choice_group, display_style);
-    scan_left_brace();
-}
-
-@ @c
-void build_choices(void)
-{
-    pointer p;                  /* the current mlist */
-    int prev_style;
-    prev_style = m_style;
-    unsave_math();
-    p = fin_mlist(null);
-    assert(saved_type(-1) == saved_choices);
-    switch (saved_value(-1)) {
-    case 0:
-        display_mlist(tail) = p;
-        break;
-    case 1:
-        text_mlist(tail) = p;
-        break;
-    case 2:
-        script_mlist(tail) = p;
-        break;
-    case 3:
-        script_script_mlist(tail) = p;
-        decr(save_ptr);
-        return;
-        break;
-    }                           /* there are no other cases */
-    set_saved_record(-1, saved_choices, 0, (saved_value(-1) + 1));
-    push_math(math_choice_group, (prev_style + 2));
-    scan_left_brace();
-}
-
-@ Subscripts and superscripts are attached to the previous nucleus by the
-action procedure called |sub_sup|.
-
- at c
-void sub_sup(void)
-{
-    pointer q;
-    if (tail == head || (!scripts_allowed(tail))) {
-        tail_append(new_noad());
-        q = new_node(sub_mlist_node, 0);
-        nucleus(tail) = q;
-    }
-    if (cur_cmd == sup_mark_cmd || cur_chr == sup_mark_cmd) {   /* |super_sub_script| */
-        if (supscr(tail) != null) {
-            const char *hlp[] = {
-                "I treat `x^1^2' essentially like `x^1{}^2'.", NULL
-            };
-            tail_append(new_noad());
-            q = new_node(sub_mlist_node, 0);
-            nucleus(tail) = q;
-            tex_error("Double superscript", hlp);
-        }
-        q = new_node(math_char_node, 0);
-        supscr(tail) = q;
-        (void) scan_math(supscr(tail), sup_style(m_style));
-    } else if (cur_cmd == sub_mark_cmd || cur_chr == sub_mark_cmd) {
-        if (subscr(tail) != null) {
-            const char *hlp[] = {
-                "I treat `x_1_2' essentially like `x_1{}_2'.", NULL
-            };
-            tail_append(new_noad());
-            q = new_node(sub_mlist_node, 0);
-            nucleus(tail) = q;
-            tex_error("Double subscript", hlp);
-        }
-        q = new_node(math_char_node, 0);
-        subscr(tail) = q;
-        (void) scan_math(subscr(tail), sub_style(m_style));
-    }
-}
-
-@ An operation like `\.{\\over}' causes the current mlist to go into a
-state of suspended animation: |incompleat_noad| points to a |fraction_noad|
-that contains the mlist-so-far as its numerator, while the denominator
-is yet to come. Finally when the mlist is finished, the denominator will
-go into the incompleat fraction noad, and that noad will become the
-whole formula, unless it is surrounded by `\.{\\left}' and `\.{\\right}'
-delimiters.
-
- at c
-void math_fraction(void)
-{
-    halfword c;                 /* the type of generalized fraction we are scanning */
-    pointer q;
-    halfword options = 0;
-    halfword temp_value;
-    c = cur_chr;
-    if (incompleat_noad_par != null) {
-        const char *hlp[] = {
-            "I'm ignoring this fraction specification, since I don't",
-            "know whether a construction like `x \\over y \\over z'",
-            "means `{x \\over y} \\over z' or `x \\over {y \\over z}'.",
-            NULL
-        };
-        if (c >= delimited_code) {
-            scan_delimiter(null, no_mathcode);
-            scan_delimiter(null, no_mathcode);
-        }
-        if ((c % delimited_code) == above_code)
-            scan_normal_dimen();
-        tex_error("Ambiguous; you need another { and }", hlp);
-    } else {
-        incompleat_noad_par = new_node(fraction_noad, 0);
-        temp_value = new_node(sub_mlist_node, 0);
-        numerator(incompleat_noad_par) = temp_value;
-        math_list(numerator(incompleat_noad_par)) = vlink(head);
-        vlink(head) = null;
-        tail = head;
-        m_style = cramped_style(m_style);
-
-        if ((c % delimited_code) == skewed_code) {
-            q = new_node(delim_node, 0);
-            middle_delimiter(incompleat_noad_par) = q;
-            scan_delimiter(middle_delimiter(incompleat_noad_par), no_mathcode);
-        }
-        if (c >= delimited_code) {
-            q = new_node(delim_node, 0);
-            left_delimiter(incompleat_noad_par) = q;
-            q = new_node(delim_node, 0);
-            right_delimiter(incompleat_noad_par) = q;
-            scan_delimiter(left_delimiter(incompleat_noad_par), no_mathcode);
-            scan_delimiter(right_delimiter(incompleat_noad_par), no_mathcode);
-        }
-        switch (c % delimited_code) {
-            case above_code:
-                while (1) {
-                    if (scan_keyword("exact")) {
-                        options = options | noad_option_exact ;
-                    } else {
-                        break;
-                    }
-                }
-                fractionoptions(incompleat_noad_par) = options;
-                scan_normal_dimen();
-                thickness(incompleat_noad_par) = cur_val;
-                break;
-            case over_code:
-                thickness(incompleat_noad_par) = default_code;
-                break;
-            case atop_code:
-                thickness(incompleat_noad_par) = 0;
-                break;
-            case skewed_code:
-                while (1) {
-                    if (scan_keyword("exact")) {
-                        options = options | noad_option_exact ;
-                    } else if (scan_keyword("noaxis")) {
-                        options = options | noad_option_no_axis ;
-                    } else {
-                        break;
-                    }
-                }
-                fractionoptions(incompleat_noad_par) = options;
-                thickness(incompleat_noad_par) = 0;
-                break;
-        }
-    }
-}
-
-@ At the end of a math formula or subformula, the |fin_mlist| routine is
-called upon to return a pointer to the newly completed mlist, and to
-pop the nest back to the enclosing semantic level. The parameter to
-|fin_mlist|, if not null, points to a |fence_noad| that ends the
-current mlist; this |fence_noad| has not yet been appended.
-
- at c
-pointer fin_mlist(pointer p)
-{
-    pointer q;                  /* the mlist to return */
-    if (incompleat_noad_par != null) {
-        if (denominator(incompleat_noad_par) != null) {
-            type(denominator(incompleat_noad_par)) = sub_mlist_node;
-        } else {
-            q = new_node(sub_mlist_node, 0);
-            denominator(incompleat_noad_par) = q;
-        }
-        math_list(denominator(incompleat_noad_par)) = vlink(head);
-        if (p == null) {
-            q = incompleat_noad_par;
-        } else {
-            q = math_list(numerator(incompleat_noad_par));
-            if ((type(q) != fence_noad) || (subtype(q) != left_noad_side)
-                || (delim_par == null))
-                confusion("right");     /* this can't happen */
-            math_list(numerator(incompleat_noad_par)) = vlink(delim_par);
-            vlink(delim_par) = incompleat_noad_par;
-            vlink(incompleat_noad_par) = p;
-        }
-    } else {
-        vlink(tail) = p;
-        q = vlink(head);
-    }
-    pop_nest();
-    return q;
-}
-
-@ Now at last we're ready to see what happens when a right brace occurs
-in a math formula. Two special cases are simplified here: Braces are effectively
-removed when they surround a single Ord without sub/superscripts, or when they
-surround an accent that is the nucleus of an Ord atom.
-
- at c
-void close_math_group(pointer p)
-{
-    int old_style = m_style;
-    unsave_math();
-
-    decr(save_ptr);
-    assert(saved_type(0) == saved_math);
-    type(saved_value(0)) = sub_mlist_node;
-    p = fin_mlist(null);
-    math_list(saved_value(0)) = p;
-    if (p != null) {
-        if (vlink(p) == null) {
-            if (type(p) == simple_noad && subtype(p) == ord_noad_type) {
-                if (subscr(p) == null && supscr(p) == null) {
-                    type(saved_value(0)) = type(nucleus(p));
-                    if (type(nucleus(p)) == math_char_node) {
-                        math_fam(saved_value(0)) = math_fam(nucleus(p));
-                        math_character(saved_value(0)) =
-                            math_character(nucleus(p));
-                    } else {
-                        math_list(saved_value(0)) = math_list(nucleus(p));
-                        math_list(nucleus(p)) = null;
-                    }
-                    delete_attribute_ref(node_attr(saved_value(0)));
-                    node_attr(saved_value(0)) = node_attr(nucleus(p));
-                    node_attr(nucleus(p)) = null;
-                    flush_node(p);
-                }
-            } else if (type(p) == accent_noad) {
-                if (saved_value(0) == nucleus(tail)) {
-                    if (type(tail) == simple_noad
-                        && subtype(tail) == ord_noad_type) {
-                        pointer q = head;
-                        while (vlink(q) != tail)
-                            q = vlink(q);
-                        vlink(q) = p;
-                        nucleus(tail) = null;
-                        subscr(tail) = null;
-                        supscr(tail) = null;
-                        delete_attribute_ref(node_attr(p));
-                        node_attr(p) = node_attr(tail);
-                        node_attr(tail) = null;
-                        flush_node(tail);
-                        tail = p;
-                    }
-                }
-            }
-        }
-    }
-    if (vlink(saved_value(0)) > 0) {
-        pointer q;
-        q = new_node(math_char_node, 0);
-        nucleus(vlink(saved_value(0))) = q;
-        vlink(saved_value(0)) = null;
-        saved_value(0) = q;
-        (void) scan_math(saved_value(0), old_style);
-        /* restart */
-    }
-}
-
-@ We have dealt with all constructions of math mode except `\.{\\left}' and
-`\.{\\right}', so the picture is completed by the following sections of
-the program. The |middle| feature of eTeX allows one ore several \.{\\middle}
-delimiters to appear between \.{\\left} and \.{\\right}.
-
- at c
-void math_left_right(void)
-{
-    halfword t;      /* |left_noad_side| .. |right_noad_side| */
-    pointer p;       /* new noad */
-    pointer q;       /* resulting mlist */
-    pointer r;       /* temporary */
-    halfword ht = 0;
-    halfword dp = 0;
-    halfword options = 0;
-    halfword type = -1 ;
-    t = cur_chr;
-
-    if (t > 10) {
-        /* we have \Uleft \Uright \Umiddle */
-        t = t - 10;
-        while (1) {
-            if (scan_keyword("height")) {
-                scan_dimen(false,false,false);
-                ht = cur_val ;
-            } else if (scan_keyword("depth")) {
-                scan_dimen(false,false,false);
-                dp = cur_val ;
-            } else if (scan_keyword("axis")) {
-                options = options | noad_option_axis ;
-            } else if (scan_keyword("noaxis")) {
-                options = options | noad_option_no_axis ;
-            } else if (scan_keyword("exact")) {
-                options = options | noad_option_exact ;
-            } else if (scan_keyword("class")) {
-                scan_int();
-                type = cur_val ;
-            } else {
-                break;
-            }
-        }
-    }
-
-    if ((t != no_noad_side) && (t != left_noad_side) && (cur_group != math_left_group)) {
-        if (cur_group == math_shift_group) {
-            scan_delimiter(null, no_mathcode);
-            if (t == middle_noad_side) {
-                const char *hlp[] = {
-                    "I'm ignoring a \\middle that had no matching \\left.",
-                    NULL
-                };
-                tex_error("Extra \\middle", hlp);
-            } else {
-                const char *hlp[] = {
-                    "I'm ignoring a \\right that had no matching \\left.",
-                    NULL
-                };
-                tex_error("Extra \\right", hlp);
-            }
-        } else {
-            off_save();
-        }
-    } else {
-        p = new_noad();
-        type(p) = fence_noad;
-        subtype(p) = (quarterword) t;
-        r = new_node(delim_node, 0);
-        delimiter(p) = r;
-
-        delimiterheight(p) = ht;
-        delimiterdepth(p) = dp;
-        delimiteroptions(p) = options;
-        delimiterclass(p) = type;
-        delimiteritalic(p) = 0;
-
-        scan_delimiter(delimiter(p), no_mathcode);
-
-        if (t == no_noad_side) {
-            tail_append(new_noad());
-            subtype(tail) = inner_noad_type;
-            r = new_node(sub_mlist_node, 0);
-            nucleus(tail) = r;
-            math_list(nucleus(tail)) = p;
-            return ;
-        }
-
-        if (t == left_noad_side) {
-            q = p;
-        } else {
-            q = fin_mlist(p);
-            unsave_math();
-        }
-        if (t != right_noad_side) {
-            push_math(math_left_group, m_style);
-            vlink(head) = q;
-            tail = p;
-            delim_par = p;
-        } else {
-            tail_append(new_noad());
-            subtype(tail) = inner_noad_type;
-            r = new_node(sub_mlist_node, 0);
-            nucleus(tail) = r;
-            math_list(nucleus(tail)) = q;
-        }
-    }
-}
-
-@ \TeX\ gets to the following part of the program when
-the first `\.\$' ending a display has been scanned.
-
- at c
-static void check_second_math_shift(void)
-{
-    get_x_token();
-    if (cur_cmd != math_shift_cmd) {
-        const char *hlp[] = {
-            "The `$' that I just saw supposedly matches a previous `$$'.",
-            "So I shall assume that you typed `$$' both times.",
-            NULL
-        };
-        back_error("Display math should end with $$", hlp);
-    }
-}
-
-static void check_display_math_end(void)
-{
-    if (cur_chr != cramped_display_style) {
-        const char *hlp[] = {
-            "I shall assume that you typed that.",
-            NULL
-        };
-        tex_error("Display math should end with \\Ustopdisplaymath", hlp);
-    }
-}
-
-static void check_inline_math_end(void)
-{
-    if (cur_chr != cramped_text_style) {
-        const char *hlp[] = {
-            "I shall assume that you typed that.",
-            NULL
-        };
-        tex_error("Inline math should end with \\Ustopmath", hlp);
-    }
-}
-
-@ @c
-static void resume_after_display(void)
-{
-    if (cur_group != math_shift_group)
-        confusion("display");
-    unsave_math();
-    prev_graf_par = prev_graf_par + 3;
-    push_nest();
-    mode = hmode;
-    space_factor_par = 1000;
-    /* this needs to be intercepted in the display math start ! */
-    tail_append(make_local_par_node(penalty_par_code));
-    get_x_token();
-    if (cur_cmd != spacer_cmd)
-        back_input();
-    if (nest_ptr == 1) {
-        normal_page_filter(after_display);
-        build_page();
-    }
-}
-
-@  The fussiest part of math mode processing occurs when a displayed formula is
-being centered and placed with an optional equation number.
-
-At this time we are in vertical mode (or internal vertical mode).
-
-  |p| points to the mlist for the formula.
-  |a| is either |null| or it points to a box containing the equation number.
-  |l| is true if there was an \.{\\leqno}/ (so |a| is a horizontal box).
-
- at c
-#define inject_display_skip_before(g) \
-    switch (display_skip_mode_par) { \
-        case 0 : /* normal tex */ \
-            tail_append(new_param_glue(g)); \
-            break;\
-        case 1 : /* always */ \
-            tail_append(new_param_glue(g)); \
-            break; \
-        case 2 : /* non-zero */ \
-            if (g != 0 && ! glue_is_zero(glue_par(g))) \
-                tail_append(new_param_glue(g)); \
-            break; \
-        case 3: /* ignore */ \
-            break; \
-    }
-
-#define inject_display_skip_after(g) \
-    switch (display_skip_mode_par) { \
-        case 0 : /* normal tex */ \
-            if (g != 0 && glue_is_positive(glue_par(g))) \
-                tail_append(new_param_glue(g)); \
-            break; \
-        case 1 : /* always */ \
-            tail_append(new_param_glue(g)); \
-            break; \
-        case 2 : /* non-zero */ \
-            if (g != 0 && ! glue_is_zero(glue_par(g))) \
-                tail_append(new_param_glue(g)); \
-            break; \
-        case 3: /* ignore */ \
-            break; \
-    }
-
-static void finish_displayed_math(boolean l, pointer eqno_box, pointer p)
-{
-    pointer eq_box;             /* box containing the equation */
-    scaled eq_w;                /* width of the equation */
-    scaled line_w;              /* width of the line */
-    scaled eqno_w;              /* width of equation number */
-    scaled eqno_w2;             /* width of equation number plus space to separate from equation */
-    scaled line_s;              /* move the line right this much */
-    scaled d;                   /* displacement of equation in the line */
-    small_number g1, g2;        /* glue parameter codes for before and after */
-    pointer r,s;                /* kern nodes used to position the display */
-    pointer t;                  /* tail of adjustment list */
-    pointer pre_t;              /* tail of pre-adjustment list */
-    boolean swap_dir;           /* true if the math and surrounding text dirs are opposed */
-    scaled eqno_width;
-    swap_dir = (pre_display_direction_par < 0 ? true : false );
-    if (eqno_box != null && swap_dir)
-        l = !l;
-    adjust_tail = adjust_head;
-    pre_adjust_tail = pre_adjust_head;
-    eq_box = hpack(p, 0, additional, -1);
-    subtype(eq_box) = equation_list; /* new */
-    build_attribute_list(eq_box);
-    p = list_ptr(eq_box);
-    t = adjust_tail;
-    adjust_tail = null;
-    pre_t = pre_adjust_tail;
-    pre_adjust_tail = null;
-    eq_w = width(eq_box);
-    line_w = display_width_par;
-    line_s = display_indent_par;
-    if (eqno_box == null) {
-        eqno_w = 0;
-        eqno_width = 0;
-        eqno_w2 = 0;
-    } else {
-        eqno_w = width(eqno_box);
-        eqno_width = eqno_w;
-        eqno_w2 = eqno_w + round_xn_over_d(math_eqno_gap_step_par, get_math_quad_style(text_style), 1000);
-        subtype(eqno_box) = equation_number_list; /* new */
-     /* build_attribute_list(eqno_box); */ /* probably already set */
-   }
-    if (eq_w + eqno_w2 > line_w) {
-        /* The user can force the equation number to go on a separate line
-           by causing its width to be zero. */
-        if ((eqno_w != 0) && ((eq_w - total_shrink[normal] + eqno_w2 <= line_w)
-                || (total_shrink[sfi] != 0)
-                || (total_shrink[fil] != 0)
-                || (total_shrink[fill] != 0)
-                || (total_shrink[filll] != 0))) {
-            list_ptr(eq_box) = null;
-            flush_node(eq_box);
-            eq_box = hpack(p, line_w - eqno_w2, exactly, -1);
-            subtype(eq_box) = equation_list; /* new */
-            build_attribute_list(eq_box);
-        } else {
-            eqno_w = 0;
-            if (eq_w > line_w) {
-                list_ptr(eq_box) = null;
-                flush_node(eq_box);
-                eq_box = hpack(p, line_w, exactly, -1);
-                subtype(eq_box) = equation_list; /* new */
-                build_attribute_list(eq_box);
-            }
-        }
-        eq_w = width(eq_box);
-    }
-    /* We try first to center the display without regard to the existence of
-       the equation number. If that would make it too close (where ``too close''
-       means that the space between display and equation number is less than the
-       width of the equation number), we either center it in the remaining space
-       or move it as far from the equation number as possible. The latter alternative
-       is taken only if the display begins with glue, since we assume that the
-       user put glue there to control the spacing precisely.
-     */
-    d = half(line_w - eq_w);
-    if ((eqno_w > 0) && (d < 2 * eqno_w)) {     /* too close */
-        d = half(line_w - eq_w - eqno_w);
-        if (p != null)
-            if (!is_char_node(p))
-                if (type(p) == glue_node)
-                    d = 0;
-    }
-
-    tail_append(new_penalty(pre_display_penalty_par));
-    if ((d + line_s <= pre_display_size_par) || l) {        /* not enough clearance */
-        g1 = above_display_skip_code;
-        g2 = below_display_skip_code;
-    } else {
-        g1 = above_display_short_skip_code;
-        g2 = below_display_short_skip_code;
-    }
-
-    /* If the equation number is set on a line by itself, either before or
-       after the formula, we append an infinite penalty so that no page break will
-       separate the display from its number; and we use the same size and
-       displacement for all three potential lines of the display, even though
-       `\.{\\parshape}' may specify them differently.
-     */
-     /* \.{\\leqno} on a forced single line due to |width=0| */
-     /* it follows that |type(a)=hlist_node| */
-
-    if (eqno_box && l && (eqno_w == 0)) {
-     /* if (math_direction_par==dir_TLT) { */
-            shift_amount(eqno_box) = 0;
-     /* } else {                       */
-     /* }                              */
-        append_to_vlist(eqno_box,lua_key_index(equation_number));
-        tail_append(new_penalty(inf_penalty));
-    } else {
-        inject_display_skip_before(g1);
-    }
-
-    if (eqno_w != 0) {
-        r = new_kern(line_w - eq_w - eqno_w - d);
-        if (l) {
-            if (swap_dir) {
-                if (math_direction_par==dir_TLT) {
-                    /* TRT + TLT + \eqno,    (swap_dir=true,  math_direction_par=TLT, l=true)  */
-#ifdef DEBUG
-        fprintf(stderr, "\nDEBUG: CASE 1\n");
-#endif
-                    s = new_kern(width(r) + eqno_w);
-                    try_couple_nodes(eqno_box,r);
-                    try_couple_nodes(r,eq_box);
-                    try_couple_nodes(eq_box,s);
-                } else {
-                    /* TLT + TRT + \eqno,    (swap_dir=true,  math_direction_par=TRT, l=true) */
-#ifdef DEBUG
-        fprintf(stderr, "\nDEBUG: CASE 2\n");
-#endif
-                    try_couple_nodes(eqno_box,r);
-                    try_couple_nodes(r,eq_box);
-                }
-            } else {
-                if (math_direction_par==dir_TLT) {
-                    /* TLT + TLT + \leqno,   (swap_dir=false, math_direction_par=TLT, l=true) */ /* OK */
-#ifdef DEBUG
-        fprintf(stderr, "\nDEBUG: CASE 3\n");
-#endif
-                    s = new_kern(width(r) + eqno_w);
-                } else {
-                    /* TRT + TRT + \leqno,    (swap_dir=false, math_direction_par=TRT, l=true) */
-#ifdef DEBUG
-        fprintf(stderr, "\nDEBUG: CASE 4\n");
-#endif
-                    s = new_kern(width(r));
-                }
-                try_couple_nodes(eqno_box,r);
-                try_couple_nodes(r,eq_box);
-                try_couple_nodes(eq_box,s);
-            }
-            eq_box = eqno_box;
-        } else {
-            if (swap_dir) {
-                if (math_direction_par==dir_TLT) {
-                    /* TRT + TLT + \leqno,   (swap_dir=true,  math_direction_par=TLT, l=false) */
-#ifdef DEBUG
-        fprintf(stderr, "\nDEBUG: CASE 5\n");
-#endif
-                } else {
-                    /* TLT + TRT + \leqno,   (swap_dir=true,  math_direction_par=TRT, l=false) */
-#ifdef DEBUG
-        fprintf(stderr, "\nDEBUG: CASE 6\n");
-#endif
-                }
-                try_couple_nodes(eq_box,r);
-                try_couple_nodes(r,eqno_box);
-            } else {
-                if (math_direction_par==dir_TLT) {
-                    /*  TLT + TLT + \eqno,    (swap_dir=false, math_direction_par=TLT, l=false) */ /* OK */
-#ifdef DEBUG
-        fprintf(stderr, "\nDEBUG: CASE 7\n");
-#endif
-                    s = new_kern(d);
-                } else {
-                    /* TRT + TRT + \eqno,   (swap_dir=false, math_direction_par=TRT, l=false) */
-#ifdef DEBUG
-        fprintf(stderr, "\nDEBUG: CASE 8\n");
-#endif
-                    s = new_kern(width(r) + eqno_w);
-                }
-                try_couple_nodes(s,eq_box);
-                try_couple_nodes(eq_box,r);
-                try_couple_nodes(r,eqno_box);
-                eq_box = s;
-            }
-        }
-        eq_box = hpack(eq_box, 0, additional, -1);
-        subtype(eq_box) = equation_list; /* new */
-        build_attribute_list(eq_box);
-        shift_amount(eq_box) = line_s;
-    } else {
-        shift_amount(eq_box) = line_s + d;
-    }
-/* check for prev: */
-    append_to_vlist(eq_box,lua_key_index(equation));
-
-    if ((eqno_box != null) && (eqno_w == 0) && !l) {
-        tail_append(new_penalty(inf_penalty));
-     /* if (math_direction_par==dir_TLT) { */
-            shift_amount(eqno_box) = line_s + line_w - eqno_width ;
-     /* } else {                       */
-     /* }                              */
-        append_to_vlist(eqno_box,lua_key_index(equation_number));
-        g2 = 0;
-    }
-    if (t != adjust_head) {     /* migrating material comes after equation number */
-        vlink(tail) = vlink(adjust_head);
-        /* needs testing */
-        alink(adjust_tail) = alink(tail);
-        tail = t;
-    }
-    if (pre_t != pre_adjust_head) {
-        vlink(tail) = vlink(pre_adjust_head);
-        /* needs testing */
-        alink(pre_adjust_tail) = alink(tail);
-        tail = pre_t;
-    }
-    tail_append(new_penalty(post_display_penalty_par));
-    inject_display_skip_after(g2);
-    resume_after_display();
-}
-
-@ @c
-void after_math(void)
-{
-    int m;                      /* |mmode| or |-mmode| */
-    pointer p;                  /* the formula */
-    pointer a = null;           /* box containing equation number */
-    boolean l = false;          /* `\.{\\leqno}' instead of `\.{\\eqno}' */
-    m = mode;
-    p = fin_mlist(null);        /* this pops the nest */
-    if (cur_cmd == math_shift_cs_cmd &&
-        (cur_chr == text_style || cur_chr == display_style)) {
-        you_cant();
-    }
-    if (mode == -m) {           /* end of equation number */
-        if (cur_cmd == math_shift_cmd) {
-            check_second_math_shift();
-        } else {
-            check_display_math_end();
-        }
-        run_mlist_to_hlist(p, false, text_style);
-        a = hpack(vlink(temp_head), 0, additional, -1);
-        build_attribute_list(a);
-        unsave_math();
-        decr(save_ptr);         /* now |cur_group=math_shift_group| */
-        assert(saved_type(0) == saved_eqno);
-        if (saved_value(0) == 1)
-            l = true;
-        m = mode;
-        p = fin_mlist(null);
-
-    }
-    if (m < 0) {
-        /* The |unsave| is done after everything else here; hence an appearance of
-           `\.{\\mathsurround}' inside of `\.{\$...\$}' affects the spacing at these
-           particular \.\$'s. This is consistent with the conventions of
-           `\.{\$\$...\$\$}', since `\.{\\abovedisplayskip}' inside a display affects the
-           space above that display.
-         */
-        if (cur_cmd == math_shift_cs_cmd) {
-            check_inline_math_end();
-        }
-        tail_append(new_math(math_surround_par, before));
-        /* begin mathskip code */
-        switch (math_skip_mode) {
-            case 0 :
-                /* obey mathsurround when zero glue */
-                if (! glue_is_zero(math_skip_par)) {
-                    copy_glue_values(tail,math_skip_par);
-                    surround(tail) = 0;
-                }
-                break ;
-            case 1 :
-                /* always left */
-            case 3 :
-                /* always both */
-            case 6 :
-                /* only when skip */
-                copy_glue_values(tail,math_skip_par);
-                surround(tail) = 0;
-                break ;
-            case 2 :
-                /* only right */
-                surround(tail) = 0;
-                break ;
-            case 4 :
-                /* ignore, obey marthsurround */
-                break ;
-            case 5:
-                /* all spacing disabled */
-                surround(tail) = 0;
-                break ;
-        }
-        /* end mathskip code */
-        if (dir_math_save) {
-            tail_append(new_dir(math_direction_par));
-        }
-        run_mlist_to_hlist(p, (mode > 0), text_style);
-        vlink(tail) = vlink(temp_head);
-        while (vlink(tail) != null) {
-            tail = vlink(tail);
-        }
-        if (dir_math_save) {
-            tail_append(new_dir(math_direction_par - dir_swap));
-        }
-        dir_math_save = false;
-        tail_append(new_math(math_surround_par, after));
-        /* begin mathskip code */
-        switch (math_skip_mode) {
-            case 0 :
-                /* obey mathsurround when zero glue */
-                if (! glue_is_zero(math_skip_par)) {
-                    copy_glue_values(tail,math_skip_par);
-                    surround(tail) = 0;
-                }
-                break ;
-            case 2 :
-                /* always right */
-            case 3 :
-                /* always both */
-            case 6 :
-                /* only when skip */
-                copy_glue_values(tail,math_skip_par);
-                surround(tail) = 0;
-                break ;
-            case 1 :
-                /* only left */
-                surround(tail) = 0;
-                break ;
-            case 4 :
-                /* ignore, obey marthsurround */
-                break ;
-            case 5:
-                /* all spacing disabled */
-                surround(tail) = 0;
-                break ;
-        }
-        /* end mathskip code */
-        space_factor_par = 1000;
-        unsave_math();
-    } else {
-        if (a == null) {
-            if (cur_cmd == math_shift_cmd) {
-                check_second_math_shift();
-            } else {
-                check_display_math_end();
-            }
-        }
-        run_mlist_to_hlist(p, false, display_style);
-        finish_displayed_math(l, a, vlink(temp_head));
-    }
-}
-
-@ When \.{\\halign} appears in a display, the alignment routines operate
-essentially as they do in vertical mode. Then the following program is
-activated, with |p| and |q| pointing to the beginning and end of the
-resulting list, and with |aux_save| holding the |prev_depth| value.
-
- at c
-void finish_display_alignment(pointer p, pointer q, halfword saved_prevdepth)
-{
-    do_assignments();
-    if (cur_cmd == math_shift_cmd) {
-        check_second_math_shift();
-    } else {
-        check_display_math_end();
-    }
-    pop_nest();
-    tail_append(new_penalty(pre_display_penalty_par));
-    inject_display_skip_before(above_display_skip_code);
-    vlink(tail) = p;
-    if (p != null)
-        tail = q;
-    tail_append(new_penalty(post_display_penalty_par));
-    inject_display_skip_after(below_display_skip_code);
-    cur_list.prev_depth_field = saved_prevdepth;
-    resume_after_display();
-}
-
-@ Interface to \.{\\Umath} and \.{\\mathstyle}
-
- at c
-void setup_math_style(void)
-{
-    pointer q;
-    tail_append(new_noad());
-    q = new_node(math_char_node, 0);
-    nucleus(tail) = q;
-    (void) scan_math_style(nucleus(tail), num_style(m_style));
-}
-
-@ @c
-void print_math_style(void)
-{
-    if (abs(mode) == mmode)
-        print_int(m_style);
-    else
-        print_int(-1);
-}
+% texmath.w
+%
+% Copyright 2008-2010 Taco Hoekwater <taco@@luatex.org>
+%
+% 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 <http://www.gnu.org/licenses/>.
+
+@ @c
+#include "ptexlib.h"
+
+@ @c
+#define mode     mode_par
+#define tail     tail_par
+#define head     head_par
+#define dir_save dirs_par
+
+/*
+
+    \mathdisplayskipmode
+
+    tex normally always inserts before and only after when larger than zero
+
+    0 = normal tex
+    1 = always
+    2 = non-zero
+    3 = ignore
+
+*/
+
+@ TODO: not sure if this is the right order
+ at c
+#define back_error(A,B) do {                    \
+    OK_to_interrupt=false;                      \
+    back_input();                               \
+    OK_to_interrupt=true;                       \
+    tex_error(A,B);                             \
+  } while (0)
+
+@ @c
+int scan_math(pointer, int);
+int scan_math_style(pointer, int);
+pointer fin_mlist(pointer);
+
+@ When \TeX\ reads a formula that is enclosed between \.\$'s, it constructs an
+{\sl mlist}, which is essentially a tree structure representing that
+formula.  An mlist is a linear sequence of items, but we can regard it as
+a tree structure because mlists can appear within mlists. For example, many
+of the entries can be subscripted or superscripted, and such ``scripts''
+are mlists in their own right.
+
+An entire formula is parsed into such a tree before any of the actual
+typesetting is done, because the current style of type is usually not
+known until the formula has been fully scanned. For example, when the
+formula `\.{\$a+b \\over c+d\$}' is being read, there is no way to tell
+that `\.{a+b}' will be in script size until `\.{\\over}' has appeared.
+
+During the scanning process, each element of the mlist being built is
+classified as a relation, a binary operator, an open parenthesis, etc.,
+or as a construct like `\.{\\sqrt}' that must be built up. This classification
+appears in the mlist data structure.
+
+After a formula has been fully scanned, the mlist is converted to an hlist
+so that it can be incorporated into the surrounding text. This conversion is
+controlled by a recursive procedure that decides all of the appropriate
+styles by a ``top-down'' process starting at the outermost level and working
+in towards the subformulas. The formula is ultimately pasted together using
+combinations of horizontal and vertical boxes, with glue and penalty nodes
+inserted as necessary.
+
+An mlist is represented internally as a linked list consisting chiefly
+of ``noads'' (pronounced ``no-adds''), to distinguish them from the somewhat
+similar ``nodes'' in hlists and vlists. Certain kinds of ordinary nodes are
+allowed to appear in mlists together with the noads; \TeX\ tells the difference
+by means of the |type| field, since a noad's |type| is always greater than
+that of a node. An mlist does not contain character nodes, hlist nodes, vlist
+nodes, math nodes or unset nodes; in particular, each mlist item appears in the
+variable-size part of |mem|, so the |type| field is always present.
+
+Each noad is five or more words long. The first word contains the
+|type| and |subtype| and |link| fields that are already so familiar to
+us; the second contains the attribute list pointer, and the third,
+fourth an fifth words are called the noad's |nucleus|, |subscr|, and
+|supscr| fields. (This use of a combined attribute list is temporary.
+Eventually, each of fields need their own list)
+
+Consider, for example, the simple formula `\.{\$x\^2\$}', which would be
+parsed into an mlist containing a single element called an |ord_noad|.
+The |nucleus| of this noad is a representation of `\.x', the |subscr| is
+empty, and the |supscr| is a representation of `\.2'.
+
+The |nucleus|, |subscr|, and |supscr| fields are further broken into
+subfields. If |p| points to a noad, and if |q| is one of its principal
+fields (e.g., |q=subscr(p)|), |q=null| indicates a field with no value (the
+corresponding attribute of noad |p| is not present). Otherwise, there are
+several possibilities for the subfields, depending on the |type| of |q|.
+
+\yskip\hang|type(q)=math_char_node| means that |math_fam(q)| refers to one of
+the sixteen font families, and |character(q)| is the number of a character
+within a font of that family, as in a character node.
+
+\yskip\hang|type(q)=math_text_char_node| is similar, but the character is
+unsubscripted and unsuperscripted and it is followed immediately by another
+character from the same font. (This |type| setting appears only
+briefly during the processing; it is used to suppress unwanted italic
+corrections.)
+
+\yskip\hang|type(q)=sub_box_node| means that |math_list(q)| points to a box
+node (either an |hlist_node| or a |vlist_node|) that should be used as the
+value of the field.  The |shift_amount| in the subsidiary box node is the
+amount by which that box will be shifted downward.
+
+\yskip\hang|type(q)=sub_mlist_node| means that |math_list(q)| points to
+an mlist; the mlist must be converted to an hlist in order to obtain
+the value of this field.
+
+\yskip\noindent In the latter case, we might have |math_list(q)=null|. This
+is not the same as |q=null|; for example, `\.{\$P\_\{\}\$}'
+and `\.{\$P\$}' produce different results (the former will not have the
+``italic correction'' added to the width of |P|, but the ``script skip''
+will be added).
+
+ at c
+static void unsave_math(void)
+{
+    unsave();
+    decr(save_ptr);
+    flush_node_list(text_dir_ptr);
+    assert(saved_type(0) == saved_textdir);
+    text_dir_ptr = saved_value(0);
+}
+
+@ Sometimes it is necessary to destroy an mlist. The following
+subroutine empties the current list, assuming that |abs(mode)=mmode|.
+
+ at c
+void flush_math(void)
+{
+    flush_node_list(vlink(head));
+    flush_node_list(incompleat_noad_par);
+    vlink(head) = null;
+    tail = head;
+    incompleat_noad_par = null;
+}
+
+@ Before we can do anything in math mode, we need fonts.
+
+ at c
+#define MATHFONTSTACK  8
+#define MATHFONTDEFAULT 0       /* == nullfont */
+
+static sa_tree math_fam_head = NULL;
+
+@ @c
+int fam_fnt(int fam_id, int size_id)
+{
+    int n = fam_id + (256 * size_id);
+    return (int) get_sa_item(math_fam_head, n).int_value;
+}
+
+void def_fam_fnt(int fam_id, int size_id, int f, int lvl)
+{
+    int n = fam_id + (256 * size_id);
+    sa_tree_item sa_value = { 0 };
+    sa_value.int_value = f;
+    set_sa_item(math_fam_head, n, sa_value, lvl);
+    fixup_math_parameters(fam_id, size_id, f, lvl);
+    if (tracing_assigns_par > 1) {
+        begin_diagnostic();
+        tprint("{assigning");
+        print_char(' ');
+        print_cmd_chr(def_family_cmd, size_id);
+        print_int(fam_id);
+        print_char('=');
+        print_font_identifier(fam_fnt(fam_id, size_id));
+        print_char('}');
+        end_diagnostic(false);
+    }
+}
+
+@ @c
+static void unsave_math_fam_data(int gl)
+{
+    sa_stack_item st;
+    if (math_fam_head->stack == NULL)
+        return;
+    while (math_fam_head->stack_ptr > 0 &&
+           abs(math_fam_head->stack[math_fam_head->stack_ptr].level)
+           >= (int) gl) {
+        st = math_fam_head->stack[math_fam_head->stack_ptr];
+        if (st.level > 0) {
+            rawset_sa_item(math_fam_head, st.code, st.value);
+            /* now do a trace message, if requested */
+            if (tracing_restores_par > 1) {
+                int size_id = st.code / 256;
+                int fam_id = st.code % 256;
+                begin_diagnostic();
+                tprint("{restoring");
+                print_char(' ');
+                print_cmd_chr(def_family_cmd, size_id);
+                print_int(fam_id);
+                print_char('=');
+                print_font_identifier(fam_fnt(fam_id, size_id));
+                print_char('}');
+                end_diagnostic(false);
+            }
+        }
+        (math_fam_head->stack_ptr)--;
+    }
+}
+
+@ and parameters
+
+ at c
+#define MATHPARAMSTACK  8
+#define MATHPARAMDEFAULT undefined_math_parameter
+
+static sa_tree math_param_head = NULL;
+
+@ @c
+void def_math_param(int param_id, int style_id, scaled value, int lvl)
+{
+    int n = param_id + (256 * style_id);
+    sa_tree_item sa_value = { 0 };
+    sa_value.int_value = (int) value;
+    set_sa_item(math_param_head, n, sa_value, lvl);
+    if (tracing_assigns_par > 1) {
+        begin_diagnostic();
+        tprint("{assigning");
+        print_char(' ');
+        print_cmd_chr(set_math_param_cmd, param_id);
+        print_cmd_chr(math_style_cmd, style_id);
+        print_char('=');
+        print_int(value);
+        print_char('}');
+        end_diagnostic(false);
+    }
+}
+
+scaled get_math_param(int param_id, int style_id)
+{
+    int n = param_id + (256 * style_id);
+    return (scaled) get_sa_item(math_param_head, n).int_value;
+}
+
+@ @c
+static void unsave_math_param_data(int gl)
+{
+    sa_stack_item st;
+    if (math_param_head->stack == NULL)
+        return;
+    while (math_param_head->stack_ptr > 0 &&
+           abs(math_param_head->stack[math_param_head->stack_ptr].level)
+           >= (int) gl) {
+        st = math_param_head->stack[math_param_head->stack_ptr];
+        if (st.level > 0) {
+            rawset_sa_item(math_param_head, st.code, st.value);
+            /* now do a trace message, if requested */
+            if (tracing_restores_par > 1) {
+                int param_id = st.code % 256;
+                int style_id = st.code / 256;
+                begin_diagnostic();
+                tprint("{restoring");
+                print_char(' ');
+                print_cmd_chr(set_math_param_cmd, param_id);
+                print_cmd_chr(math_style_cmd, style_id);
+                print_char('=');
+                print_int(get_math_param(param_id, style_id));
+                print_char('}');
+                end_diagnostic(false);
+            }
+        }
+        (math_param_head->stack_ptr)--;
+    }
+}
+
+@ saving and unsaving of both
+
+ at c
+void unsave_math_data(int gl)
+{
+    unsave_math_fam_data(gl);
+    unsave_math_param_data(gl);
+}
+
+@ Dumping and undumping
+ at c
+void dump_math_data(void)
+{
+    sa_tree_item sa_value = { 0 };
+    if (math_fam_head == NULL) {
+        sa_value.int_value = MATHFONTDEFAULT;
+        math_fam_head = new_sa_tree(MATHFONTSTACK, 1, sa_value);
+    }
+    dump_sa_tree(math_fam_head, "mathfonts");
+    if (math_param_head == NULL) {
+        sa_value.int_value = MATHPARAMDEFAULT;
+        math_param_head = new_sa_tree(MATHPARAMSTACK, 1, sa_value);
+    }
+    dump_sa_tree(math_param_head, "mathparameters");
+}
+
+void undump_math_data(void)
+{
+    math_fam_head = undump_sa_tree("mathfonts");
+    math_param_head = undump_sa_tree("mathparameters");
+}
+
+@ @c
+void initialize_math(void)
+{
+    sa_tree_item sa_value = { 0 };
+    if (math_fam_head == NULL) {
+        sa_value.int_value = MATHFONTDEFAULT;
+        math_fam_head = new_sa_tree(MATHFONTSTACK, 1, sa_value);
+    }
+    if (math_param_head == NULL) {
+        sa_value.int_value = MATHPARAMDEFAULT;
+        math_param_head = new_sa_tree(MATHPARAMSTACK, 1, sa_value);
+        initialize_math_spacing();
+    }
+    return;
+}
+
+@ Each portion of a formula is classified as Ord, Op, Bin, Rel, Ope,
+Clo, Pun, or Inn, for purposes of spacing and line breaking. An
+|ord_noad|, |op_noad|, |bin_noad|, |rel_noad|, |open_noad|, |close_noad|,
+|punct_noad|, or |inner_noad| is used to represent portions of the various
+types. For example, an `\.=' sign in a formula leads to the creation of a
+|rel_noad| whose |nucleus| field is a representation of an equals sign
+(usually |fam=0|, |character=075|).  A formula preceded by \.{\\mathrel}
+also results in a |rel_noad|.  When a |rel_noad| is followed by an
+|op_noad|, say, and possibly separated by one or more ordinary nodes (not
+noads), \TeX\ will insert a penalty node (with the current |rel_penalty|)
+just after the formula that corresponds to the |rel_noad|, unless there
+already was a penalty immediately following; and a ``thick space'' will be
+inserted just before the formula that corresponds to the |op_noad|.
+
+A noad of type |ord_noad|, |op_noad|, \dots, |inner_noad| usually
+has a |subtype=normal|. The only exception is that an |op_noad| might
+have |subtype=limits| or |no_limits|, if the normal positioning of
+limits has been overridden for this operator.
+
+A |radical_noad| also has a |left_delimiter| field, which usually
+represents a square root sign.
+
+A |fraction_noad| has a |right_delimiter| field as well as a |left_delimiter|.
+
+Delimiter fields have four subfields
+called |small_fam|, |small_char|, |large_fam|, |large_char|. These subfields
+represent variable-size delimiters by giving the ``small'' and ``large''
+starting characters, as explained in Chapter~17 of {\sl The \TeX book}.
+@:TeXbook}{\sl The \TeX book@>
+
+A |fraction_noad| is actually quite different from all other noads.
+It has |thickness|, |denominator|, and |numerator| fields instead of
+|nucleus|, |subscr|, and |supscr|. The |thickness| is a scaled value
+that tells how thick to make a fraction rule; however, the special
+value |default_code| is used to stand for the
+|default_rule_thickness| of the current size. The |numerator| and
+|denominator| point to mlists that define a fraction; we always have
+$$\hbox{|type(numerator)=type(denominator)=sub_mlist|}.$$ The
+|left_delimiter| and |right_delimiter| fields specify delimiters that will
+be placed at the left and right of the fraction. In this way, a
+|fraction_noad| is able to represent all of \TeX's operators \.{\\over},
+\.{\\atop}, \.{\\above}, \.{\\overwithdelims}, \.{\\atopwithdelims}, and
+ \.{\\abovewithdelims}.
+
+@ The |new_noad| function creates an |ord_noad| that is completely null
+
+ at c
+pointer new_noad(void)
+{
+    pointer p;
+    p = new_node(simple_noad, ord_noad_type);
+    /* all noad fields are zero after this */
+    return p;
+}
+
+@ @c
+pointer new_sub_box(pointer curbox)
+{
+    pointer p, q;
+    p = new_noad();
+    q = new_node(sub_box_node, 0);
+    nucleus(p) = q;
+    math_list(nucleus(p)) = curbox;
+    return p;
+}
+
+@ A few more kinds of noads will complete the set: An |under_noad| has its
+nucleus underlined; an |over_noad| has it overlined. An |accent_noad| places
+an accent over its nucleus; the accent character appears as
+|math_fam(accent_chr(p))| and |math_character(accent_chr(p))|. A |vcenter_noad|
+centers its nucleus vertically with respect to the axis of the formula;
+in such noads we always have |type(nucleus(p))=sub_box|.
+
+And finally, we have the |fence_noad| type, to implement
+\TeX's \.{\\left} and \.{\\right} as well as eTeX's \.{\\middle}.
+The |nucleus| of such noads is
+replaced by a |delimiter| field; thus, for example, `\.{\\left(}' produces
+a |fence_noad| such that |delimiter(p)| holds the family and character
+codes for all left parentheses. A |fence_noad| of subtype |left_noad_side|
+never appears in an mlist except as the first element, and a |fence_noad|
+with subtype |right_noad_side| never appears in an mlist
+except as the last element; furthermore, we either have both a |left_noad_side|
+and a |right_noad_side|, or neither one is present.
+
+@ Math formulas can also contain instructions like \.{\\textstyle} that
+override \TeX's normal style rules. A |style_node| is inserted into the
+data structure to record such instructions; it is three words long, so it
+is considered a node instead of a noad. The |subtype| is either |display_style|
+or |text_style| or |script_style| or |script_script_style|. The
+second and third words of a |style_node| are not used, but they are
+present because a |choice_node| is converted to a |style_node|.
+
+\TeX\ uses even numbers 0, 2, 4, 6 to encode the basic styles
+|display_style|, \dots, |script_script_style|, and adds~1 to get the
+``cramped'' versions of these styles. This gives a numerical order that
+is backwards from the convention of Appendix~G in {\sl The \TeX book\/};
+i.e., a smaller style has a larger numerical value.
+@:TeXbook}{\sl The \TeX book@>
+
+ at c
+const char *math_style_names[] = {
+    "display", "crampeddisplay",
+    "text", "crampedtext",
+    "script", "crampedscript",
+    "scriptscript", "crampedscriptscript",
+    NULL
+};
+
+const char *math_param_names[] = {
+    "quad", "axis", "operatorsize",
+    "overbarkern", "overbarrule", "overbarvgap",
+    "underbarkern", "underbarrule", "underbarvgap",
+    "radicalkern", "radicalrule", "radicalvgap",
+    "radicaldegreebefore", "radicaldegreeafter", "radicaldegreeraise",
+    "stackvgap", "stacknumup", "stackdenomdown",
+    "fractionrule", "fractionnumvgap", "fractionnumup",
+    "fractiondenomvgap", "fractiondenomdown", "fractiondelsize",
+    "limitabovevgap", "limitabovebgap", "limitabovekern",
+    "limitbelowvgap", "limitbelowbgap", "limitbelowkern",
+    "nolimitsubfactor", "nolimitsupfactor", /* bonus */
+    "underdelimitervgap", "underdelimiterbgap",
+    "overdelimitervgap", "overdelimiterbgap",
+    "subshiftdrop", "supshiftdrop", "subshiftdown",
+    "subsupshiftdown", "subtopmax", "supshiftup",
+    "supbottommin", "supsubbottommax", "subsupvgap",
+    "spaceafterscript", "connectoroverlapmin",
+    "ordordspacing", "ordopspacing", "ordbinspacing", "ordrelspacing",
+    "ordopenspacing", "ordclosespacing", "ordpunctspacing", "ordinnerspacing",
+    "opordspacing", "opopspacing", "opbinspacing", "oprelspacing",
+    "opopenspacing", "opclosespacing", "oppunctspacing", "opinnerspacing",
+    "binordspacing", "binopspacing", "binbinspacing", "binrelspacing",
+    "binopenspacing", "binclosespacing", "binpunctspacing", "bininnerspacing",
+    "relordspacing", "relopspacing", "relbinspacing", "relrelspacing",
+    "relopenspacing", "relclosespacing", "relpunctspacing", "relinnerspacing",
+    "openordspacing", "openopspacing", "openbinspacing", "openrelspacing",
+    "openopenspacing", "openclosespacing", "openpunctspacing",
+    "openinnerspacing",
+    "closeordspacing", "closeopspacing", "closebinspacing", "closerelspacing",
+    "closeopenspacing", "closeclosespacing", "closepunctspacing",
+    "closeinnerspacing",
+    "punctordspacing", "punctopspacing", "punctbinspacing", "punctrelspacing",
+    "punctopenspacing", "punctclosespacing", "punctpunctspacing",
+    "punctinnerspacing",
+    "innerordspacing", "inneropspacing", "innerbinspacing", "innerrelspacing",
+    "inneropenspacing", "innerclosespacing", "innerpunctspacing",
+    "innerinnerspacing",
+    NULL
+};
+
+@ @c
+pointer new_style(small_number s)
+{                               /* create a style node */
+    m_style = s;
+    return new_node(style_node, s);
+}
+
+@ Finally, the \.{\\mathchoice} primitive creates a |choice_node|, which
+has special subfields |display_mlist|, |text_mlist|, |script_mlist|,
+and |script_script_mlist| pointing to the mlists for each style.
+
+ at c
+static pointer new_choice(void)
+{                               /* create a choice node */
+    return new_node(choice_node, 0);    /* the |subtype| is not used */
+}
+
+@ Let's consider now the previously unwritten part of |show_node_list|
+that displays the things that can only be present in mlists; this
+program illustrates how to access the data structures just defined.
+
+In the context of the following program, |p| points to a node or noad that
+should be displayed, and the current string contains the ``recursion history''
+that leads to this point. The recursion history consists of a dot for each
+outer level in which |p| is subsidiary to some node, or in which |p| is
+subsidiary to the |nucleus| field of some noad; the dot is replaced by
+`\.\_' or `\.\^' or `\./' or `\.\\' if |p| is descended from the |subscr|
+or |supscr| or |denominator| or |numerator| fields of noads. For example,
+the current string would be `\.{.\^.\_/}' if |p| points to the |ord_noad| for
+|x| in the (ridiculous) formula
+`\.{\$\\sqrt\{a\^\{\\mathinner\{b\_\{c\\over x+y\}\}\}\}\$}'.
+
+ at c
+void display_normal_noad(pointer p);    /* forward */
+void display_fence_noad(pointer p);     /* forward */
+void display_fraction_noad(pointer p);  /* forward */
+
+void show_math_node(pointer p)
+{
+    switch (type(p)) {
+    case style_node:
+        print_cmd_chr(math_style_cmd, subtype(p));
+        break;
+    case choice_node:
+        tprint_esc("mathchoice");
+        append_char('D');
+        show_node_list(display_mlist(p));
+        flush_char();
+        append_char('T');
+        show_node_list(text_mlist(p));
+        flush_char();
+        append_char('S');
+        show_node_list(script_mlist(p));
+        flush_char();
+        append_char('s');
+        show_node_list(script_script_mlist(p));
+        flush_char();
+        break;
+    case simple_noad:
+    case radical_noad:
+    case accent_noad:
+        display_normal_noad(p);
+        break;
+    case fence_noad:
+        display_fence_noad(p);
+        break;
+    case fraction_noad:
+        display_fraction_noad(p);
+        break;
+    default:
+        tprint("Unknown node type!");
+        break;
+    }
+}
+
+@ Here are some simple routines used in the display of noads.
+
+ at c
+static void print_fam_and_char(pointer p)
+{                               /* prints family and character */
+    tprint_esc("fam");
+    print_int(math_fam(p));
+    print_char(' ');
+    print(math_character(p));
+}
+
+@ @c
+static void print_delimiter(pointer p)
+{
+    int a;
+    if (delimiteroptionset(p)) {
+        tprint(" [ ");
+        if (delimiteraxis(p))
+            tprint("axis ");
+        if (delimiternoaxis(p))
+            tprint("noaxis ");
+        if (delimiterexact(p))
+            tprint("exact ");
+        tprint("]");
+    }
+    if (delimiterheight(p)) {
+        tprint("height=");
+        print_scaled(delimiterheight(p));
+        tprint(" ");
+    }
+    if (delimiterdepth(p)) {
+        tprint("depth=");
+        print_scaled(delimiterdepth(p));
+        tprint(" ");
+    }
+    if (delimiterclass(p)) {
+        tprint("class=");
+        print_int(delimiterclass(p));
+        tprint(" ");
+    }
+    if (small_fam(p) < 0) {
+        print_int(-1);          /* this should never happen */
+    } else if (small_fam(p) < 16 && large_fam(p) < 16 &&
+               small_char(p) < 256 && large_char(p) < 256) {
+        /* traditional tex style */
+        a = small_fam(p) * 256 + small_char(p);
+        a = a * 0x1000 + large_fam(p) * 256 + large_char(p);
+        print_hex(a);
+    } else if ((large_fam(p) == 0 && large_char(p) == 0) ||
+               small_char(p) > 65535 || large_char(p) > 65535) {
+        /* modern xetex/luatex style */
+        print_hex(small_fam(p));
+        print_hex(small_char(p));
+    }
+}
+
+@ The next subroutine will descend to another level of recursion when a
+subsidiary mlist needs to be displayed. The parameter |c| indicates what
+character is to become part of the recursion history. An empty mlist is
+distinguished from a missing field, because these are not equivalent
+(as explained above).
+@^recursion@>
+
+ at c
+static void print_subsidiary_data(pointer p, ASCII_code c)
+{                               /* display a noad field */
+    if ((int) cur_length >= depth_threshold) {
+        if (p != null)
+            tprint(" []");
+    } else {
+        append_char(c);         /* include |c| in the recursion history */
+        if (p != null) {
+            switch (type(p)) {
+            case math_char_node:
+                print_ln();
+                print_current_string();
+                print_fam_and_char(p);
+                break;
+            case sub_box_node:
+                show_node_list(math_list(p));
+                break;
+            case sub_mlist_node:
+                if (math_list(p) == null) {
+                    print_ln();
+                    print_current_string();
+                    tprint("{}");
+                } else {
+                    show_node_list(math_list(p));
+                }
+                break;
+            }
+        }
+        flush_char();           /* remove |c| from the recursion history */
+    }
+}
+
+@ @c
+void display_normal_noad(pointer p)
+{
+    switch (type(p)) {
+    case simple_noad:
+        switch (subtype(p)) {
+        case ord_noad_type:
+            tprint_esc("mathord");
+            break;
+        case op_noad_type_normal:
+        case op_noad_type_limits:
+        case op_noad_type_no_limits:
+            tprint_esc("mathop");
+            if (subtype(p) == op_noad_type_limits)
+                tprint_esc("limits");
+            else if (subtype(p) == op_noad_type_no_limits)
+                tprint_esc("nolimits");
+            break;
+        case bin_noad_type:
+            tprint_esc("mathbin");
+            break;
+        case rel_noad_type:
+            tprint_esc("mathrel");
+            break;
+        case open_noad_type:
+            tprint_esc("mathopen");
+            break;
+        case close_noad_type:
+            tprint_esc("mathclose");
+            break;
+        case punct_noad_type:
+            tprint_esc("mathpunct");
+            break;
+        case inner_noad_type:
+            tprint_esc("mathinner");
+            break;
+        case over_noad_type:
+            tprint_esc("overline");
+            break;
+        case under_noad_type:
+            tprint_esc("underline");
+            break;
+        case vcenter_noad_type:
+            tprint_esc("vcenter");
+            break;
+        default:
+            tprint("<unknown noad type!>");
+            break;
+        }
+        break;
+    case radical_noad:
+        if (subtype(p) == 6)
+            tprint_esc("Udelimiterover");
+        else if (subtype(p) == 5)
+            tprint_esc("Udelimiterunder");
+        else if (subtype(p) == 4)
+            tprint_esc("Uoverdelimiter");
+        else if (subtype(p) == 3)
+            tprint_esc("Uunderdelimiter");
+        else if (subtype(p) == 2)
+            tprint_esc("Uroot");
+        else
+            tprint_esc("radical");
+        print_delimiter(left_delimiter(p));
+        if (degree(p) != null) {
+            print_subsidiary_data(degree(p), '/');
+        }
+        if (radicalwidth(p)) {
+            tprint("width=");
+            print_scaled(radicalwidth(p));
+            tprint(" ");
+        }
+        if (radicaloptionset(p)) {
+            tprint(" [ ");
+            if (radicalexact(p))
+                tprint("exact ");
+            if (radicalleft(p))
+                tprint("left ");
+            if (radicalmiddle(p))
+                tprint("middle ");
+            if (radicalright(p))
+                tprint("right ");
+            tprint("]");
+        }
+        break;
+    case accent_noad:
+       if (top_accent_chr(p) != null) {
+           if (bot_accent_chr(p) != null) {
+               tprint_esc("Umathaccent both");
+           } else {
+               tprint_esc("Umathaccent");
+           }
+        } else if (bot_accent_chr(p) != null) {
+            tprint_esc("Umathaccent bottom");
+        } else {
+            tprint_esc("Umathaccent overlay");
+        }
+        if (accentfraction(p)) {
+            tprint(" fraction=");
+            print_int(accentfraction(p));
+            tprint(" ");
+        }
+        switch (subtype(p)) {
+            case 0:
+                if (top_accent_chr(p) != null) {
+                    if (bot_accent_chr(p) != null) {
+                        print_fam_and_char(top_accent_chr(p));
+                        print_fam_and_char(bot_accent_chr(p));
+                    } else {
+                        print_fam_and_char(top_accent_chr(p));
+                    }
+                } else if (bot_accent_chr(p) != null) {
+                    print_fam_and_char(bot_accent_chr(p));
+                } else {
+                    print_fam_and_char(overlay_accent_chr(p));
+                }
+                break;
+            case 1:
+                if (top_accent_chr(p) != null) {
+                    tprint(" fixed ");
+                    print_fam_and_char(top_accent_chr(p));
+                    if (bot_accent_chr(p) != null) {
+                        print_fam_and_char(bot_accent_chr(p));
+                    }
+                } else {
+                    confusion("display_accent_noad");
+                }
+                break;
+            case 2:
+                if (bot_accent_chr(p) != null) {
+                    if (top_accent_chr(p) != null) {
+                        print_fam_and_char(top_accent_chr(p));
+                    }
+                    tprint(" fixed ");
+                    print_fam_and_char(bot_accent_chr(p));
+                } else{
+                    confusion("display_accent_noad");
+                }
+                break;
+            case 3:
+                if (top_accent_chr(p) != null && bot_accent_chr(p) != null) {
+                    tprint(" fixed ");
+                    print_fam_and_char(top_accent_chr(p));
+                    tprint(" fixed ");
+                    print_fam_and_char(bot_accent_chr(p));
+                } else {
+                    confusion("display_accent_noad");
+                }
+                break;
+            }
+        break;
+    }
+    print_subsidiary_data(nucleus(p), '.');
+    print_subsidiary_data(supscr(p), '^');
+    print_subsidiary_data(subscr(p), '_');
+}
+
+@ @c
+void display_fence_noad(pointer p)
+{
+    if (subtype(p) == right_noad_side)
+        tprint_esc("right");
+    else if (subtype(p) == left_noad_side)
+        tprint_esc("left");
+    else
+        tprint_esc("middle");
+    print_delimiter(delimiter(p));
+}
+
+@ @c
+void display_fraction_noad(pointer p)
+{
+    tprint_esc("fraction, thickness ");
+    if (thickness(p) == default_code)
+        tprint("= default");
+    else
+        print_scaled(thickness(p));
+    if ((left_delimiter(p) != null) &&
+        ((small_fam(left_delimiter(p)) != 0) ||
+         (small_char(left_delimiter(p)) != 0) ||
+         (large_fam(left_delimiter(p)) != 0) ||
+         (large_char(left_delimiter(p)) != 0))) {
+        tprint(", left-delimiter ");
+        print_delimiter(left_delimiter(p));
+    }
+    if ((right_delimiter(p) != null) &&
+        ((small_fam(right_delimiter(p)) != 0) ||
+         (small_char(right_delimiter(p)) != 0) ||
+         (large_fam(right_delimiter(p)) != 0) ||
+         (large_char(right_delimiter(p)) != 0))) {
+        tprint(", right-delimiter ");
+        print_delimiter(right_delimiter(p));
+    }
+    print_subsidiary_data(numerator(p), '\\');
+    print_subsidiary_data(denominator(p), '/');
+}
+
+@ The routines that \TeX\ uses to create mlists are similar to those we have
+just seen for the generation of hlists and vlists. But it is necessary to
+make ``noads'' as well as nodes, so the reader should review the
+discussion of math mode data structures before trying to make sense out of
+the following program.
+
+Here is a little routine that needs to be done whenever a subformula
+is about to be processed. The parameter is a code like |math_group|.
+
+ at c
+static void new_save_level_math(group_code c)
+{
+    set_saved_record(0, saved_textdir, 0, text_dir_ptr);
+    text_dir_ptr = new_dir(math_direction_par);
+    incr(save_ptr);
+    new_save_level(c);
+    eq_word_define(int_base + body_direction_code, math_direction_par);
+    eq_word_define(int_base + par_direction_code, math_direction_par);
+    eq_word_define(int_base + text_direction_code, math_direction_par);
+}
+
+@ @c
+static void push_math(group_code c, int mstyle)
+{
+    if (math_direction_par != text_direction_par)
+        dir_math_save = true;
+    push_nest();
+    mode = -mmode;
+    incompleat_noad_par = null;
+    m_style = mstyle;
+    new_save_level_math(c);
+}
+
+@ @c
+static void enter_ordinary_math(void)
+{
+    push_math(math_shift_group, text_style);
+    eq_word_define(int_base + cur_fam_code, -1);
+    if (every_math_par != null)
+        begin_token_list(every_math_par, every_math_text);
+}
+
+@ @c
+void enter_display_math(void);
+
+@ We get into math mode from horizontal mode when a `\.\$' (i.e., a
+|math_shift| character) is scanned. We must check to see whether this
+`\.\$' is immediately followed by another, in case display math mode is
+called for.
+
+ at c
+void init_math(void)
+{
+    if (cur_cmd == math_shift_cmd) {
+        get_token();            /* |get_x_token| would fail on \.{\\ifmmode}\thinspace! */
+        if ((cur_cmd == math_shift_cmd) && (mode > 0)) {
+            enter_display_math();
+        } else {
+            back_input();
+            enter_ordinary_math();
+        }
+    } else if (cur_cmd == math_shift_cs_cmd && cur_chr == display_style && (mode > 0)) {
+        enter_display_math();
+    } else if (cur_cmd == math_shift_cs_cmd && cur_chr == text_style) {
+        enter_ordinary_math();
+    } else {
+        you_cant();
+    }
+}
+
+@ We get into ordinary math mode from display math mode when `\.{\\eqno}' or
+`\.{\\leqno}' appears. In such cases |cur_chr| will be 0 or~1, respectively;
+the value of |cur_chr| is placed onto |save_stack| for safe keeping.
+
+@ When \TeX\ is in display math mode, |cur_group=math_shift_group|,
+so it is not necessary for the |start_eq_no| procedure to test for
+this condition.
+
+ at c
+void start_eq_no(void)
+{
+    set_saved_record(0, saved_eqno, 0, cur_chr);
+    incr(save_ptr);
+    enter_ordinary_math();
+}
+
+@ Subformulas of math formulas cause a new level of math mode to be entered,
+on the semantic nest as well as the save stack. These subformulas arise in
+several ways: (1)~A left brace by itself indicates the beginning of a
+subformula that will be put into a box, thereby freezing its glue and
+preventing line breaks. (2)~A subscript or superscript is treated as a
+subformula if it is not a single character; the same applies to
+the nucleus of things like \.{\\underline}. (3)~The \.{\\left} primitive
+initiates a subformula that will be terminated by a matching \.{\\right}.
+The group codes placed on |save_stack| in these three cases are
+|math_group|, |math_group|, and |math_left_group|, respectively.
+
+Here is the code that handles case (1); the other cases are not quite as
+trivial, so we shall consider them later.
+
+ at c
+void math_left_brace(void)
+{
+    pointer q;
+    tail_append(new_noad());
+    q = new_node(math_char_node, 0);
+    nucleus(tail) = q;
+    back_input();
+    (void) scan_math(nucleus(tail), m_style);
+}
+
+@ If the inline directions of \.{\\pardir} and \.{\\mathdir} are
+opposite, then this function will return true. Discovering that fact
+is somewhat odd because it needs traversal of the |save_stack|.
+The occurance of displayed equations is weird enough that this is
+probably still better than having yet another field in the |input_stack|
+structures.
+
+None of this makes much sense if the inline direction of either one of
+\.{\\pardir} or \.{\\mathdir} is vertical, but in that case the current
+math machinery is ill suited anyway so I do not bother to test that.
+
+ at c
+static boolean math_and_text_reversed_p(void)
+{
+    int i = save_ptr - 1;
+    while (save_type(i) != level_boundary)
+        i--;
+    while (i < save_ptr) {
+        if (save_type(i) == restore_old_value &&
+            save_value(i) == int_base + par_direction_code) {
+            if (textdir_opposite(math_direction_par, save_value(i - 1)))
+                return true;
+        }
+        i++;
+    }
+    return false;
+}
+
+@ When we enter display math mode, we need to call |line_break| to
+process the partial paragraph that has just been interrupted by the
+display. Then we can set the proper values of |display_width| and
+|display_indent| and |pre_display_size|.
+
+ at c
+void enter_display_math(void)
+{
+    scaled w;                   /* new or partial |pre_display_size| */
+    scaled l;                   /* new |display_width| */
+    scaled s;                   /* new |display_indent| */
+    pointer p;
+    int n;                      /* scope of paragraph shape specification */
+    if (head == tail ||         /* `\.{\\noindent\$\$}' or `\.{\$\${ }\$\$}' */
+        (vlink(head) == tail && /* the 2nd of \.{\$\${ }\$\$} \.{\$\${ }\$\$} */
+         type(tail) == local_par_node && vlink(tail) == null)) {
+        if (vlink(head) == tail) {
+            /* bug \#270: |resume_after_display| inserts a |local_par_node|, but if
+               there is another display immediately following, we have to get rid
+               of that node */
+            flush_node(tail);
+        }
+        pop_nest();
+        w = -max_dimen;
+    } else {
+        line_break(true, math_shift_group);
+        w = actual_box_width(just_box, x_over_n(quad(get_cur_font()),1000) * math_pre_display_gap_factor_par);
+    }
+    /* now we are in vertical mode, working on the list that will contain the display */
+    /* A displayed equation is considered to be three lines long, so we
+       calculate the length and offset of line number |prev_graf+2|. */
+    if (par_shape_par_ptr == null) {
+        if ((hang_indent_par != 0) && (((hang_after_par >= 0) && (prev_graf_par + 2 > hang_after_par)) || (prev_graf_par + 1 < -hang_after_par))) {
+            halfword used_hang_indent = swap_hang_indent(hang_indent_par);
+            l = hsize_par - abs(used_hang_indent);
+            if (used_hang_indent > 0)
+                s = used_hang_indent;
+            else
+                s = 0;
+        } else {
+            l = hsize_par;
+            s = 0;
+        }
+    } else {
+        n = vinfo(par_shape_par_ptr + 1);
+        if (prev_graf_par + 2 >= n)
+            p = par_shape_par_ptr + 2 * n + 1;
+        else
+            p = par_shape_par_ptr + 2 * (prev_graf_par + 2) + 1;
+        s = varmem[(p - 1)].cint;
+        l = varmem[p].cint;
+        s = swap_parshape_indent(s,l);
+    }
+
+    push_math(math_shift_group, display_style);
+    mode = mmode;
+    eq_word_define(int_base + cur_fam_code, -1);
+    eq_word_define(dimen_base + pre_display_size_code, w);
+    eq_word_define(dimen_base + display_width_code, l);
+    eq_word_define(dimen_base + display_indent_code, s);
+    eq_word_define(int_base + pre_display_direction_code, (math_and_text_reversed_p() ? -1 : 0));
+    if (every_display_par != null)
+        begin_token_list(every_display_par, every_display_text);
+    if (nest_ptr == 1) {
+        checked_page_filter(before_display);
+        build_page();
+    }
+}
+
+@ The next routine parses all variations of a delimiter code. The |extcode|
+ tells what syntax form to use (\TeX, XeTeX, XeTeXnum, ...) , the
+ |doclass| tells whether or not read a math class also (for \.{\\delimiter} c.s.).
+ (the class is passed on for conversion to \.{\\mathchar}).
+
+ at c
+static delcodeval do_scan_extdef_del_code(int extcode, boolean doclass)
+{
+    const char *hlp[] = {
+        "I'm going to use 0 instead of that illegal code value.",
+        NULL
+    };
+    delcodeval d;
+    int mcls = 0, msfam = 0, mschr = 0, mlfam = 0, mlchr = 0;
+    if (extcode == tex_mathcode) {      /* \.{\\delcode}, this is the easiest */
+        scan_int();
+        /*  "MFCCFCC or "FCCFCC */
+        if (doclass) {
+            mcls = (cur_val / 0x1000000);
+            cur_val = (cur_val & 0xFFFFFF);
+        }
+        if (cur_val > 0xFFFFFF) {
+            tex_error("Invalid delimiter code", hlp);
+            cur_val = 0;
+        }
+        msfam = (cur_val / 0x100000);
+        mschr = (cur_val % 0x100000) / 0x1000;
+        mlfam = (cur_val & 0xFFF) / 0x100;
+        mlchr = (cur_val % 0x100);
+    } else if (extcode == umath_mathcode) {     /* \.{\\Udelcode} */
+        /* <0-7>,<0-0xFF>,<0-0x10FFFF>  or <0-0xFF>,<0-0x10FFFF> */
+        if (doclass) {
+            scan_int();
+            mcls = cur_val;
+        }
+        scan_int();
+        msfam = cur_val;
+        scan_char_num();
+        mschr = cur_val;
+        if (msfam < 0 || msfam > 255) {
+            tex_error("Invalid delimiter code", hlp);
+            msfam = 0;
+            mschr = 0;
+        }
+        mlfam = 0;
+        mlchr = 0;
+    } else if (extcode == umathnum_mathcode) {  /* \.{\\Udelcodenum} */
+        /* "FF<21bits> */
+        /* the largest numeric value is $2^29-1$, but
+           the top of bit 21 can't be used as it contains invalid USV's
+         */
+        if (doclass) {          /* such a primitive doesn't exist */
+            confusion("umathnum_mathcode");
+        }
+        scan_int();
+        msfam = (cur_val / 0x200000);
+        mschr = cur_val & 0x1FFFFF;
+        if (msfam < 0 || msfam > 255 || mschr > 0x10FFFF) {
+            tex_error("Invalid delimiter code", hlp);
+            msfam = 0;
+            mschr = 0;
+        }
+        mlfam = 0;
+        mlchr = 0;
+    } else {
+        /* something's gone wrong */
+        confusion("unknown_extcode");
+    }
+    d.class_value = mcls;
+    d.small_family_value = msfam;
+    d.small_character_value = mschr;
+    d.large_family_value = mlfam;
+    d.large_character_value = mlchr;
+    return d;
+}
+
+@ @c
+void scan_extdef_del_code(int level, int extcode)
+{
+    delcodeval d;
+    int p;
+    scan_char_num();
+    p = cur_val;
+    scan_optional_equals();
+    d = do_scan_extdef_del_code(extcode, false);
+    set_del_code(p, d.small_family_value, d.small_character_value,
+                 d.large_family_value, d.large_character_value,
+                 (quarterword) (level));
+}
+
+@ @c
+mathcodeval scan_mathchar(int extcode)
+{
+    char errstr[255] = { 0 };
+    const char *hlp[] = {
+        "I'm going to use 0 instead of that illegal code value.",
+        NULL
+    };
+    mathcodeval d;
+    int mcls = 0, mfam = 0, mchr = 0;
+    if (extcode == tex_mathcode) {      /* \.{\\mathcode} */
+        /* "TFCC */
+        scan_int();
+        if (cur_val > 0x8000) {
+            /*
+                tex_error("Invalid math code", hlp);
+                cur_val = 0;
+            */
+            /* needed for latex: fallback to umathnum_mathcode */
+            mfam = (cur_val / 0x200000) & 0x7FF;
+            mcls = mfam % 0x08;
+            mfam = mfam / 0x08;
+            mchr = cur_val & 0x1FFFFF;
+            if (mchr > 0x10FFFF) {
+                tex_error("Invalid math code during > 0x8000 mathcode fallback", hlp);
+                mcls = 0;
+                mfam = 0;
+                mchr = 0;
+            }
+        } else {
+            if (cur_val < 0) {
+                snprintf(errstr, 255, "Bad mathchar (%d)", (int)cur_val);
+                tex_error(errstr, hlp);
+                cur_val = 0;
+            }
+            mcls = (cur_val / 0x1000);
+            mfam = ((cur_val % 0x1000) / 0x100);
+            mchr = (cur_val % 0x100);
+        }
+    } else if (extcode == umath_mathcode) {
+        /* <0-0x7> <0-0xFF> <0-0x10FFFF> */
+        scan_int();
+        mcls = cur_val;
+        scan_int();
+        mfam = cur_val;
+        scan_char_num();
+        mchr = cur_val;
+        if (mcls < 0 || mcls > 7 || mfam > 255) {
+            tex_error("Invalid math code", hlp);
+            mchr = 0;
+            mfam = 0;
+            mcls = 0;
+        }
+    } else if (extcode == umathnum_mathcode) {
+        /* "FFT<21bits> */
+        /* the largest numeric value is $2^32-1$, but
+           the top of bit 21 can't be used as it contains invalid USV's
+         */
+        /* Note: |scan_int| won't accept families 128-255 because these use bit 32 */
+        scan_int();
+        mfam = (cur_val / 0x200000) & 0x7FF;
+        mcls = mfam % 0x08;
+        mfam = mfam / 0x08;
+        mchr = cur_val & 0x1FFFFF;
+        if (mchr > 0x10FFFF) {
+            tex_error("Invalid math code", hlp);
+            mcls = 0;
+            mfam = 0;
+            mchr = 0;
+        }
+    } else {
+        /* something's gone wrong */
+        confusion("unknown_extcode");
+    }
+    d.class_value = mcls;
+    d.family_value = mfam;
+    d.character_value = mchr;
+    return d;
+}
+
+@ @c
+void scan_extdef_math_code(int level, int extcode)
+{
+    mathcodeval d;
+    int p;
+    scan_char_num();
+    p = cur_val;
+    scan_optional_equals();
+    d = scan_mathchar(extcode);
+    set_math_code(p, d.class_value,
+                  d.family_value, d.character_value, (quarterword) (level));
+}
+
+@ this reads in a delcode when actually a mathcode is needed
+ at c
+mathcodeval scan_delimiter_as_mathchar(int extcode)
+{
+    delcodeval dval;
+    mathcodeval mval;
+    dval = do_scan_extdef_del_code(extcode, true);
+    mval.class_value = dval.class_value;
+    mval.family_value = dval.small_family_value;
+    mval.character_value = dval.small_character_value;
+    return mval;
+}
+
+@ Recall that the |nucleus|, |subscr|, and |supscr| fields in a noad
+are broken down into subfields called |type| and either |math_list| or
+|(math_fam,math_character)|. The job of |scan_math| is to figure out
+what to place in one of these principal fields; it looks at the
+subformula that comes next in the input, and places an encoding of
+that subformula into a given word of |mem|.
+
+ at c
+#define get_next_nb_nr() do { get_x_token(); } while (cur_cmd==spacer_cmd||cur_cmd==relax_cmd)
+
+int scan_math_style(pointer p, int mstyle)
+{
+    get_next_nb_nr();
+    back_input();
+    scan_left_brace();
+    set_saved_record(0, saved_math, 0, p);
+    incr(save_ptr);
+    push_math(math_group, mstyle);
+    return 1;
+}
+
+int scan_math(pointer p, int mstyle)
+{
+    /* label restart,reswitch,exit; */
+    mathcodeval mval = { 0, 0, 0 };
+    assert(p != null);
+  RESTART:
+    get_next_nb_nr();
+  RESWITCH:
+    switch (cur_cmd) {
+    case letter_cmd:
+    case other_char_cmd:
+    case char_given_cmd:
+        mval = get_math_code(cur_chr);
+        if (mval.class_value == 8) {
+            /* An active character that is an |outer_call| is allowed here */
+            cur_cs = active_to_cs(cur_chr, true);
+            cur_cmd = eq_type(cur_cs);
+            cur_chr = equiv(cur_cs);
+            x_token();
+            back_input();
+            goto RESTART;
+        }
+        break;
+    case char_num_cmd:
+        scan_char_num();
+        cur_chr = cur_val;
+        cur_cmd = char_given_cmd;
+        goto RESWITCH;
+        break;
+    case math_char_num_cmd:
+        if (cur_chr == 0)
+            mval = scan_mathchar(tex_mathcode);
+        else if (cur_chr == 1)
+            mval = scan_mathchar(umath_mathcode);
+        else if (cur_chr == 2)
+            mval = scan_mathchar(umathnum_mathcode);
+        else
+            confusion("scan_math");
+        break;
+    case math_given_cmd:
+        mval = mathchar_from_integer(cur_chr, tex_mathcode);
+        break;
+    case xmath_given_cmd:
+        mval = mathchar_from_integer(cur_chr, umath_mathcode);
+        break;
+    case delim_num_cmd:
+        if (cur_chr == 0)
+            mval = scan_delimiter_as_mathchar(tex_mathcode);
+        else if (cur_chr == 1)
+            mval = scan_delimiter_as_mathchar(umath_mathcode);
+        else
+            confusion("scan_math");
+        break;
+    default:
+        /* The pointer |p| is placed on |save_stack| while a complex subformula
+           is being scanned. */
+        back_input();
+        scan_left_brace();
+        set_saved_record(0, saved_math, 0, p);
+        incr(save_ptr);
+        push_math(math_group, mstyle);
+        return 1;
+    }
+    type(p) = math_char_node;
+    math_character(p) = mval.character_value;
+    if ((mval.class_value == math_use_current_family_code) && cur_fam_par_in_range)
+        math_fam(p) = cur_fam_par;
+    else
+        math_fam(p) = mval.family_value;
+    return 0;
+}
+
+@ The |set_math_char| procedure creates a new noad appropriate to a given
+math code, and appends it to the current mlist. However, if the math code
+is sufficiently large, the |cur_chr| is treated as an active character and
+nothing is appended.
+
+ at c
+void set_math_char(mathcodeval mval)
+{
+    pointer p;                  /* the new noad */
+    if (mval.class_value == 8) {
+        /* An active character that is an |outer_call| is allowed here */
+        cur_cs = active_to_cs(cur_chr, true);
+        cur_cmd = eq_type(cur_cs);
+        cur_chr = equiv(cur_cs);
+        x_token();
+        back_input();
+    } else {
+        pointer q;
+        p = new_noad();
+        q = new_node(math_char_node, 0);
+        nucleus(p) = q;
+        math_character(nucleus(p)) = mval.character_value;
+        math_fam(nucleus(p)) = mval.family_value;
+        if (mval.class_value == math_use_current_family_code) {
+            if (cur_fam_par_in_range)
+                math_fam(nucleus(p)) = cur_fam_par;
+            subtype(p) = ord_noad_type;
+        } else {
+            switch (mval.class_value) {
+                  /* *INDENT-OFF* */
+                case 0: subtype(p) = ord_noad_type; break;
+                case 1: subtype(p) = op_noad_type_normal; break;
+                case 2: subtype(p) = bin_noad_type; break;
+                case 3: subtype(p) = rel_noad_type; break;
+                case 4: subtype(p) = open_noad_type; break;
+                case 5: subtype(p) = close_noad_type; break;
+                case 6: subtype(p) = punct_noad_type; break;
+                  /* *INDENT-ON* */
+            }
+        }
+        vlink(tail) = p;
+        tail = p;
+    }
+}
+
+@ The |math_char_in_text| procedure creates a new node representing a math char
+in text code, and appends it to the current list. However, if the math code
+is sufficiently large, the |cur_chr| is treated as an active character and
+nothing is appended.
+
+ at c
+void math_char_in_text(mathcodeval mval)
+{
+    pointer p;                  /* the new node */
+    if (mval.class_value == 8) {
+        /* An active character that is an |outer_call| is allowed here */
+        cur_cs = active_to_cs(cur_chr, true);
+        cur_cmd = eq_type(cur_cs);
+        cur_chr = equiv(cur_cs);
+        x_token();
+        back_input();
+    } else {
+        p = new_char(fam_fnt(mval.family_value, text_size), mval.character_value);
+        vlink(tail) = p;
+        tail = p;
+    }
+}
+
+@ @c
+void math_math_comp(void)
+{
+    pointer q;
+    tail_append(new_noad());
+    subtype(tail) = (quarterword) cur_chr;
+    q = new_node(math_char_node, 0);
+    nucleus(tail) = q;
+    if (cur_chr == over_noad_type)
+        (void) scan_math(nucleus(tail), cramped_style(m_style));
+    else
+        (void) scan_math(nucleus(tail), m_style);
+}
+
+@ @c
+void math_limit_switch(void)
+{
+    const char *hlp[] = {
+        "I'm ignoring this misplaced \\limits or \\nolimits command.",
+        NULL
+    };
+    if (head != tail) {
+         if (type(tail) == simple_noad &&
+             (subtype(tail) == op_noad_type_normal ||
+              subtype(tail) == op_noad_type_limits ||
+              subtype(tail) == op_noad_type_no_limits)) {
+            subtype(tail) = (quarterword) cur_chr;
+            return;
+        }
+    }
+    tex_error("Limit controls must follow a math operator", hlp);
+}
+
+@ Delimiter fields of noads are filled in by the |scan_delimiter| routine.
+The first parameter of this procedure is the |mem| address where the
+delimiter is to be placed; the second tells if this delimiter follows
+\.{\\radical} or not.
+
+ at c
+static void scan_delimiter(pointer p, int r)
+{
+    delcodeval dval = { 0, 0, 0, 0, 0 };
+    if (r == tex_mathcode) {    /* \.{\\radical} */
+        dval = do_scan_extdef_del_code(tex_mathcode, true);
+    } else if (r == umath_mathcode) {   /* \.{\\Uradical} */
+        dval = do_scan_extdef_del_code(umath_mathcode, false);
+    } else if (r == no_mathcode) {
+        get_next_nb_nr();
+        switch (cur_cmd) {
+        case letter_cmd:
+        case other_char_cmd:
+            dval = get_del_code(cur_chr);
+            break;
+        case delim_num_cmd:
+            if (cur_chr == 0)   /* \.{\\delimiter} */
+                dval = do_scan_extdef_del_code(tex_mathcode, true);
+            else if (cur_chr == 1)      /* \.{\\Udelimiter} */
+                dval = do_scan_extdef_del_code(umath_mathcode, true);
+            else
+                confusion("scan_delimiter1");
+            break;
+        default:
+            dval.small_family_value = -1;
+            break;
+        }
+    } else {
+        confusion("scan_delimiter2");
+    }
+    if (p == null)
+        return;
+    if (dval.small_family_value < 0) {
+        const char *hlp[] = {
+            "I was expecting to see something like `(' or `\\{' or",
+            "`\\}' here. If you typed, e.g., `{' instead of `\\{', you",
+            "should probably delete the `{' by typing `1' now, so that",
+            "braces don't get unbalanced. Otherwise just proceed",
+            "Acceptable delimiters are characters whose \\delcode is",
+            "nonnegative, or you can use `\\delimiter <delimiter code>'.",
+            NULL
+        };
+        back_error("Missing delimiter (. inserted)", hlp);
+        small_fam(p) = 0;
+        small_char(p) = 0;
+        large_fam(p) = 0;
+        large_char(p) = 0;
+    } else {
+        small_fam(p) = dval.small_family_value;
+        small_char(p) = dval.small_character_value;
+        large_fam(p) = dval.large_family_value;
+        large_char(p) = dval.large_character_value;
+    }
+    return;
+}
+
+@ @c
+void math_radical(void)
+{
+    halfword q;
+    int chr_code = cur_chr;
+    halfword options = 0;
+    tail_append(new_node(radical_noad, chr_code));
+    q = new_node(delim_node, 0);
+    left_delimiter(tail) = q;
+    while (1) {
+        if (scan_keyword("width")) {
+            scan_dimen(false,false,false);
+            radicalwidth(tail) = cur_val ;
+        } else if (scan_keyword("left")) {
+            options = options | noad_option_left ;
+        } else if (scan_keyword("middle")) {
+            options = options | noad_option_middle ;
+        } else if (scan_keyword("right")) {
+            options = options | noad_option_right ;
+        } else {
+            break;
+        }
+    }
+    radicaloptions(tail) = options;
+    if (chr_code == 0)          /* \.{\\radical} */
+        scan_delimiter(left_delimiter(tail), tex_mathcode);
+    else if (chr_code == 1)     /* \.{\\Uradical} */
+        scan_delimiter(left_delimiter(tail), umath_mathcode);
+    else if (chr_code == 2)     /* \.{\\Uroot} */
+        scan_delimiter(left_delimiter(tail), umath_mathcode);
+    else if (chr_code == 3)     /* \.{\\Uunderdelimiter} */
+        scan_delimiter(left_delimiter(tail), umath_mathcode);
+    else if (chr_code == 4)     /* \.{\\Uoverdelimiter} */
+        scan_delimiter(left_delimiter(tail), umath_mathcode);
+    else if (chr_code == 5)     /* \.{\\Udelimiterunder} */
+        scan_delimiter(left_delimiter(tail), umath_mathcode);
+    else if (chr_code == 6)     /* \.{\\Udelimiterover} */
+        scan_delimiter(left_delimiter(tail), umath_mathcode);
+    else if (chr_code == 7)     /* \.{\\Uhextensible} */
+        scan_delimiter(left_delimiter(tail), umath_mathcode);
+    else
+        confusion("math_radical");
+    if (chr_code == 7) {
+        q = new_node(sub_box_node, 0); /* type will change */
+        nucleus(tail) = q;
+        return;
+    } else if (chr_code == 2) {
+        /* the trick with the |vlink(q)| is used by |scan_math|
+           to decide whether it needs to go on */
+        q = new_node(math_char_node, 0);
+        vlink(q) = tail;
+        degree(tail) = q;
+        if (!scan_math(degree(tail), sup_sup_style(m_style))) {
+            vlink(degree(tail)) = null;
+            q = new_node(math_char_node, 0);
+            nucleus(tail) = q;
+            (void) scan_math(nucleus(tail), cramped_style(m_style));
+        }
+    } else {
+        q = new_node(math_char_node, 0);
+        nucleus(tail) = q;
+        (void) scan_math(nucleus(tail), cramped_style(m_style));
+    }
+}
+
+@ @c
+void math_ac(void)
+{
+    halfword q;
+    mathcodeval t = { 0, 0, 0 };
+    mathcodeval b = { 0, 0, 0 };
+    mathcodeval o = { 0, 0, 0 };
+    if (cur_cmd == accent_cmd) {
+        const char *hlp[] = {
+            "I'm changing \\accent to \\mathaccent here; wish me luck.",
+            "(Accents are not the same in formulas as they are in text.)",
+            NULL
+        };
+        tex_error("Please use \\mathaccent for accents in math mode", hlp);
+    }
+    tail_append(new_node(accent_noad, 0));
+    if (cur_chr == 0) {         /* \.{\\mathaccent} */
+        t = scan_mathchar(tex_mathcode);
+    } else if (cur_chr == 1) {  /* \.{\\Umathaccent} */
+        if (scan_keyword("fixed")) {
+            /* top */
+            subtype(tail) = 1;
+            t = scan_mathchar(umath_mathcode);
+        } else if (scan_keyword("both")) {
+            /* top bottom */
+            if (scan_keyword("fixed")) {
+                subtype(tail) = 1;
+            }
+            t = scan_mathchar(umath_mathcode);
+            if (scan_keyword("fixed")) {
+                subtype(tail) += 2;
+            }
+            b = scan_mathchar(umath_mathcode);
+        } else if (scan_keyword("bottom")) {
+            /* bottom */
+            if (scan_keyword("fixed")) {
+                subtype(tail) = 2;
+            }
+            b = scan_mathchar(umath_mathcode);
+        } else if (scan_keyword("top")) {
+            /* top */
+            if (scan_keyword("fixed")) {
+                subtype(tail) = 1;
+            }
+            t = scan_mathchar(umath_mathcode);
+        } else if (scan_keyword("overlay")) {
+            /* overlay */
+            if (scan_keyword("fixed")) {
+                subtype(tail) = 1;
+            }
+            o = scan_mathchar(umath_mathcode);
+        } else {
+            /* top */
+            t = scan_mathchar(umath_mathcode);
+        }
+        if (scan_keyword("fraction")) {
+            scan_int();
+            accentfraction(tail) = cur_val;
+        }
+    } else {
+        confusion("mathaccent");
+    }
+    if (!(t.character_value == 0 && t.family_value == 0)) {
+        q = new_node(math_char_node, 0);
+        top_accent_chr(tail) = q;
+        math_character(top_accent_chr(tail)) = t.character_value;
+        if ((t.class_value == math_use_current_family_code) && cur_fam_par_in_range)
+            math_fam(top_accent_chr(tail)) = cur_fam_par;
+        else
+            math_fam(top_accent_chr(tail)) = t.family_value;
+    }
+    if (!(b.character_value == 0 && b.family_value == 0)) {
+        q = new_node(math_char_node, 0);
+        bot_accent_chr(tail) = q;
+        math_character(bot_accent_chr(tail)) = b.character_value;
+        if ((b.class_value == math_use_current_family_code) && cur_fam_par_in_range)
+            math_fam(bot_accent_chr(tail)) = cur_fam_par;
+        else
+            math_fam(bot_accent_chr(tail)) = b.family_value;
+    }
+    if (!(o.character_value == 0 && o.family_value == 0)) {
+        q = new_node(math_char_node, 0);
+        overlay_accent_chr(tail) = q;
+        math_character(overlay_accent_chr(tail)) = o.character_value;
+        if ((o.class_value == math_use_current_family_code) && cur_fam_par_in_range)
+            math_fam(overlay_accent_chr(tail)) = cur_fam_par;
+        else
+            math_fam(overlay_accent_chr(tail)) = o.family_value;
+    }
+    q = new_node(math_char_node, 0);
+    nucleus(tail) = q;
+    (void) scan_math(nucleus(tail), cramped_style(m_style));
+}
+
+@ @c
+pointer math_vcenter_group(pointer p)
+{
+    pointer q, r;
+    q = new_noad();
+    subtype(q) = vcenter_noad_type;
+    r = new_node(sub_box_node, 0);
+    nucleus(q) = r;
+    math_list(nucleus(q)) = p;
+    return q;
+}
+
+@ The routine that scans the four mlists of a \.{\\mathchoice} is very
+much like the routine that builds discretionary nodes.
+
+ at c
+void append_choices(void)
+{
+    tail_append(new_choice());
+    incr(save_ptr);
+    set_saved_record(-1, saved_choices, 0, 0);
+    push_math(math_choice_group, display_style);
+    scan_left_brace();
+}
+
+@ @c
+void build_choices(void)
+{
+    pointer p;                  /* the current mlist */
+    int prev_style;
+    prev_style = m_style;
+    unsave_math();
+    p = fin_mlist(null);
+    assert(saved_type(-1) == saved_choices);
+    switch (saved_value(-1)) {
+    case 0:
+        display_mlist(tail) = p;
+        break;
+    case 1:
+        text_mlist(tail) = p;
+        break;
+    case 2:
+        script_mlist(tail) = p;
+        break;
+    case 3:
+        script_script_mlist(tail) = p;
+        decr(save_ptr);
+        return;
+        break;
+    }                           /* there are no other cases */
+    set_saved_record(-1, saved_choices, 0, (saved_value(-1) + 1));
+    push_math(math_choice_group, (prev_style + 2));
+    scan_left_brace();
+}
+
+@ Subscripts and superscripts are attached to the previous nucleus by the
+action procedure called |sub_sup|.
+
+ at c
+void sub_sup(void)
+{
+    pointer q;
+    if (tail == head || (!scripts_allowed(tail))) {
+        tail_append(new_noad());
+        q = new_node(sub_mlist_node, 0);
+        nucleus(tail) = q;
+    }
+    if (cur_cmd == sup_mark_cmd || cur_chr == sup_mark_cmd) {   /* |super_sub_script| */
+        if (supscr(tail) != null) {
+            const char *hlp[] = {
+                "I treat `x^1^2' essentially like `x^1{}^2'.", NULL
+            };
+            tail_append(new_noad());
+            q = new_node(sub_mlist_node, 0);
+            nucleus(tail) = q;
+            tex_error("Double superscript", hlp);
+        }
+        q = new_node(math_char_node, 0);
+        supscr(tail) = q;
+        (void) scan_math(supscr(tail), sup_style(m_style));
+    } else if (cur_cmd == sub_mark_cmd || cur_chr == sub_mark_cmd) {
+        if (subscr(tail) != null) {
+            const char *hlp[] = {
+                "I treat `x_1_2' essentially like `x_1{}_2'.", NULL
+            };
+            tail_append(new_noad());
+            q = new_node(sub_mlist_node, 0);
+            nucleus(tail) = q;
+            tex_error("Double subscript", hlp);
+        }
+        q = new_node(math_char_node, 0);
+        subscr(tail) = q;
+        (void) scan_math(subscr(tail), sub_style(m_style));
+    }
+}
+
+@ An operation like `\.{\\over}' causes the current mlist to go into a
+state of suspended animation: |incompleat_noad| points to a |fraction_noad|
+that contains the mlist-so-far as its numerator, while the denominator
+is yet to come. Finally when the mlist is finished, the denominator will
+go into the incompleat fraction noad, and that noad will become the
+whole formula, unless it is surrounded by `\.{\\left}' and `\.{\\right}'
+delimiters.
+
+ at c
+void math_fraction(void)
+{
+    halfword c;                 /* the type of generalized fraction we are scanning */
+    pointer q;
+    halfword options = 0;
+    halfword temp_value;
+    c = cur_chr;
+    if (incompleat_noad_par != null) {
+        const char *hlp[] = {
+            "I'm ignoring this fraction specification, since I don't",
+            "know whether a construction like `x \\over y \\over z'",
+            "means `{x \\over y} \\over z' or `x \\over {y \\over z}'.",
+            NULL
+        };
+        if (c >= delimited_code) {
+            scan_delimiter(null, no_mathcode);
+            scan_delimiter(null, no_mathcode);
+        }
+        if ((c % delimited_code) == above_code)
+            scan_normal_dimen();
+        tex_error("Ambiguous; you need another { and }", hlp);
+    } else {
+        incompleat_noad_par = new_node(fraction_noad, 0);
+        temp_value = new_node(sub_mlist_node, 0);
+        numerator(incompleat_noad_par) = temp_value;
+        math_list(numerator(incompleat_noad_par)) = vlink(head);
+        vlink(head) = null;
+        tail = head;
+        m_style = cramped_style(m_style);
+
+        if ((c % delimited_code) == skewed_code) {
+            q = new_node(delim_node, 0);
+            middle_delimiter(incompleat_noad_par) = q;
+            scan_delimiter(middle_delimiter(incompleat_noad_par), no_mathcode);
+        }
+        if (c >= delimited_code) {
+            q = new_node(delim_node, 0);
+            left_delimiter(incompleat_noad_par) = q;
+            q = new_node(delim_node, 0);
+            right_delimiter(incompleat_noad_par) = q;
+            scan_delimiter(left_delimiter(incompleat_noad_par), no_mathcode);
+            scan_delimiter(right_delimiter(incompleat_noad_par), no_mathcode);
+        }
+        switch (c % delimited_code) {
+            case above_code:
+                while (1) {
+                    if (scan_keyword("exact")) {
+                        options = options | noad_option_exact ;
+                    } else {
+                        break;
+                    }
+                }
+                fractionoptions(incompleat_noad_par) = options;
+                scan_normal_dimen();
+                thickness(incompleat_noad_par) = cur_val;
+                break;
+            case over_code:
+                thickness(incompleat_noad_par) = default_code;
+                break;
+            case atop_code:
+                thickness(incompleat_noad_par) = 0;
+                break;
+            case skewed_code:
+                while (1) {
+                    if (scan_keyword("exact")) {
+                        options = options | noad_option_exact ;
+                    } else if (scan_keyword("noaxis")) {
+                        options = options | noad_option_no_axis ;
+                    } else {
+                        break;
+                    }
+                }
+                fractionoptions(incompleat_noad_par) = options;
+                thickness(incompleat_noad_par) = 0;
+                break;
+        }
+    }
+}
+
+@ At the end of a math formula or subformula, the |fin_mlist| routine is
+called upon to return a pointer to the newly completed mlist, and to
+pop the nest back to the enclosing semantic level. The parameter to
+|fin_mlist|, if not null, points to a |fence_noad| that ends the
+current mlist; this |fence_noad| has not yet been appended.
+
+ at c
+pointer fin_mlist(pointer p)
+{
+    pointer q;                  /* the mlist to return */
+    if (incompleat_noad_par != null) {
+        if (denominator(incompleat_noad_par) != null) {
+            type(denominator(incompleat_noad_par)) = sub_mlist_node;
+        } else {
+            q = new_node(sub_mlist_node, 0);
+            denominator(incompleat_noad_par) = q;
+        }
+        math_list(denominator(incompleat_noad_par)) = vlink(head);
+        if (p == null) {
+            q = incompleat_noad_par;
+        } else {
+            q = math_list(numerator(incompleat_noad_par));
+            if ((type(q) != fence_noad) || (subtype(q) != left_noad_side)
+                || (delim_par == null))
+                confusion("right");     /* this can't happen */
+            math_list(numerator(incompleat_noad_par)) = vlink(delim_par);
+            vlink(delim_par) = incompleat_noad_par;
+            vlink(incompleat_noad_par) = p;
+        }
+    } else {
+        vlink(tail) = p;
+        q = vlink(head);
+    }
+    pop_nest();
+    return q;
+}
+
+@ Now at last we're ready to see what happens when a right brace occurs
+in a math formula. Two special cases are simplified here: Braces are effectively
+removed when they surround a single Ord without sub/superscripts, or when they
+surround an accent that is the nucleus of an Ord atom.
+
+ at c
+void close_math_group(pointer p)
+{
+    int old_style = m_style;
+    unsave_math();
+
+    decr(save_ptr);
+    assert(saved_type(0) == saved_math);
+    type(saved_value(0)) = sub_mlist_node;
+    p = fin_mlist(null);
+    math_list(saved_value(0)) = p;
+    if (p != null) {
+        if (vlink(p) == null) {
+            if (type(p) == simple_noad && subtype(p) == ord_noad_type) {
+                if (subscr(p) == null && supscr(p) == null) {
+                    type(saved_value(0)) = type(nucleus(p));
+                    if (type(nucleus(p)) == math_char_node) {
+                        math_fam(saved_value(0)) = math_fam(nucleus(p));
+                        math_character(saved_value(0)) =
+                            math_character(nucleus(p));
+                    } else {
+                        math_list(saved_value(0)) = math_list(nucleus(p));
+                        math_list(nucleus(p)) = null;
+                    }
+                    delete_attribute_ref(node_attr(saved_value(0)));
+                    node_attr(saved_value(0)) = node_attr(nucleus(p));
+                    node_attr(nucleus(p)) = null;
+                    flush_node(p);
+                }
+            } else if (type(p) == accent_noad) {
+                if (saved_value(0) == nucleus(tail)) {
+                    if (type(tail) == simple_noad
+                        && subtype(tail) == ord_noad_type) {
+                        pointer q = head;
+                        while (vlink(q) != tail)
+                            q = vlink(q);
+                        vlink(q) = p;
+                        nucleus(tail) = null;
+                        subscr(tail) = null;
+                        supscr(tail) = null;
+                        delete_attribute_ref(node_attr(p));
+                        node_attr(p) = node_attr(tail);
+                        node_attr(tail) = null;
+                        flush_node(tail);
+                        tail = p;
+                    }
+                }
+            }
+        }
+    }
+    if (vlink(saved_value(0)) > 0) {
+        pointer q;
+        q = new_node(math_char_node, 0);
+        nucleus(vlink(saved_value(0))) = q;
+        vlink(saved_value(0)) = null;
+        saved_value(0) = q;
+        (void) scan_math(saved_value(0), old_style);
+        /* restart */
+    }
+}
+
+@ We have dealt with all constructions of math mode except `\.{\\left}' and
+`\.{\\right}', so the picture is completed by the following sections of
+the program. The |middle| feature of eTeX allows one ore several \.{\\middle}
+delimiters to appear between \.{\\left} and \.{\\right}.
+
+ at c
+void math_left_right(void)
+{
+    halfword t;      /* |left_noad_side| .. |right_noad_side| */
+    pointer p;       /* new noad */
+    pointer q;       /* resulting mlist */
+    pointer r;       /* temporary */
+    halfword ht = 0;
+    halfword dp = 0;
+    halfword options = 0;
+    halfword type = -1 ;
+    t = cur_chr;
+
+    if (t > 10) {
+        /* we have \Uleft \Uright \Umiddle */
+        t = t - 10;
+        while (1) {
+            if (scan_keyword("height")) {
+                scan_dimen(false,false,false);
+                ht = cur_val ;
+            } else if (scan_keyword("depth")) {
+                scan_dimen(false,false,false);
+                dp = cur_val ;
+            } else if (scan_keyword("axis")) {
+                options = options | noad_option_axis ;
+            } else if (scan_keyword("noaxis")) {
+                options = options | noad_option_no_axis ;
+            } else if (scan_keyword("exact")) {
+                options = options | noad_option_exact ;
+            } else if (scan_keyword("class")) {
+                scan_int();
+                type = cur_val ;
+            } else {
+                break;
+            }
+        }
+    }
+
+    if ((t != no_noad_side) && (t != left_noad_side) && (cur_group != math_left_group)) {
+        if (cur_group == math_shift_group) {
+            scan_delimiter(null, no_mathcode);
+            if (t == middle_noad_side) {
+                const char *hlp[] = {
+                    "I'm ignoring a \\middle that had no matching \\left.",
+                    NULL
+                };
+                tex_error("Extra \\middle", hlp);
+            } else {
+                const char *hlp[] = {
+                    "I'm ignoring a \\right that had no matching \\left.",
+                    NULL
+                };
+                tex_error("Extra \\right", hlp);
+            }
+        } else {
+            off_save();
+        }
+    } else {
+        p = new_noad();
+        type(p) = fence_noad;
+        subtype(p) = (quarterword) t;
+        r = new_node(delim_node, 0);
+        delimiter(p) = r;
+
+        delimiterheight(p) = ht;
+        delimiterdepth(p) = dp;
+        delimiteroptions(p) = options;
+        delimiterclass(p) = type;
+        delimiteritalic(p) = 0;
+
+        scan_delimiter(delimiter(p), no_mathcode);
+
+        if (t == no_noad_side) {
+            tail_append(new_noad());
+            subtype(tail) = inner_noad_type;
+            r = new_node(sub_mlist_node, 0);
+            nucleus(tail) = r;
+            math_list(nucleus(tail)) = p;
+            return ;
+        }
+
+        if (t == left_noad_side) {
+            q = p;
+        } else {
+            q = fin_mlist(p);
+            unsave_math();
+        }
+        if (t != right_noad_side) {
+            push_math(math_left_group, m_style);
+            vlink(head) = q;
+            tail = p;
+            delim_par = p;
+        } else {
+            tail_append(new_noad());
+            subtype(tail) = inner_noad_type;
+            r = new_node(sub_mlist_node, 0);
+            nucleus(tail) = r;
+            math_list(nucleus(tail)) = q;
+        }
+    }
+}
+
+@ \TeX\ gets to the following part of the program when
+the first `\.\$' ending a display has been scanned.
+
+ at c
+static void check_second_math_shift(void)
+{
+    get_x_token();
+    if (cur_cmd != math_shift_cmd) {
+        const char *hlp[] = {
+            "The `$' that I just saw supposedly matches a previous `$$'.",
+            "So I shall assume that you typed `$$' both times.",
+            NULL
+        };
+        back_error("Display math should end with $$", hlp);
+    }
+}
+
+static void check_display_math_end(void)
+{
+    if (cur_chr != cramped_display_style) {
+        const char *hlp[] = {
+            "I shall assume that you typed that.",
+            NULL
+        };
+        tex_error("Display math should end with \\Ustopdisplaymath", hlp);
+    }
+}
+
+static void check_inline_math_end(void)
+{
+    if (cur_chr != cramped_text_style) {
+        const char *hlp[] = {
+            "I shall assume that you typed that.",
+            NULL
+        };
+        tex_error("Inline math should end with \\Ustopmath", hlp);
+    }
+}
+
+@ @c
+static void resume_after_display(void)
+{
+    if (cur_group != math_shift_group)
+        confusion("display");
+    unsave_math();
+    prev_graf_par = prev_graf_par + 3;
+    push_nest();
+    mode = hmode;
+    space_factor_par = 1000;
+    /* this needs to be intercepted in the display math start ! */
+    tail_append(make_local_par_node(penalty_par_code));
+    get_x_token();
+    if (cur_cmd != spacer_cmd)
+        back_input();
+    if (nest_ptr == 1) {
+        normal_page_filter(after_display);
+        build_page();
+    }
+}
+
+@  The fussiest part of math mode processing occurs when a displayed formula is
+being centered and placed with an optional equation number.
+
+At this time we are in vertical mode (or internal vertical mode).
+
+  |p| points to the mlist for the formula.
+  |a| is either |null| or it points to a box containing the equation number.
+  |l| is true if there was an \.{\\leqno}/ (so |a| is a horizontal box).
+
+ at c
+#define inject_display_skip_before(g) \
+    switch (display_skip_mode_par) { \
+        case 0 : /* normal tex */ \
+            tail_append(new_param_glue(g)); \
+            break;\
+        case 1 : /* always */ \
+            tail_append(new_param_glue(g)); \
+            break; \
+        case 2 : /* non-zero */ \
+            if (g != 0 && ! glue_is_zero(glue_par(g))) \
+                tail_append(new_param_glue(g)); \
+            break; \
+        case 3: /* ignore */ \
+            break; \
+    }
+
+#define inject_display_skip_after(g) \
+    switch (display_skip_mode_par) { \
+        case 0 : /* normal tex */ \
+            if (g != 0 && glue_is_positive(glue_par(g))) \
+                tail_append(new_param_glue(g)); \
+            break; \
+        case 1 : /* always */ \
+            tail_append(new_param_glue(g)); \
+            break; \
+        case 2 : /* non-zero */ \
+            if (g != 0 && ! glue_is_zero(glue_par(g))) \
+                tail_append(new_param_glue(g)); \
+            break; \
+        case 3: /* ignore */ \
+            break; \
+    }
+
+static void finish_displayed_math(boolean l, pointer eqno_box, pointer p)
+{
+    pointer eq_box;             /* box containing the equation */
+    scaled eq_w;                /* width of the equation */
+    scaled line_w;              /* width of the line */
+    scaled eqno_w;              /* width of equation number */
+    scaled eqno_w2;             /* width of equation number plus space to separate from equation */
+    scaled line_s;              /* move the line right this much */
+    scaled d;                   /* displacement of equation in the line */
+    small_number g1, g2;        /* glue parameter codes for before and after */
+    pointer r,s;                /* kern nodes used to position the display */
+    pointer t;                  /* tail of adjustment list */
+    pointer pre_t;              /* tail of pre-adjustment list */
+    boolean swap_dir;           /* true if the math and surrounding text dirs are opposed */
+    scaled eqno_width;
+    swap_dir = (pre_display_direction_par < 0 ? true : false );
+    if (eqno_box != null && swap_dir)
+        l = !l;
+    adjust_tail = adjust_head;
+    pre_adjust_tail = pre_adjust_head;
+    eq_box = hpack(p, 0, additional, -1);
+    subtype(eq_box) = equation_list; /* new */
+    build_attribute_list(eq_box);
+    p = list_ptr(eq_box);
+    t = adjust_tail;
+    adjust_tail = null;
+    pre_t = pre_adjust_tail;
+    pre_adjust_tail = null;
+    eq_w = width(eq_box);
+    line_w = display_width_par;
+    line_s = display_indent_par;
+    if (eqno_box == null) {
+        eqno_w = 0;
+        eqno_width = 0;
+        eqno_w2 = 0;
+    } else {
+        eqno_w = width(eqno_box);
+        eqno_width = eqno_w;
+        eqno_w2 = eqno_w + round_xn_over_d(math_eqno_gap_step_par, get_math_quad_style(text_style), 1000);
+        subtype(eqno_box) = equation_number_list; /* new */
+     /* build_attribute_list(eqno_box); */ /* probably already set */
+   }
+    if (eq_w + eqno_w2 > line_w) {
+        /* The user can force the equation number to go on a separate line
+           by causing its width to be zero. */
+        if ((eqno_w != 0) && ((eq_w - total_shrink[normal] + eqno_w2 <= line_w)
+                || (total_shrink[sfi] != 0)
+                || (total_shrink[fil] != 0)
+                || (total_shrink[fill] != 0)
+                || (total_shrink[filll] != 0))) {
+            list_ptr(eq_box) = null;
+            flush_node(eq_box);
+            eq_box = hpack(p, line_w - eqno_w2, exactly, -1);
+            subtype(eq_box) = equation_list; /* new */
+            build_attribute_list(eq_box);
+        } else {
+            eqno_w = 0;
+            if (eq_w > line_w) {
+                list_ptr(eq_box) = null;
+                flush_node(eq_box);
+                eq_box = hpack(p, line_w, exactly, -1);
+                subtype(eq_box) = equation_list; /* new */
+                build_attribute_list(eq_box);
+            }
+        }
+        eq_w = width(eq_box);
+    }
+    /* We try first to center the display without regard to the existence of
+       the equation number. If that would make it too close (where ``too close''
+       means that the space between display and equation number is less than the
+       width of the equation number), we either center it in the remaining space
+       or move it as far from the equation number as possible. The latter alternative
+       is taken only if the display begins with glue, since we assume that the
+       user put glue there to control the spacing precisely.
+     */
+    d = half(line_w - eq_w);
+    if ((eqno_w > 0) && (d < 2 * eqno_w)) {     /* too close */
+        d = half(line_w - eq_w - eqno_w);
+        if (p != null)
+            if (!is_char_node(p))
+                if (type(p) == glue_node)
+                    d = 0;
+    }
+
+    tail_append(new_penalty(pre_display_penalty_par,after_display_penalty));
+    if ((d + line_s <= pre_display_size_par) || l) {        /* not enough clearance */
+        g1 = above_display_skip_code;
+        g2 = below_display_skip_code;
+    } else {
+        g1 = above_display_short_skip_code;
+        g2 = below_display_short_skip_code;
+    }
+
+    /* If the equation number is set on a line by itself, either before or
+       after the formula, we append an infinite penalty so that no page break will
+       separate the display from its number; and we use the same size and
+       displacement for all three potential lines of the display, even though
+       `\.{\\parshape}' may specify them differently.
+     */
+     /* \.{\\leqno} on a forced single line due to |width=0| */
+     /* it follows that |type(a)=hlist_node| */
+
+    if (eqno_box && l && (eqno_w == 0)) {
+     /* if (math_direction_par==dir_TLT) { */
+            shift_amount(eqno_box) = 0;
+     /* } else {                       */
+     /* }                              */
+        append_to_vlist(eqno_box,lua_key_index(equation_number));
+        tail_append(new_penalty(inf_penalty,equation_number_penalty));
+    } else {
+        inject_display_skip_before(g1);
+    }
+
+    if (eqno_w != 0) {
+        r = new_kern(line_w - eq_w - eqno_w - d);
+        if (l) {
+            if (swap_dir) {
+                if (math_direction_par==dir_TLT) {
+                    /* TRT + TLT + \eqno,    (swap_dir=true,  math_direction_par=TLT, l=true)  */
+#ifdef DEBUG
+        fprintf(stderr, "\nDEBUG: CASE 1\n");
+#endif
+                    s = new_kern(width(r) + eqno_w);
+                    try_couple_nodes(eqno_box,r);
+                    try_couple_nodes(r,eq_box);
+                    try_couple_nodes(eq_box,s);
+                } else {
+                    /* TLT + TRT + \eqno,    (swap_dir=true,  math_direction_par=TRT, l=true) */
+#ifdef DEBUG
+        fprintf(stderr, "\nDEBUG: CASE 2\n");
+#endif
+                    try_couple_nodes(eqno_box,r);
+                    try_couple_nodes(r,eq_box);
+                }
+            } else {
+                if (math_direction_par==dir_TLT) {
+                    /* TLT + TLT + \leqno,   (swap_dir=false, math_direction_par=TLT, l=true) */ /* OK */
+#ifdef DEBUG
+        fprintf(stderr, "\nDEBUG: CASE 3\n");
+#endif
+                    s = new_kern(width(r) + eqno_w);
+                } else {
+                    /* TRT + TRT + \leqno,    (swap_dir=false, math_direction_par=TRT, l=true) */
+#ifdef DEBUG
+        fprintf(stderr, "\nDEBUG: CASE 4\n");
+#endif
+                    s = new_kern(width(r));
+                }
+                try_couple_nodes(eqno_box,r);
+                try_couple_nodes(r,eq_box);
+                try_couple_nodes(eq_box,s);
+            }
+            eq_box = eqno_box;
+        } else {
+            if (swap_dir) {
+                if (math_direction_par==dir_TLT) {
+                    /* TRT + TLT + \leqno,   (swap_dir=true,  math_direction_par=TLT, l=false) */
+#ifdef DEBUG
+        fprintf(stderr, "\nDEBUG: CASE 5\n");
+#endif
+                } else {
+                    /* TLT + TRT + \leqno,   (swap_dir=true,  math_direction_par=TRT, l=false) */
+#ifdef DEBUG
+        fprintf(stderr, "\nDEBUG: CASE 6\n");
+#endif
+                }
+                try_couple_nodes(eq_box,r);
+                try_couple_nodes(r,eqno_box);
+            } else {
+                if (math_direction_par==dir_TLT) {
+                    /*  TLT + TLT + \eqno,    (swap_dir=false, math_direction_par=TLT, l=false) */ /* OK */
+#ifdef DEBUG
+        fprintf(stderr, "\nDEBUG: CASE 7\n");
+#endif
+                    s = new_kern(d);
+                } else {
+                    /* TRT + TRT + \eqno,   (swap_dir=false, math_direction_par=TRT, l=false) */
+#ifdef DEBUG
+        fprintf(stderr, "\nDEBUG: CASE 8\n");
+#endif
+                    s = new_kern(width(r) + eqno_w);
+                }
+                try_couple_nodes(s,eq_box);
+                try_couple_nodes(eq_box,r);
+                try_couple_nodes(r,eqno_box);
+                eq_box = s;
+            }
+        }
+        eq_box = hpack(eq_box, 0, additional, -1);
+        subtype(eq_box) = equation_list; /* new */
+        build_attribute_list(eq_box);
+        shift_amount(eq_box) = line_s;
+    } else {
+        shift_amount(eq_box) = line_s + d;
+    }
+/* check for prev: */
+    append_to_vlist(eq_box,lua_key_index(equation));
+
+    if ((eqno_box != null) && (eqno_w == 0) && !l) {
+        tail_append(new_penalty(inf_penalty,equation_number_penalty));
+     /* if (math_direction_par==dir_TLT) { */
+            shift_amount(eqno_box) = line_s + line_w - eqno_width ;
+     /* } else {                       */
+     /* }                              */
+        append_to_vlist(eqno_box,lua_key_index(equation_number));
+        g2 = 0;
+    }
+    if (t != adjust_head) {     /* migrating material comes after equation number */
+        vlink(tail) = vlink(adjust_head);
+        /* needs testing */
+        alink(adjust_tail) = alink(tail);
+        tail = t;
+    }
+    if (pre_t != pre_adjust_head) {
+        vlink(tail) = vlink(pre_adjust_head);
+        /* needs testing */
+        alink(pre_adjust_tail) = alink(tail);
+        tail = pre_t;
+    }
+    tail_append(new_penalty(post_display_penalty_par,after_display_penalty));
+    inject_display_skip_after(g2);
+    resume_after_display();
+}
+
+@ @c
+void after_math(void)
+{
+    int m;                      /* |mmode| or |-mmode| */
+    pointer p;                  /* the formula */
+    pointer a = null;           /* box containing equation number */
+    boolean l = false;          /* `\.{\\leqno}' instead of `\.{\\eqno}' */
+    m = mode;
+    p = fin_mlist(null);        /* this pops the nest */
+    if (cur_cmd == math_shift_cs_cmd &&
+        (cur_chr == text_style || cur_chr == display_style)) {
+        you_cant();
+    }
+    if (mode == -m) {           /* end of equation number */
+        if (cur_cmd == math_shift_cmd) {
+            check_second_math_shift();
+        } else {
+            check_display_math_end();
+        }
+        run_mlist_to_hlist(p, false, text_style);
+        a = hpack(vlink(temp_head), 0, additional, -1);
+        build_attribute_list(a);
+        unsave_math();
+        decr(save_ptr);         /* now |cur_group=math_shift_group| */
+        assert(saved_type(0) == saved_eqno);
+        if (saved_value(0) == 1)
+            l = true;
+        m = mode;
+        p = fin_mlist(null);
+
+    }
+    if (m < 0) {
+        /* The |unsave| is done after everything else here; hence an appearance of
+           `\.{\\mathsurround}' inside of `\.{\$...\$}' affects the spacing at these
+           particular \.\$'s. This is consistent with the conventions of
+           `\.{\$\$...\$\$}', since `\.{\\abovedisplayskip}' inside a display affects the
+           space above that display.
+         */
+        if (cur_cmd == math_shift_cs_cmd) {
+            check_inline_math_end();
+        }
+        tail_append(new_math(math_surround_par, before));
+        /* begin mathskip code */
+        switch (math_skip_mode) {
+            case 0 :
+                /* obey mathsurround when zero glue */
+                if (! glue_is_zero(math_skip_par)) {
+                    copy_glue_values(tail,math_skip_par);
+                    surround(tail) = 0;
+                }
+                break ;
+            case 1 :
+                /* always left */
+            case 3 :
+                /* always both */
+            case 6 :
+                /* only when skip */
+                copy_glue_values(tail,math_skip_par);
+                surround(tail) = 0;
+                break ;
+            case 2 :
+                /* only right */
+                surround(tail) = 0;
+                break ;
+            case 4 :
+                /* ignore, obey marthsurround */
+                break ;
+            case 5:
+                /* all spacing disabled */
+                surround(tail) = 0;
+                break ;
+        }
+        /* end mathskip code */
+        if (dir_math_save) {
+            tail_append(new_dir(math_direction_par));
+        }
+        run_mlist_to_hlist(p, (mode > 0), text_style);
+        vlink(tail) = vlink(temp_head);
+        while (vlink(tail) != null) {
+            tail = vlink(tail);
+        }
+        if (dir_math_save) {
+            tail_append(new_dir(math_direction_par - dir_swap));
+        }
+        dir_math_save = false;
+        tail_append(new_math(math_surround_par, after));
+        /* begin mathskip code */
+        switch (math_skip_mode) {
+            case 0 :
+                /* obey mathsurround when zero glue */
+                if (! glue_is_zero(math_skip_par)) {
+                    copy_glue_values(tail,math_skip_par);
+                    surround(tail) = 0;
+                }
+                break ;
+            case 2 :
+                /* always right */
+            case 3 :
+                /* always both */
+            case 6 :
+                /* only when skip */
+                copy_glue_values(tail,math_skip_par);
+                surround(tail) = 0;
+                break ;
+            case 1 :
+                /* only left */
+                surround(tail) = 0;
+                break ;
+            case 4 :
+                /* ignore, obey marthsurround */
+                break ;
+            case 5:
+                /* all spacing disabled */
+                surround(tail) = 0;
+                break ;
+        }
+        /* end mathskip code */
+        space_factor_par = 1000;
+        unsave_math();
+    } else {
+        if (a == null) {
+            if (cur_cmd == math_shift_cmd) {
+                check_second_math_shift();
+            } else {
+                check_display_math_end();
+            }
+        }
+        run_mlist_to_hlist(p, false, display_style);
+        finish_displayed_math(l, a, vlink(temp_head));
+    }
+}
+
+@ When \.{\\halign} appears in a display, the alignment routines operate
+essentially as they do in vertical mode. Then the following program is
+activated, with |p| and |q| pointing to the beginning and end of the
+resulting list, and with |aux_save| holding the |prev_depth| value.
+
+ at c
+void finish_display_alignment(pointer p, pointer q, halfword saved_prevdepth)
+{
+    do_assignments();
+    if (cur_cmd == math_shift_cmd) {
+        check_second_math_shift();
+    } else {
+        check_display_math_end();
+    }
+    pop_nest();
+    tail_append(new_penalty(pre_display_penalty_par,before_display_penalty));
+    inject_display_skip_before(above_display_skip_code);
+    vlink(tail) = p;
+    if (p != null)
+        tail = q;
+    tail_append(new_penalty(post_display_penalty_par,after_display_penalty));
+    inject_display_skip_after(below_display_skip_code);
+    cur_list.prev_depth_field = saved_prevdepth;
+    resume_after_display();
+}
+
+@ Interface to \.{\\Umath} and \.{\\mathstyle}
+
+ at c
+void setup_math_style(void)
+{
+    pointer q;
+    tail_append(new_noad());
+    q = new_node(math_char_node, 0);
+    nucleus(tail) = q;
+    (void) scan_math_style(nucleus(tail), num_style(m_style));
+}
+
+@ @c
+void print_math_style(void)
+{
+    if (abs(mode) == mmode)
+        print_int(m_style);
+    else
+        print_int(-1);
+}

Modified: trunk/Build/source/texk/web2c/luatexdir/tex/texnodes.h
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/tex/texnodes.h	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/tex/texnodes.h	2017-03-11 00:31:06 UTC (rev 43454)
@@ -127,6 +127,18 @@
 #  define penalty_node_size    3
 #  define penalty(a)           vlink((a)+2)
 
+typedef enum {
+    user_penalty,
+    linebreak_penalty, /* includes widow, club, broken ect */
+    line_penalty,
+    word_penalty,
+    final_penalty,
+    noad_penalty,
+    before_display_penalty,
+    after_display_penalty,
+    equation_number_penalty,
+} penalty_subtypes ;
+
 /*
 #  define glue_node_size       4
 #  define glue_ptr(a)          vinfo((a)+2)
@@ -993,7 +1005,7 @@
 extern halfword new_glue(halfword q);
 extern halfword new_skip_param(int n);
 extern halfword new_kern(scaled w);
-extern halfword new_penalty(int m);
+extern halfword new_penalty(int m, int s);
 
 extern int lua_properties_enabled ;
 extern int lua_properties_level ;

Modified: trunk/Build/source/texk/web2c/luatexdir/tex/texnodes.w
===================================================================
--- trunk/Build/source/texk/web2c/luatexdir/tex/texnodes.w	2017-03-10 23:54:47 UTC (rev 43453)
+++ trunk/Build/source/texk/web2c/luatexdir/tex/texnodes.w	2017-03-11 00:31:06 UTC (rev 43454)
@@ -1,3775 +1,3777 @@
-% texnodes.w
-%
-% Copyright 2006-2010 Taco Hoekwater <taco@@luatex.org>
-%
-% 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 <http://www.gnu.org/licenses/>.
-
-@ @c
-
-#include "ptexlib.h"
-#include "lua/luatex-api.h"
-
-/* we can consider less mode sizes: 2 4 6 8 */
-
-@
-This module started out using NDEBUG to trigger checking invalid node usage,
-something that is needed because users can mess up nodes in Lua. At some point
-that code was always enabled so it is now always on but still can be recognized
-as additional code. And as the performance hit is close to zero so disabling
-makes no sense, not even to make it configureable. There is a little more memory
-used but that is neglectable compared to other memory usage.
-
- at c
-#define MAX_CHAIN_SIZE   13 /* why not a bit larger */
-#define CHECK_NODE_USAGE  1 /* this triggers checking */
-
-memory_word *volatile varmem = NULL;
-
-#ifdef CHECK_NODE_USAGE
-    char *varmem_sizes = NULL;
-#endif
-
-halfword var_mem_max = 0;
-halfword rover = 0;
-
-halfword free_chain[MAX_CHAIN_SIZE] = { null };
-
-static int my_prealloc = 0;
-
-int fix_node_lists = 1; /* used in font and lang */
-
-halfword slow_get_node(int s);  /* defined below */
-
-#define fake_node       100
-#define fake_node_size  2
-#define fake_node_name "fake"
-
-#define variable_node_size 2
-
-/* core nodes */
-
-const char *node_fields_list[] = {
-    "attr", "width", "depth", "height", "dir", "shift", "glue_order", "glue_sign",
-    "glue_set", "head", NULL
-};
-const char *node_fields_rule[] = {
-    "attr", "width", "depth", "height", "dir", "index", NULL
-};
-const char *node_fields_insert[] = {
-    "attr", "cost", "depth", "height", "spec", "head", NULL
-};
-const char *node_fields_mark[] = {
-    "attr", "class", "mark", NULL
-};
-const char *node_fields_adjust[] = {
-    "attr", "head", NULL
-};
-const char *node_fields_disc[] = {
-    "attr", "pre", "post", "replace", "penalty", NULL
-};
-const char *node_fields_math[] = {
-    "attr", "surround", "width", "stretch", "shrink", "stretch_order", "shrink_order", NULL
-};
-const char *node_fields_glue[] = {
-    "attr", "leader", "width", "stretch", "shrink", "stretch_order", "shrink_order", NULL
-};
-const char *node_fields_kern[] = {
-    "attr", "kern", "expansion_factor", NULL
-};
-const char *node_fields_penalty[] = {
-    "attr", "penalty", NULL
-};
-const char *node_fields_unset[] = {
-    "attr", "width", "depth", "height", "dir", "shrink", "glue_order",
-    "glue_sign", "stretch", "span", "head", NULL
-};
-const char *node_fields_margin_kern[]  = {
-    "attr", "width", "glyph", NULL
-};
-const char *node_fields_glyph[] = {
-    "attr", "char", "font", "lang", "left", "right", "uchyph", "components",
-    "xoffset", "yoffset", "width", "height", "depth", "expansion_factor", NULL
-};
-const char *node_fields_inserting[] = {
-    "height", "last_ins_ptr", "best_ins_ptr",
-    "width", "stretch", "shrink", "stretch_order", "shrink_order", NULL
-};
-const char *node_fields_splitup[] = {
-    "height", "last_ins_ptr", "best_ins_ptr", "broken_ptr", "broken_ins", NULL
-};
-const char *node_fields_attribute[] = {
-    "number", "value", NULL
-};
-const char *node_fields_glue_spec[] = {
-    "width", "stretch", "shrink", "stretch_order", "shrink_order", NULL
-};
-const char *node_fields_attribute_list[] = {
-    NULL
-};
-const char *node_fields_local_par[] = {
-    "attr", "pen_inter", "pen_broken", "dir", "box_left", "box_left_width",
-    "box_right", "box_right_width", NULL
-};
-const char *node_fields_dir[] = {
-    "attr", "dir", "level", NULL
-};
-const char *node_fields_boundary[] = {
-    "attr", "value", NULL
-};
-
-/* math nodes */
-
-const char *node_fields_noad[] = {
-    "attr", "nucleus", "sub", "sup", NULL
-};
-
-const char *node_fields_style[] = {
-    "attr", "style", NULL
-};
-const char *node_fields_choice[] = {
-    "attr", "display", "text", "script", "scriptscript", NULL
-};
-const char *node_fields_radical[] = {
-    "attr", "nucleus", "sub", "sup", "left", "degree", "width", "options", NULL
-};
-const char *node_fields_fraction[] = {
-    "attr", "width", "num", "denom", "left", "right", "middle", "options", NULL
-};
-const char *node_fields_accent[] = {
-    "attr", "nucleus", "sub", "sup", "accent", "bot_accent", "top_accent",
-    "overlay_accent", "fraction", NULL
-};
-const char *node_fields_fence[] = {
-    "attr", "delim", "italic", "height", "depth", "options", "class", NULL
-};
-const char *node_fields_math_char[] = {
-    "attr", "fam", "char", NULL
-};
-const char *node_fields_sub_box[] = {
-    "attr", "head", NULL
-};
-const char *node_fields_sub_mlist[] = {
-    "attr", "head", NULL
-};
-const char *node_fields_math_text_char[] = {
-    "attr", "fam", "char", NULL
-};
-const char *node_fields_delim[] = {
-    "attr", "small_fam", "small_char", "large_fam", "large_char", NULL
-};
-
-/* whatsit nodes */
-
-const char *node_fields_whatsit_open[] = {
-    "attr", "stream", "name", "area", "ext", NULL
-};
-const char *node_fields_whatsit_write[] = {
-    "attr", "stream", "data", NULL
-};
-const char *node_fields_whatsit_close[] = {
-    "attr", "stream", NULL
-};
-const char *node_fields_whatsit_special[] = {
-    "attr", "data", NULL
-};
-const char *node_fields_whatsit_save_pos[] = {
-    "attr", NULL
-};
-const char *node_fields_whatsit_late_lua[] = {
-    "attr", "reg", "data", "name", "string", NULL
-};
-const char *node_fields_whatsit_user_defined[] = {
-    "attr", "user_id", "type", "value", NULL
-};
-
-/* pdf backend whatsit nodes */
-
-const char *node_fields_whatsit_pdf_literal[] = {
-    "attr", "mode", "data", NULL
-};
-const char *node_fields_whatsit_pdf_refobj[] = {
-    "attr", "objnum", NULL
-};
-const char *node_fields_whatsit_pdf_annot[] = {
-    "attr", "width", "depth", "height", "objnum", "data", NULL
-};
-const char *node_fields_whatsit_pdf_start_link[] = {
-    "attr", "width", "depth", "height", "objnum", "link_attr", "action", NULL
-};
-const char *node_fields_whatsit_pdf_end_link[] = {
-    "attr", NULL
-};
-const char *node_fields_whatsit_pdf_dest[] = {
-    "attr", "width", "depth", "height", "named_id", "dest_id", "dest_type",
-    "xyz_zoom", "objnum", NULL
-};
-const char *node_fields_whatsit_pdf_action[] = {
-    "action_type", "named_id", "action_id", "file", "new_window", "data", NULL
-};
-const char *node_fields_whatsit_pdf_thread[] = {
-    "attr", "width", "depth", "height",  "named_id", "thread_id", "thread_attr", NULL
-};
-const char *node_fields_whatsit_pdf_start_thread[] = {
-    "attr", "width", "depth", "height", "named_id", "thread_id", "thread_attr", NULL
-};
-const char *node_fields_whatsit_pdf_end_thread[] = {
-    "attr", NULL
-};
-const char *node_fields_whatsit_pdf_colorstack[] = {
-    "attr", "stack", "cmd", "data", NULL
-};
-const char *node_fields_whatsit_pdf_setmatrix[] = {
-    "attr", "data", NULL
-};
-const char *node_fields_whatsit_pdf_save[] = {
-    "attr", NULL
-};
-const char *node_fields_whatsit_pdf_restore[] = {
-    "attr", NULL
-};
-
-/* subtypes */
-
-const char *node_subtypes_glue[] = {
-    "userskip", "lineskip", "baselineskip", "parskip", "abovedisplayskip", "belowdisplayskip",
-    "abovedisplayshortskip", "belowdisplayshortskip", "leftskip", "rightskip", "topskip",
-    "splittopskip", "tabskip", "spaceskip", "xspaceskip", "parfillskip",
-    "mathskip", "thinmuskip", "medmuskip", "thickmuskip", NULL
-};
-const char *node_subtypes_mathglue[] = { /* 98+ */
-    "conditionalmathskip", "muglue", NULL
-};
-const char *node_subtypes_leader[] = { /* 100+ */
-    "leaders", "cleaders", "xleaders", "gleaders", NULL
-};
-const char *node_subtypes_fill[] = {
-    "stretch", "fi", "fil", "fill", "filll", NULL
-};
-const char *node_subtypes_boundary[] = {
-    "cancel", "user", "protrusion", "word", NULL
-};
-const char *node_subtypes_penalty[] = {
-    "userpenalty", NULL
-};
-const char *node_subtypes_kern[] = {
-    "fontkern", "userkern", "accentkern", "italiccorrection", NULL
-};
-const char *node_subtypes_rule[] = {
-    "normal", "box", "image", "empty", "user", "over", "under", "fraction", "radical", NULL
-};
-const char *node_subtypes_glyph[] = {
-    "character", "glyph", "ligature", "ghost", "left", "right", NULL
-};
-const char *node_subtypes_disc[] = {
-    "discretionary", "explicit", "automatic", "regular", "first", "second", NULL
-};
-const char *node_subtypes_marginkern[] = {
-    "left", "right", NULL
-};
-const char *node_subtypes_list[] = {
-    "unknown", "line", "box", "indent", "alignment", "cell", "equation", "equationnumber", NULL
-};
-const char *node_subtypes_adjust[] = {
-    "normal", "pre", NULL
-};
-const char *node_subtypes_math[] = {
-    "beginmath", "endmath", NULL
-};
-const char *node_subtypes_noad[] = {
-    "ord", "opdisplaylimits", "oplimits", "opnolimits", "bin", "rel", "open", "close",
-    "punct", "inner", "under", "over", "vcenter", NULL
-};
-const char *node_subtypes_radical[] = {
-    "radical", "uradical", "uroot", "uunderdelimiter", "uoverdelimiter", "udelimiterunder",
-    "udelimiterover", NULL
-};
-const char *node_subtypes_accent[] = {
-    "bothflexible", "fixedtop", "fixedbottom", "fixedboth", NULL,
-};
-const char *node_subtypes_fence[] = {
-    "unset", "left", "middle", "right", NULL
-};
-
-node_info node_data[] = { /* the last entry in a row is the etex number */
-    { hlist_node,          box_node_size,         node_fields_list,                          "hlist",           1 },
-    { vlist_node,          box_node_size,         node_fields_list,                          "vlist",           2 },
-    { rule_node,           rule_node_size,        node_fields_rule,                          "rule",            3 },
-    { ins_node,            ins_node_size,         node_fields_insert,                        "ins",             4 },
-    { mark_node,           mark_node_size,        node_fields_mark,                          "mark",            5 },
-    { adjust_node,         adjust_node_size,      node_fields_adjust,                        "adjust",          6 },
-    { boundary_node,       boundary_node_size,    node_fields_boundary,                      "boundary",       -1 },
-    { disc_node,           disc_node_size,        node_fields_disc,                          "disc",            8 },
-    { whatsit_node,        -1,                    NULL,                                      "whatsit",         9 },
-    { local_par_node,      local_par_size,        node_fields_local_par,                     "local_par",      -1 },
-    { dir_node,            dir_node_size,         node_fields_dir,                           "dir",            -1 },
-    { math_node,           math_node_size,        node_fields_math,                          "math",           10 },
-    { glue_node,           glue_node_size,        node_fields_glue,                          "glue",           11 },
-    { kern_node,           kern_node_size,        node_fields_kern,                          "kern",           12 },
-    { penalty_node,        penalty_node_size,     node_fields_penalty,                       "penalty",        13 },
-    { unset_node,          box_node_size,         node_fields_unset,                         "unset",          14 },
-    { style_node,          style_node_size,       node_fields_style,                         "style",          15 },
-    { choice_node,         style_node_size,       node_fields_choice,                        "choice",         15 },
-    { simple_noad,         noad_size,             node_fields_noad,                          "noad",           15 },
-    { radical_noad,        radical_noad_size,     node_fields_radical,                       "radical",        15 },
-    { fraction_noad,       fraction_noad_size,    node_fields_fraction,                      "fraction",       15 },
-    { accent_noad,         accent_noad_size,      node_fields_accent,                        "accent",         15 },
-    { fence_noad,          fence_noad_size,       node_fields_fence,                         "fence",          15 },
-    { math_char_node,      math_kernel_node_size, node_fields_math_char,                     "math_char",      15 },
-    { sub_box_node,        math_kernel_node_size, node_fields_sub_box,                       "sub_box",        15 },
-    { sub_mlist_node,      math_kernel_node_size, node_fields_sub_mlist,                     "sub_mlist",      15 },
-    { math_text_char_node, math_kernel_node_size, node_fields_math_text_char,                "math_text_char", 15 },
-    { delim_node,          math_shield_node_size, node_fields_delim,                         "delim",          15 },
-    { margin_kern_node,    margin_kern_node_size, node_fields_margin_kern,                   "margin_kern",    -1 },
-    { glyph_node,          glyph_node_size,       node_fields_glyph,                         "glyph",           0 },
-    { align_record_node,   box_node_size,         NULL,                                      "align_record",   -1 },
-    { pseudo_file_node,    pseudo_file_node_size, NULL,                                      "pseudo_file",    -1 },
-    { pseudo_line_node,    variable_node_size,    NULL,                                      "pseudo_line",    -1 },
-    { inserting_node,      page_ins_node_size,    node_fields_inserting,                     "page_insert",    -1 },
-    { split_up_node,       page_ins_node_size,    node_fields_splitup,                       "split_insert",   -1 },
-    { expr_node,           expr_node_size,        NULL,                                      "expr_stack",     -1 },
-    { nesting_node,        nesting_node_size,     NULL,                                      "nested_list",    -1 },
-    { span_node,           span_node_size,        NULL,                                      "span",           -1 },
-    { attribute_node,      attribute_node_size,   node_fields_attribute,                     "attribute",      -1 },
-    { glue_spec_node,      glue_spec_size,        node_fields_glue_spec,                     "glue_spec",      -1 },
-    { attribute_list_node, attribute_node_size,   node_fields_attribute_list,                "attribute_list", -1 },
-    { temp_node,           temp_node_size,        NULL,                                      "temp",           -1 },
-    { align_stack_node,    align_stack_node_size, NULL,                                      "align_stack",    -1 },
-    { movement_node,       movement_node_size,    NULL,                                      "movement_stack", -1 },
-    { if_node,             if_node_size,          NULL,                                      "if_stack",       -1 },
-    { unhyphenated_node,   active_node_size,      NULL,                                      "unhyphenated",   -1 },
-    { hyphenated_node,     active_node_size,      NULL,                                      "hyphenated",     -1 },
-    { delta_node,          delta_node_size,       NULL,                                      "delta",          -1 },
-    { passive_node,        passive_node_size,     NULL,                                      "passive",        -1 },
-    { shape_node,          variable_node_size,    NULL,                                      "shape",          -1 },
-    { -1,                 -1,                     NULL,                                      NULL,             -1 },
-};
-
-const char *node_subtypes_pdf_destination[] = {
-    "xyz", "fit", "fith", "fitv", "fitb", "fitbh", "fitbv", "fitr", NULL
-};
-const char *node_subtypes_pdf_literal[] = {
-    "origin", "page", "direct", NULL
-};
-
-node_info whatsit_node_data[] = {
-    { open_node,         open_node_size,               node_fields_whatsit_open,             "open",             -1 },
-    { write_node,        write_node_size,              node_fields_whatsit_write,            "write",            -1 },
-    { close_node,        close_node_size,              node_fields_whatsit_close,            "close",            -1 },
-    { special_node,      special_node_size,            node_fields_whatsit_special,          "special",          -1 },
-    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
-    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
-    { save_pos_node,     save_pos_node_size,           node_fields_whatsit_save_pos,         "save_pos",         -1 },
-    { late_lua_node,     late_lua_node_size,           node_fields_whatsit_late_lua,         "late_lua",         -1 },
-    { user_defined_node, user_defined_node_size,       node_fields_whatsit_user_defined,     "user_defined",     -1 },
-    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
-    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
-    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
-    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
-    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
-    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
-    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
-    /* here starts the dvi backend section, todo: a separate list  */
-    /* nothing for dvi */
-    /* here starts the pdf backend section, todo: a separate list  */
-    { pdf_literal_node,      write_node_size,          node_fields_whatsit_pdf_literal,      "pdf_literal",      -1 },
-    { pdf_refobj_node,       pdf_refobj_node_size,     node_fields_whatsit_pdf_refobj,       "pdf_refobj",       -1 },
-    { pdf_annot_node,        pdf_annot_node_size,      node_fields_whatsit_pdf_annot,        "pdf_annot",        -1 },
-    { pdf_start_link_node,   pdf_annot_node_size,      node_fields_whatsit_pdf_start_link,   "pdf_start_link",   -1 },
-    { pdf_end_link_node,     pdf_end_link_node_size,   node_fields_whatsit_pdf_end_link,     "pdf_end_link",     -1 },
-    { pdf_dest_node,         pdf_dest_node_size,       node_fields_whatsit_pdf_dest,         "pdf_dest",         -1 },
-    { pdf_action_node,       pdf_action_size,          node_fields_whatsit_pdf_action,       "pdf_action",       -1 },
-    { pdf_thread_node,       pdf_thread_node_size,     node_fields_whatsit_pdf_thread,       "pdf_thread",       -1 },
-    { pdf_start_thread_node, pdf_thread_node_size,     node_fields_whatsit_pdf_start_thread, "pdf_start_thread", -1 },
-    { pdf_end_thread_node,   pdf_end_thread_node_size, node_fields_whatsit_pdf_end_thread,   "pdf_end_thread",   -1 },
-    { pdf_thread_data_node,  pdf_thread_node_size,     NULL,                                 "pdf_thread_data",  -1 },
-    { pdf_link_data_node,    pdf_annot_node_size,      NULL,                                 "pdf_link_data",    -1 },
-    { pdf_colorstack_node,   pdf_colorstack_node_size, node_fields_whatsit_pdf_colorstack,   "pdf_colorstack",   -1 },
-    { pdf_setmatrix_node,    pdf_setmatrix_node_size,  node_fields_whatsit_pdf_setmatrix,    "pdf_setmatrix",    -1 },
-    { pdf_save_node,         pdf_save_node_size,       node_fields_whatsit_pdf_save,         "pdf_save",         -1 },
-    { pdf_restore_node,      pdf_restore_node_size,    node_fields_whatsit_pdf_restore,      "pdf_restore",      -1 },
-    /* done */
-    { -1,                    -1,                       NULL,                                 NULL,               -1 },
-};
-
-#define last_whatsit_node pdf_restore_node
-
-@
-When we copy a node list, there are several possibilities: we do the same as a new node,
-we copy the entry to the table in properties (a reference), we do a deep copy of a table
-in the properties, we create a new table and give it the original one as a metatable.
-After some experiments (that also included timing) with these scenarios I decided that a
-deep copy made no sense, nor did nilling. In the end both the shallow copy and the metatable
-variant were both ok, although the second ons is slower. The most important aspect to keep
-in mind is that references to other nodes in properties no longer can be valid for that
-copy. We could use two tables (one unique and one shared) or metatables but that only
-complicates matters.
-
-When defining a new node, we could already allocate a table but it is rather easy to do
-that at the lua end e.g. using a metatable __index method. That way it is under macro
-package control.
-
-When deleting a node, we could keep the slot (e.g. setting it to false) but it could make
-memory consumption raise unneeded when we have temporary large node lists and after that
-only small lists.
-
-So, in the end this is what we ended up with. For the record, I also experimented with the
-following:
-
-- copy attributes to the properties so that we have fast access at the lua end: in the end
-  the overhead is not compensated by speed and convenience, in fact, attributes are not
-  that slow when it comes to accessing them
-
-- a bitset in the node but again the gain compared to attributes is neglectable and it also
-  demands a pretty string agreement over what bit represents what, and this is unlikely to
-  succeed in the tex community (I could use it for font handling, which is cross package,
-  but decided that it doesn't pay off
-
-In case one wonders why properties make sense then, well, it is not so much speed that we
-gain, but more convenience: storing all kind of (temporary) data in attributes is no fun and
-this mechanism makes sure that properties are cleaned up when a node is freed. Also, the
-advantage of a more or less global properties table is that we stay at the lua end. An
-alternative is to store a reference in the node itself but that is complicated by the fact
-that the register has some limitations (no numeric keys) and we also don't want to mess with
-it too much.
-
- at c
-int lua_properties_level         = 0 ; /* can be private */
-int lua_properties_enabled       = 0 ;
-int lua_properties_use_metatable = 0 ;
-
-@
-We keep track of nesting so that we don't oveflow the stack, and, what is more
-important, don't keep resolving the registry index.
-
- at c
-#define lua_properties_push do { \
-    if (lua_properties_enabled) { \
-        lua_properties_level = lua_properties_level + 1 ; \
-        if (lua_properties_level == 1) { \
-            lua_get_metatablelua_l(Luas,node_properties); \
-        } \
-    } \
-} while(0)
-
-#define lua_properties_pop do { \
-    if (lua_properties_enabled) { \
-        if (lua_properties_level == 1) \
-            lua_pop(Luas,1); \
-        lua_properties_level = lua_properties_level - 1 ; \
-    } \
-} while(0)
-
-/* No setting is needed: */
-
-#define lua_properties_set(target) do { \
-} while(0)
-
-/* Resetting boils down to nilling. */
-
-#define lua_properties_reset(target) do { \
-    if (lua_properties_enabled) { \
-        if (lua_properties_level == 0) { \
-            lua_get_metatablelua_l(Luas,node_properties); \
-            lua_pushnil(Luas); \
-            lua_rawseti(Luas,-2,target); \
-            lua_pop(Luas,1); \
-        } else { \
-            lua_pushnil(Luas); \
-            lua_rawseti(Luas,-2,target); \
-        } \
-    } \
-} while(0)
-
-/*
-    For a moment I considered supporting all kind of data types but in practice
-    that makes no sense. So we stick to a cheap shallow copy with as option a
-    metatable. Btw, a deep copy would look like this:
-
-    static void copy_lua_table(lua_State* L, int index) {
-        lua_newtable(L);
-        lua_pushnil(L);
-        while(lua_next(L, index-1) != 0) {
-            lua_pushvalue(L, -2);
-            lua_insert(L, -2);
-            if (lua_type(L,-1)==LUA_TTABLE)
-                copy_lua_table(L,-1);
-            lua_settable(L, -4);
-        }
-        lua_pop(L,1);
-    }
-
-    #define lua_properties_copy(target, source) do { \
-        if (lua_properties_enabled) { \
-            lua_pushinteger(Luas,source); \
-            lua_rawget(Luas,-2); \
-            if (lua_type(Luas,-1)==LUA_TTABLE) { \
-                copy_lua_table(Luas,-1); \
-                lua_pushinteger(Luas,target); \
-                lua_insert(Luas,-2); \
-                lua_rawset(Luas,-3); \
-            } else { \
-                lua_pop(Luas,1); \
-            } \
-        } \
-    } while(0)
-
-*/
-
-/* isn't there a faster way to metatable? */
-
-/*
-
-#define lua_properties_copy(target,source) do { \
-    if (lua_properties_enabled) { \
-        if (lua_properties_level == 0) { \
-            lua_get_metatablelua_l(Luas,node_properties); \
-            lua_rawgeti(Luas,-1,source); \
-            if (lua_type(Luas,-1)==LUA_TTABLE) { \
-                if (lua_properties_use_metatable) { \
-                    lua_newtable(Luas); \
-                    lua_insert(Luas,-2); \
-                    lua_setfield(Luas,-2,"__index"); \
-                    lua_newtable(Luas); \
-                    lua_insert(Luas,-2); \
-                    lua_setmetatable(Luas,-2); \
-                } \
-                lua_rawseti(Luas,-2,target); \
-            } else { \
-                lua_pop(Luas,1); \
-            } \
-            lua_pop(Luas,1); \
-        } else { \
-            lua_rawgeti(Luas,-1,source); \
-            if (lua_type(Luas,-1)==LUA_TTABLE) { \
-                if (lua_properties_use_metatable) { \
-                    lua_newtable(Luas); \
-                    lua_insert(Luas,-2); \
-                    lua_setfield(Luas,-2,"__index"); \
-                    lua_newtable(Luas); \
-                    lua_insert(Luas,-2); \
-                    lua_setmetatable(Luas,-2); \
-                } \
-                lua_rawseti(Luas,-2,target); \
-            } else { \
-                lua_pop(Luas,1); \
-            } \
-        } \
-    } \
-} while(0)
-
-*/
-
-/*
-    A simple testrun on many pages of dumb text shows 1% gain (of course it depends
-    on how properties are used but some other tests confirm it).
-*/
-
-#define lua_properties_copy(target,source) do { \
-    if (lua_properties_enabled) { \
-        if (lua_properties_level == 0) { \
-            lua_get_metatablelua_l(Luas,node_properties); \
-            lua_rawgeti(Luas,-1,source); \
-            if (lua_type(Luas,-1)==LUA_TTABLE) { \
-                if (lua_properties_use_metatable) { \
-                    lua_newtable(Luas); \
-                    lua_insert(Luas,-2); \
-                    lua_push_string_by_name(Luas,__index); \
-                    lua_insert(Luas,-2); \
-                    lua_rawset(Luas, -3); \
-                    lua_newtable(Luas); \
-                    lua_insert(Luas,-2); \
-                    lua_setmetatable(Luas,-2); \
-                } \
-                lua_rawseti(Luas,-2,target); \
-            } else { \
-                lua_pop(Luas,1); \
-            } \
-            lua_pop(Luas,1); \
-        } else { \
-            lua_rawgeti(Luas,-1,source); \
-            if (lua_type(Luas,-1)==LUA_TTABLE) { \
-                if (lua_properties_use_metatable) { \
-                    lua_newtable(Luas); \
-                    lua_insert(Luas,-2); \
-                    lua_push_string_by_name(Luas,__index); \
-                    lua_insert(Luas,-2); \
-                    lua_rawset(Luas, -3); \
-                    lua_newtable(Luas); \
-                    lua_insert(Luas,-2); \
-                    lua_setmetatable(Luas,-2); \
-                } \
-                lua_rawseti(Luas,-2,target); \
-            } else { \
-                lua_pop(Luas,1); \
-            } \
-        } \
-    } \
-} while(0)
-
-/* Here end the property handlers. */
-
-@ @c
-int valid_node(halfword p)
-{
-    if (p > my_prealloc && p < var_mem_max) {
-#ifdef CHECK_NODE_USAGE
-        if (varmem_sizes[p] > 0) {
-            return 1;
-        }
-#else
-        return 1;
-#endif
-    }
-    return 0;
-}
-
-@ @c
-static int test_count = 1;
-
-#define dorangetest(a,b,c)  do {                                 \
-    if (!(b>=0 && b<c)) {                                        \
-        fprintf(stdout,"For node p:=%d, 0<=%d<%d (l.%d,r.%d)\n", \
-            (int)a, (int)b, (int)c, __LINE__,test_count);        \
-        confusion("node range test failed");                     \
-    } } while (0)
-
-#define dotest(a,b,c) do {                                     \
-    if (b!=c) {                                                \
-        fprintf(stdout,"For node p:=%d, %d==%d (l.%d,r.%d)\n", \
-            (int)a, (int)b, (int)c, __LINE__,test_count);      \
-        confusion("node test failed");                         \
-    } } while (0)
-
-#define check_action_ref(a)    { dorangetest(p,a,var_mem_max); }
-#define check_attribute_ref(a) { dorangetest(p,a,var_mem_max); }
-#define check_token_ref(a) { \
-    if (type(p) == whatsit_node) { \
-        formatted_error("nodes","fuzzy token cleanup in whatsit node with id %i and subtype %i",type(p),subtype(p)); \
-    } else { \
-        formatted_error("nodes","fuzzy token cleanup in node with id %i",type(p)); \
-    } \
-}
-
-#ifdef CHECK_NODE_USAGE
-
-static void check_static_node_mem(void)
-{
-    dotest(zero_glue, width(zero_glue), 0);
-    dotest(zero_glue, type(zero_glue), glue_spec_node);
-    dotest(zero_glue, vlink(zero_glue), null);
-    dotest(zero_glue, stretch(zero_glue), 0);
-    dotest(zero_glue, stretch_order(zero_glue), normal);
-    dotest(zero_glue, shrink(zero_glue), 0);
-    dotest(zero_glue, shrink_order(zero_glue), normal);
-
-    dotest(sfi_glue, width(sfi_glue), 0);
-    dotest(sfi_glue, type(sfi_glue), glue_spec_node);
-    dotest(sfi_glue, vlink(sfi_glue), null);
-    dotest(sfi_glue, stretch(sfi_glue), 0);
-    dotest(sfi_glue, stretch_order(sfi_glue), sfi);
-    dotest(sfi_glue, shrink(sfi_glue), 0);
-    dotest(sfi_glue, shrink_order(sfi_glue), normal);
-
-    dotest(fil_glue, width(fil_glue), 0);
-    dotest(fil_glue, type(fil_glue), glue_spec_node);
-    dotest(fil_glue, vlink(fil_glue), null);
-    dotest(fil_glue, stretch(fil_glue), unity);
-    dotest(fil_glue, stretch_order(fil_glue), fil);
-    dotest(fil_glue, shrink(fil_glue), 0);
-    dotest(fil_glue, shrink_order(fil_glue), normal);
-
-    dotest(fill_glue, width(fill_glue), 0);
-    dotest(fill_glue, type(fill_glue), glue_spec_node);
-    dotest(fill_glue, vlink(fill_glue), null);
-    dotest(fill_glue, stretch(fill_glue), unity);
-    dotest(fill_glue, stretch_order(fill_glue), fill);
-    dotest(fill_glue, shrink(fill_glue), 0);
-    dotest(fill_glue, shrink_order(fill_glue), normal);
-
-    dotest(ss_glue, width(ss_glue), 0);
-    dotest(ss_glue, type(ss_glue), glue_spec_node);
-    dotest(ss_glue, vlink(ss_glue), null);
-    dotest(ss_glue, stretch(ss_glue), unity);
-    dotest(ss_glue, stretch_order(ss_glue), fil);
-    dotest(ss_glue, shrink(ss_glue), unity);
-    dotest(ss_glue, shrink_order(ss_glue), fil);
-
-    dotest(fil_neg_glue, width(fil_neg_glue), 0);
-    dotest(fil_neg_glue, type(fil_neg_glue), glue_spec_node);
-    dotest(fil_neg_glue, vlink(fil_neg_glue), null);
-    dotest(fil_neg_glue, stretch(fil_neg_glue), -unity);
-    dotest(fil_neg_glue, stretch_order(fil_neg_glue), fil);
-    dotest(fil_neg_glue, shrink(fil_neg_glue), 0);
-    dotest(fil_neg_glue, shrink_order(fil_neg_glue), normal);
-}
-
-static void node_mem_dump(halfword p)
-{
-    halfword r;
-    for (r = my_prealloc + 1; r < var_mem_max; r++) {
-        if (vlink(r) == p) {
-            halfword s = r;
-            while (s > my_prealloc && varmem_sizes[s] == 0) {
-                s--;
-            }
-            if (s != null
-                && s != my_prealloc
-                && s != var_mem_max
-                && (r - s) < get_node_size(type(s), subtype(s))
-                && alink(s) != p) {
-                if (type(s) == disc_node) {
-                    fprintf(stdout,"  pointed to from %s node %d (vlink %d, alink %d): ",
-                            get_node_name(type(s), subtype(s)), (int) s,
-                            (int) vlink(s), (int) alink(s));
-                    fprintf(stdout, "pre_break(%d,%d,%d), ",
-                            (int) vlink_pre_break(s), (int) tlink(pre_break(s)),
-                            (int) alink(pre_break(s)));
-                    fprintf(stdout, "post_break(%d,%d,%d), ",
-                            (int) vlink_post_break(s),
-                            (int) tlink(post_break(s)),
-                            (int) alink(post_break(s)));
-                    fprintf(stdout, "no_break(%d,%d,%d)",
-                            (int) vlink_no_break(s), (int) tlink(no_break(s)),
-                            (int) alink(no_break(s)));
-                    fprintf(stdout, "\n");
-                } else {
-                    if (vlink(s) == p
-                        || (type(s) == glyph_node && lig_ptr (s) == p)
-                        || (type(s) == vlist_node && list_ptr(s) == p)
-                        || (type(s) == hlist_node && list_ptr(s) == p)
-                        || (type(s) == unset_node && list_ptr(s) == p)
-                        || (type(s) == ins_node   && ins_ptr (s) == p)
-                        ) {
-                        fprintf(stdout,"  pointed to from %s node %d (vlink %d, alink %d): ",
-                                get_node_name(type(s), subtype(s)), (int) s,
-                                (int) vlink(s), (int) alink(s));
-                        if (type(s) == glyph_node) {
-                            fprintf(stdout, "lig_ptr(%d)", (int) lig_ptr(s));
-                        } else if (type(s) == vlist_node || type(s) == hlist_node) {
-                            fprintf(stdout, "list_ptr(%d)", (int) list_ptr(s));
-                        }
-                        fprintf(stdout, "\n");
-                    } else {
-                        if ((type(s) != penalty_node) && (type(s) != math_node) && (type(s) != kern_node)) {
-                            fprintf(stdout, "  pointed to from %s node %d\n",
-                                get_node_name(type(s), subtype(s)), (int) s);
-                        }
-                    }
-                }
-            }
-        }
-    }
-}
-
-#endif
-
-static int free_error(halfword p)
-{
-    if (p > my_prealloc && p < var_mem_max) {
-#ifdef CHECK_NODE_USAGE
-        int i;
-        if (varmem_sizes[p] == 0) {
-            check_static_node_mem();
-            for (i = (my_prealloc + 1); i < var_mem_max; i++) {
-                if (varmem_sizes[i] > 0) {
-                    check_node(i);
-                }
-            }
-            test_count++;
-            if (type(p) == glyph_node) {
-                formatted_error("nodes", "attempt to double-free glyph (%c) node %d, ignored", (int) character(p), (int) p);
-            } else {
-                formatted_error("nodes", "attempt to double-free %s node %d, ignored", get_node_name(type(p), subtype(p)), (int) p);
-            }
-            node_mem_dump(p);
-            return 1;
-        }
-#endif
-    } else {
-        formatted_error("nodes", "attempt to free an impossible node %d", (int) p);
-        return 1;
-    }
-    return 0;
-}
-
-@ @c
-static int copy_error(halfword p)
-{
-    if (p >= 0 && p < var_mem_max) {
-#ifdef CHECK_NODE_USAGE
-        if (p > my_prealloc && varmem_sizes[p] == 0) {
-            if (type(p) == glyph_node) {
-                formatted_warning("nodes", "attempt to copy free glyph (%c) node %d, ignored", (int) character(p), (int) p);
-            } else {
-                formatted_warning("nodes", "attempt to copy free %s node %d, ignored", get_node_name(type(p), subtype(p)), (int) p);
-            }
-            return 1;
-        }
-#endif
-    } else {
-        formatted_error("nodes", "attempt to copy an impossible node %d", (int) p);
-        return 1;
-    }
-    return 0;
-}
-
-@ @c
-halfword new_node(int i, int j)
-{
-    int s = get_node_size(i, j);
-    halfword n = get_node(s);
-    /*
-        It should be possible to do this memset at |free_node()|.
-
-        Both type() and subtype() will be set below, and vlink() is
-        set to null by |get_node()|, so we can do we clearing one
-        word less than |s|
-    */
-    (void) memset((void *) (varmem + n + 1), 0, (sizeof(memory_word) * ((unsigned) s - 1)));
-    switch (i) {
-        case glyph_node:
-            init_lang_data(n);
-            break;
-        case hlist_node:
-        case vlist_node:
-            box_dir(n) = -1;
-            break;
-        case disc_node:
-            pre_break(n) = pre_break_head(n);
-            type(pre_break(n)) = nesting_node;
-            subtype(pre_break(n)) = pre_break_head(0);
-            post_break(n) = post_break_head(n);
-            type(post_break(n)) = nesting_node;
-            subtype(post_break(n)) = post_break_head(0);
-            no_break(n) = no_break_head(n);
-            type(no_break(n)) = nesting_node;
-            subtype(no_break(n)) = no_break_head(0);
-            break;
-        case rule_node:
-            depth(n) = null_flag;
-            height(n) = null_flag;
-            width(n) = null_flag;
-            rule_dir(n) = -1;
-            rule_index(n) = 0;
-            rule_transform(n) = 0;
-            break;
-        case whatsit_node:
-            if (j == open_node) {
-                open_name(n) = get_nullstr();
-                open_area(n) = open_name(n);
-                open_ext(n) = open_name(n);
-            }
-            break;
-        case unset_node:
-            width(n) = null_flag;
-            break;
-        case pseudo_line_node:
-        case shape_node:
-            /* this is a trick that makes |pseudo_files| slightly slower,
-             but the overall allocation faster then an explicit test
-             at the top of |new_node()|.
-             */
-            if (j>0) {
-              free_node(n, variable_node_size);
-              n = slow_get_node(j);
-              (void) memset((void *) (varmem + n + 1), 0, (sizeof(memory_word) * ((unsigned) j - 1)));
-            }
-            break;
-        default:
-            break;
-    }
-    if (synctex_par) {
-        /* handle synctex extension */
-        switch (i) {
-            case math_node:
-                synctex_tag_math(n) = cur_input.synctex_tag_field;
-                synctex_line_math(n) = line;
-                break;
-            case glue_node:
-                synctex_tag_glue(n) = cur_input.synctex_tag_field;
-                synctex_line_glue(n) = line;
-                break;
-            case kern_node:
-                if (j != 0) {
-                    synctex_tag_kern(n) = cur_input.synctex_tag_field;
-                    synctex_line_kern(n) = line;
-                }
-                break;
-            case hlist_node:
-            case vlist_node:
-            case unset_node:
-                synctex_tag_box(n) = cur_input.synctex_tag_field;
-                synctex_line_box(n) = line;
-                break;
-            case rule_node:
-                synctex_tag_rule(n) = cur_input.synctex_tag_field;
-                synctex_line_rule(n) = line;
-                break;
-        }
-    }
-    /* take care of attributes */
-    if (nodetype_has_attributes(i)) {
-        build_attribute_list(n);
-        /* lua_properties_set */
-    }
-    type(n) = (quarterword) i;
-    subtype(n) = (quarterword) j;
-    return n;
-}
-
-halfword raw_glyph_node(void)
-{
-    register halfword n = get_node(glyph_node_size);
-    (void) memset((void *) (varmem + n + 1), 0, (sizeof(memory_word) * (glyph_node_size - 1)));
-    type(n) = glyph_node;
-    subtype(n) = 0;
-    return n;
-}
-
-halfword new_glyph_node(void)
-{
-    register halfword n = get_node(glyph_node_size);
-    (void) memset((void *) (varmem + n + 1), 0, (sizeof(memory_word) * (glyph_node_size - 1)));
-    type(n) = glyph_node;
-    subtype(n) = 0;
-    build_attribute_list(n);
-    /* lua_properties_set */
-    return n;
-}
-
-@ makes a duplicate of the node list that starts at |p| and returns a
-pointer to the new list
-
- at c
-halfword do_copy_node_list(halfword p, halfword end)
-{
-    halfword q = null; /* previous position in new list */
-    halfword h = null; /* head of the list */
-    register halfword s ;
-    lua_properties_push; /* saves stack and time */
-    while (p != end) {
-        s = copy_node(p);
-        if (h == null) {
-            h = s;
-        } else {
-            couple_nodes(q, s);
-        }
-        q = s;
-        p = vlink(p);
-    }
-    lua_properties_pop; /* saves stack and time */
-    return h;
-}
-
-halfword copy_node_list(halfword p)
-{
-    return do_copy_node_list(p, null);
-}
-
-#define copy_sub_list(target,source) do { \
-     if (source != null) { \
-         s = do_copy_node_list(source, null); \
-         target = s; \
-     } else { \
-         target = null; \
-     } \
- } while (0)
-
-#define copy_sub_node(target,source) do { \
-    if (source != null) { \
-        s = copy_node(source); \
-        target = s ; \
-    } else { \
-        target = null; \
-    } \
-} while (0)
-
-@ make a dupe of a single node
-
- at c
-static void copy_node_wrapup_core(halfword p, halfword r)
-{
-    halfword s ;
-    switch (subtype(p)) {
-        case write_node:
-        case special_node:
-            add_token_ref(write_tokens(p));
-            break;
-        case late_lua_node:
-            copy_late_lua(r, p);
-            break;
-        case user_defined_node:
-            switch (user_node_type(p)) {
-            case 'a':
-                add_node_attr_ref(user_node_value(p));
-                break;
-            case 'l':
-                copy_user_lua(r, p);
-                break;
-            case 'n':
-                s = copy_node_list(user_node_value(p));
-                user_node_value(r) = s;
-                break;
-            case 's':
-                /* |add_string_ref(user_node_value(p));| */
-                break;
-            case 't':
-                add_token_ref(user_node_value(p));
-                break;
-            }
-            break;
-        default:
-            break ;
-    }
-}
-
-void copy_node_wrapup_dvi(halfword p, halfword r)
-{
-}
-
-void copy_node_wrapup_pdf(halfword p, halfword r)
-{
-    switch(subtype(p)) {
-        case pdf_literal_node:
-            copy_pdf_literal(r, p);
-            break;
-        case pdf_colorstack_node:
-            if (pdf_colorstack_cmd(p) <= colorstack_data)
-                add_token_ref(pdf_colorstack_data(p));
-            break;
-        case pdf_setmatrix_node:
-            add_token_ref(pdf_setmatrix_data(p));
-            break;
-        case pdf_annot_node:
-            add_token_ref(pdf_annot_data(p));
-            break;
-        case pdf_start_link_node:
-            if (pdf_link_attr(r) != null)
-                add_token_ref(pdf_link_attr(r));
-            add_action_ref(pdf_link_action(r));
-            break;
-        case pdf_dest_node:
-            if (pdf_dest_named_id(p) > 0)
-                add_token_ref(pdf_dest_id(p));
-            break;
-        case pdf_thread_node:
-        case pdf_start_thread_node:
-            if (pdf_thread_named_id(p) > 0)
-                add_token_ref(pdf_thread_id(p));
-            if (pdf_thread_attr(p) != null)
-                add_token_ref(pdf_thread_attr(p));
-            break;
-        default:
-            break;
-    }
-}
-
-halfword copy_node(const halfword p)
-{
-    halfword r;                 /* current node being fabricated for new list */
-    halfword w;                 /* whatsit subtype */
-    halfword t;                 /* type of node */
-    register halfword s;        /* a helper variable for copying into variable mem  */
-    register int i;
-    if (copy_error(p)) {
-        r = new_node(temp_node, 0);
-        return r;
-    }
-    t = type(p);
-    i = get_node_size(t,subtype(p));
-    r = get_node(i);
-
-    (void) memcpy((void *) (varmem + r), (void *) (varmem + p), (sizeof(memory_word) * (unsigned) i));
-
-    /* possible speedup: */
-    /*
-        if t == glue_spec) {
-            return r;
-        }
-    */
-
-    if (synctex_par) {
-        /* handle synctex extension */
-        switch (t) {
-            case math_node:
-                synctex_tag_math(r) = cur_input.synctex_tag_field;
-                synctex_line_math(r) = line;
-                break;
-            case kern_node:
-                synctex_tag_kern(r) = cur_input.synctex_tag_field;
-                synctex_line_kern(r) = line;
-                break;
-        }
-    }
-    if (nodetype_has_attributes(t)) {
-        add_node_attr_ref(node_attr(p));
-        alink(r) = null;
-        lua_properties_copy(r,p);
-    }
-    vlink(r) = null;
-
-    switch (t) {
-        case glyph_node:
-            copy_sub_list(lig_ptr(r),lig_ptr(p)) ;
-            break;
-        case glue_node:
-            copy_sub_list(leader_ptr(r),leader_ptr(p)) ;
-            break;
-        case hlist_node:
-        case vlist_node:
-        case unset_node:
-            copy_sub_list(list_ptr(r),list_ptr(p)) ;
-            break;
-        case disc_node:
-            pre_break(r) = pre_break_head(r);
-            if (vlink_pre_break(p) != null) {
-                s = copy_node_list(vlink_pre_break(p));
-                alink(s) = pre_break(r);
-                tlink_pre_break(r) = tail_of_list(s);
-                vlink_pre_break(r) = s;
-            } else {
-                assert(tlink(pre_break(r)) == null);
-            }
-            post_break(r) = post_break_head(r);
-            if (vlink_post_break(p) != null) {
-                s = copy_node_list(vlink_post_break(p));
-                alink(s) = post_break(r);
-                tlink_post_break(r) = tail_of_list(s);
-                vlink_post_break(r) = s;
-            } else {
-                assert(tlink_post_break(r) == null);
-            }
-            no_break(r) = no_break_head(r);
-            if (vlink(no_break(p)) != null) {
-                s = copy_node_list(vlink_no_break(p));
-                alink(s) = no_break(r);
-                tlink_no_break(r) = tail_of_list(s);
-                vlink_no_break(r) = s;
-            } else {
-                assert(tlink_no_break(r) == null);
-            }
-            break;
-        case math_node:
-            break;
-        case ins_node:
-            copy_sub_list(ins_ptr(r),ins_ptr(p)) ;
-            break;
-        case margin_kern_node:
-            copy_sub_node(margin_char(r),margin_char(p));
-            break;
-        case mark_node:
-            add_token_ref(mark_ptr(p));
-            break;
-        case adjust_node:
-            copy_sub_list(adjust_ptr(r),adjust_ptr(p));
-            break;
-        case choice_node:
-            copy_sub_list(display_mlist(r),display_mlist(p)) ;
-            copy_sub_list(text_mlist(r),text_mlist(p)) ;
-            copy_sub_list(script_mlist(r),script_mlist(p)) ;
-            copy_sub_list(script_script_mlist(r),script_script_mlist(p)) ;
-            break;
-        case simple_noad:
-            copy_sub_list(nucleus(r),nucleus(p)) ;
-            copy_sub_list(subscr(r),subscr(p)) ;
-            copy_sub_list(supscr(r),supscr(p)) ;
-            break;
-        case radical_noad:
-            copy_sub_list(nucleus(r),nucleus(p)) ;
-            copy_sub_list(subscr(r),subscr(p)) ;
-            copy_sub_list(supscr(r),supscr(p)) ;
-            copy_sub_node(left_delimiter(r),left_delimiter(p)) ;
-            copy_sub_list(degree(r),degree(p)) ;
-            break;
-        case accent_noad:
-            copy_sub_list(nucleus(r),nucleus(p)) ;
-            copy_sub_list(subscr(r),subscr(p)) ;
-            copy_sub_list(supscr(r),supscr(p)) ;
-            copy_sub_list(top_accent_chr(r),top_accent_chr(p)) ;
-            copy_sub_list(bot_accent_chr(r),bot_accent_chr(p)) ;
-            copy_sub_list(overlay_accent_chr(r),overlay_accent_chr(p)) ;
-            break;
-        case fence_noad:
-            copy_sub_node(delimiter(r),delimiter(p)) ;
-            break;
-        case sub_box_node:
-        case sub_mlist_node:
-            copy_sub_list(math_list(r),math_list(p)) ;
-            break;
-        case fraction_noad:
-            copy_sub_list(numerator(r),numerator(p)) ;
-            copy_sub_list(denominator(r),denominator(p)) ;
-            copy_sub_node(left_delimiter(r),left_delimiter(p)) ;
-            copy_sub_node(right_delimiter(r),right_delimiter(p)) ;
-            break;
-        case glue_spec_node:
-        case dir_node:
-        case local_par_node:
-        case boundary_node:
-            break;
-        case whatsit_node:
-            w = subtype(p) ;
-            if (w >= backend_first_pdf_whatsit) {
-                copy_node_wrapup_pdf(p,r);
-            } else if (w >= backend_first_dvi_whatsit) {
-                copy_node_wrapup_dvi(p,r);
-            } else {
-                copy_node_wrapup_core(p,r);
-            }
-            break;
-    }
-    return r;
-}
-
-/* x */
-
-#define free_sub_list(source) if (source != null) flush_node_list(source);
-#define free_sub_node(source) if (source != null) flush_node(source);
-
-@ @c
-
-static void flush_node_wrapup_core(halfword p)
-{
-    switch (subtype(p)) {
-        case open_node:
-        case write_node:
-        case close_node:
-        case save_pos_node:
-            break;
-        case special_node:
-            delete_token_ref(write_tokens(p));
-            break;
-        case late_lua_node:
-            free_late_lua(p);
-            break;
-        case user_defined_node:
-            switch (user_node_type(p)) {
-            case 'a':
-                delete_attribute_ref(user_node_value(p));
-                break;
-            case 'd':
-                break;
-            case 'l':
-                free_user_lua(user_node_value(p));
-                break;
-            case 'n':
-                flush_node_list(user_node_value(p));
-                break;
-            case 's':
-                /* |delete_string_ref(user_node_value(p));| *//* if this was mpost .. */
-                break;
-            case 't':
-                delete_token_ref(user_node_value(p));
-                break;
-            default:
-                {
-                    const char *hlp[] = {
-                        "The type of the value in a user defined whatsit node should be one",
-                        "of 'a' (attribute list), 'd' (number), 'n' (node list), 's' (string),",
-                        "or 't' (tokenlist). Yours has an unknown type, and therefore I don't",
-                        "know how to free the node's value. A memory leak may result.",
-                        NULL
-                    };
-                    tex_error("Unidentified user defined whatsit", hlp);
-                }
-                break;
-            }
-            break;
-    }
-}
-
-void flush_node_wrapup_dvi(halfword p)
-{
-}
-
-void flush_node_wrapup_pdf(halfword p)
-{
-    switch(subtype(p)) {
-        case pdf_save_node:
-        case pdf_restore_node:
-        case pdf_refobj_node:
-        case pdf_end_link_node:
-        case pdf_end_thread_node:
-            break;
-        case pdf_literal_node:
-            free_pdf_literal(p);
-            break;
-        case pdf_colorstack_node:
-            if (pdf_colorstack_cmd(p) <= colorstack_data)
-                delete_token_ref(pdf_colorstack_data(p));
-            break;
-        case pdf_setmatrix_node:
-            delete_token_ref(pdf_setmatrix_data(p));
-            break;
-        case pdf_annot_node:
-            delete_token_ref(pdf_annot_data(p));
-            break;
-        case pdf_link_data_node:
-            break;
-        case pdf_start_link_node:
-            if (pdf_link_attr(p) != null)
-                delete_token_ref(pdf_link_attr(p));
-            delete_action_ref(pdf_link_action(p));
-            break;
-        case pdf_dest_node:
-            if (pdf_dest_named_id(p) > 0)
-                delete_token_ref(pdf_dest_id(p));
-            break;
-        case pdf_action_node:
-            if (pdf_action_type(p) == pdf_action_user) {
-                delete_token_ref(pdf_action_tokens(p));
-            } else {
-                if (pdf_action_file(p) != null)
-                    delete_token_ref(pdf_action_file(p));
-                if (pdf_action_type(p) == pdf_action_page)
-                    delete_token_ref(pdf_action_tokens(p));
-                else if (pdf_action_named_id(p) > 0)
-                    delete_token_ref(pdf_action_id(p));
-            }
-            break;
-        case pdf_thread_data_node:
-            break;
-        case pdf_thread_node:
-        case pdf_start_thread_node:
-            if (pdf_thread_named_id(p) > 0)
-                delete_token_ref(pdf_thread_id(p));
-            if (pdf_thread_attr(p) != null)
-                delete_token_ref(pdf_thread_attr(p));
-            break;
-    }
-}
-
-void flush_node(halfword p)
-{
-    halfword w;
-    if (p == null)              /* legal, but no-op */
-        return;
-    if (free_error(p))
-        return;
-    switch (type(p)) {
-        case glyph_node:
-            free_sub_list(lig_ptr(p));
-            break;
-        case glue_node:
-            free_sub_list(leader_ptr(p));
-            break;
-        case hlist_node:
-        case vlist_node:
-        case unset_node:
-            free_sub_list(list_ptr(p));
-            break;
-        case disc_node:
-            /* watch the start at temp node hack */
-            free_sub_list(vlink(pre_break(p)));
-            free_sub_list(vlink(post_break(p)));
-            free_sub_list(vlink(no_break(p)));
-            break;
-        case rule_node:
-        case kern_node:
-        case penalty_node:
-        case math_node:
-            break;
-        case glue_spec_node:
-            /* this allows free-ing of lua-allocated glue specs */
-//if (valid_node(p)) {
-//    free_node(p, subtype(p));
-//}
-//            return ;
-            break ;
-        case dir_node:
-        case local_par_node:
-        case boundary_node:
-            break;
-        case whatsit_node:
-            w = subtype(p) ;
-            if (w >= backend_first_pdf_whatsit) {
-                flush_node_wrapup_pdf(p);
-            } else if (w >= backend_first_dvi_whatsit) {
-                flush_node_wrapup_dvi(p);
-            } else {
-                flush_node_wrapup_core(p);
-            }
-            break;
-        case ins_node:
-            flush_node_list(ins_ptr(p));
-            break;
-        case margin_kern_node:
-            flush_node(margin_char(p));
-            break;
-        case mark_node:
-            delete_token_ref(mark_ptr(p));
-            break;
-        case adjust_node:
-            flush_node_list(adjust_ptr(p));
-            break;
-        case style_node:           /* nothing to do */
-            break;
-        case choice_node:
-            free_sub_list(display_mlist(p));
-            free_sub_list(text_mlist(p));
-            free_sub_list(script_mlist(p));
-            free_sub_list(script_script_mlist(p));
-            break;
-        case simple_noad:
-            free_sub_list(nucleus(p));
-            free_sub_list(subscr(p));
-            free_sub_list(supscr(p));
-            break;
-        case radical_noad:
-            free_sub_list(nucleus(p));
-            free_sub_list(subscr(p));
-            free_sub_list(supscr(p));
-            free_sub_node(left_delimiter(p));
-            free_sub_list(degree(p));
-            break;
-        case accent_noad:
-            free_sub_list(nucleus(p));
-            free_sub_list(subscr(p));
-            free_sub_list(supscr(p));
-            free_sub_list(top_accent_chr(p));
-            free_sub_list(bot_accent_chr(p));
-            free_sub_list(overlay_accent_chr(p));
-            break;
-        case fence_noad:
-            free_sub_list(delimiter(p));
-            break;
-        case delim_node:           /* nothing to do */
-        case math_char_node:
-        case math_text_char_node:
-            break;
-        case sub_box_node:
-        case sub_mlist_node:
-            free_sub_list(math_list(p));
-            break;
-        case fraction_noad:
-            free_sub_list(numerator(p));
-            free_sub_list(denominator(p));
-            free_sub_node(left_delimiter(p));
-            free_sub_node(right_delimiter(p));
-            break;
-        case pseudo_file_node:
-            free_sub_list(pseudo_lines(p));
-            break;
-        case pseudo_line_node:
-        case shape_node:
-            free_node(p, subtype(p));
-            return;
-            break;
-        case align_stack_node:
-        case span_node:
-        case movement_node:
-        case if_node:
-        case nesting_node:
-        case unhyphenated_node:
-        case hyphenated_node:
-        case delta_node:
-        case passive_node:
-        case inserting_node:
-        case split_up_node:
-        case expr_node:
-        case attribute_node:
-        case attribute_list_node:
-        case temp_node:
-            break;
-        default:
-            formatted_error("nodes","flushing weird node type %d", type(p));
-            return;
-    }
-    if (nodetype_has_attributes(type(p))) {
-        delete_attribute_ref(node_attr(p));
-        lua_properties_reset(p);
-    }
-    free_node(p, get_node_size(type(p), subtype(p)));
-    return;
-}
-
-@ @c
-void flush_node_list(halfword pp)
-{                               /* erase list of nodes starting at |p| */
-    register halfword p = pp;
-    if (p == null)              /* legal, but no-op */
-        return;
-    if (free_error(p))
-        return;
-    lua_properties_push; /* saves stack and time */
-    while (p != null) {
-        register halfword q = vlink(p);
-        flush_node(p);
-        p = q;
-    }
-    lua_properties_pop; /* saves stack and time */
-}
-
-@ @c
-static void check_node_wrapup_core(halfword p)
-{
-    switch (subtype(p)) {
-        /* frontend code */
-        case special_node:
-            check_token_ref(write_tokens(p));
-            break;
-        case user_defined_node:
-            switch (user_node_type(p)) {
-                case 'a':
-                    check_attribute_ref(user_node_value(p));
-                    break;
-                case 't':
-                    check_token_ref(user_node_value(p));
-                    break;
-                case 'n':
-                    dorangetest(p, user_node_value(p), var_mem_max);
-                    break;
-                case 's':
-                case 'd':
-                    break;
-                default:
-                    confusion("unknown user node type");
-                    break;
-            }
-            break;
-        case open_node:
-        case write_node:
-        case close_node:
-        case save_pos_node:
-            break;
-    }
-}
-
-void check_node_wrapup_dvi(halfword p)
-{
-}
-
-void check_node_wrapup_pdf(halfword p)
-{
-    switch (subtype(p)) {
-        case pdf_literal_node:
-            if (pdf_literal_type(p) == normal)
-                check_token_ref(pdf_literal_data(p));
-            break;
-        case pdf_colorstack_node:
-            if (pdf_colorstack_cmd(p) <= colorstack_data)
-                check_token_ref(pdf_colorstack_data(p));
-            break;
-        case pdf_setmatrix_node:
-            check_token_ref(pdf_setmatrix_data(p));
-            break;
-        case late_lua_node:
-            if (late_lua_name(p) > 0)
-                check_token_ref(late_lua_name(p));
-            if (late_lua_type(p) == normal)
-                check_token_ref(late_lua_data(p));
-            break;
-        case pdf_annot_node:
-            check_token_ref(pdf_annot_data(p));
-            break;
-        case pdf_start_link_node:
-            if (pdf_link_attr(p) != null)
-                check_token_ref(pdf_link_attr(p));
-            check_action_ref(pdf_link_action(p));
-            break;
-        case pdf_dest_node:
-            if (pdf_dest_named_id(p) > 0)
-                check_token_ref(pdf_dest_id(p));
-            break;
-        case pdf_thread_node:
-        case pdf_start_thread_node:
-            if (pdf_thread_named_id(p) > 0)
-                check_token_ref(pdf_thread_id(p));
-            if (pdf_thread_attr(p) != null)
-                check_token_ref(pdf_thread_attr(p));
-            break;
-        case pdf_save_node:
-        case pdf_restore_node:
-        case pdf_refobj_node:
-        case pdf_end_link_node:
-        case pdf_end_thread_node:
-            break;
-        default:
-            confusion("wrapup pdf nodes");
-            break;
-    }
-}
-
-void check_node(halfword p)
-{
-    halfword w ;
-    switch (type(p)) {
-        case glyph_node:
-            dorangetest(p, lig_ptr(p), var_mem_max);
-            break;
-        case glue_node:
-            dorangetest(p, leader_ptr(p), var_mem_max);
-            break;
-        case hlist_node:
-        case vlist_node:
-        case unset_node:
-        case align_record_node:
-            dorangetest(p, list_ptr(p), var_mem_max);
-            break;
-        case ins_node:
-            dorangetest(p, ins_ptr(p), var_mem_max);
-            break;
-        case whatsit_node:
-            w = subtype(p) ;
-            if (w >= backend_first_pdf_whatsit) {
-                check_node_wrapup_pdf(p);
-            } else if (w >= backend_first_dvi_whatsit) {
-                check_node_wrapup_dvi(p);
-            } else {
-                check_node_wrapup_core(p);
-            }
-            break;
-        case margin_kern_node:
-            check_node(margin_char(p));
-            break;
-        case math_node:
-            break;
-        case disc_node:
-            dorangetest(p, vlink(pre_break(p)), var_mem_max);
-            dorangetest(p, vlink(post_break(p)), var_mem_max);
-            dorangetest(p, vlink(no_break(p)), var_mem_max);
-            break;
-        case adjust_node:
-            dorangetest(p, adjust_ptr(p), var_mem_max);
-            break;
-        case pseudo_file_node:
-            dorangetest(p, pseudo_lines(p), var_mem_max);
-            break;
-        case pseudo_line_node:
-        case shape_node:
-            break;
-        case choice_node:
-            dorangetest(p, display_mlist(p), var_mem_max);
-            dorangetest(p, text_mlist(p), var_mem_max);
-            dorangetest(p, script_mlist(p), var_mem_max);
-            dorangetest(p, script_script_mlist(p), var_mem_max);
-            break;
-        case fraction_noad:
-            dorangetest(p, numerator(p), var_mem_max);
-            dorangetest(p, denominator(p), var_mem_max);
-            dorangetest(p, left_delimiter(p), var_mem_max);
-            dorangetest(p, right_delimiter(p), var_mem_max);
-            break;
-        case simple_noad:
-            dorangetest(p, nucleus(p), var_mem_max);
-            dorangetest(p, subscr(p), var_mem_max);
-            dorangetest(p, supscr(p), var_mem_max);
-            break;
-        case radical_noad:
-            dorangetest(p, nucleus(p), var_mem_max);
-            dorangetest(p, subscr(p), var_mem_max);
-            dorangetest(p, supscr(p), var_mem_max);
-            dorangetest(p, degree(p), var_mem_max);
-            dorangetest(p, left_delimiter(p), var_mem_max);
-            break;
-        case accent_noad:
-            dorangetest(p, nucleus(p), var_mem_max);
-            dorangetest(p, subscr(p), var_mem_max);
-            dorangetest(p, supscr(p), var_mem_max);
-            dorangetest(p, top_accent_chr(p), var_mem_max);
-            dorangetest(p, bot_accent_chr(p), var_mem_max);
-            dorangetest(p, overlay_accent_chr(p), var_mem_max);
-            break;
-        case fence_noad:
-            dorangetest(p, delimiter(p), var_mem_max);
-            break;
-        /*
-        case rule_node:
-        case kern_node:
-        case penalty_node:
-        case mark_node:
-        case style_node:
-        case attribute_list_node:
-        case attribute_node:
-        case glue_spec_node:
-        case temp_node:
-        case align_stack_node:
-        case movement_node:
-        case if_node:
-        case nesting_node:
-        case span_node:
-        case unhyphenated_node:
-        case hyphenated_node:
-        case delta_node:
-        case passive_node:
-        case expr_node:
-        case dir_node:
-        case boundary_node:
-        case local_par_node:
-            break;
-        default:
-            fprintf(stdout, "check_node: type is %d\n", type(p));
-        */
-    }
-}
-
-@ @c
-void fix_node_list(halfword head)
-{
-    halfword p, q;
-    if (head == null)
-        return;
-    p = head;
-    q = vlink(p);
-    while (q != null) {
-        alink(q) = p;
-        p = q;
-        q = vlink(p);
-    }
-}
-
-@ @c
-halfword get_node(int s)
-{
-    register halfword r;
-
-    if (s < MAX_CHAIN_SIZE) {
-        r = free_chain[s];
-        if (r != null) {
-            free_chain[s] = vlink(r);
-#ifdef CHECK_NODE_USAGE
-            varmem_sizes[r] = (char) s;
-#endif
-            vlink(r) = null;
-            var_used += s; /* maintain usage statistics */
-            return r;
-        }
-        /* this is the end of the 'inner loop' */
-        return slow_get_node(s);
-    } else {
-        normal_error("nodes","there is a problem in getting a node, case 1");
-        return null;
-    }
-}
-
-@ @c
-void free_node(halfword p, int s)
-{
-    if (p <= my_prealloc) {
-        formatted_error("nodes", "node number %d of type %d should not be freed", (int) p, type(p));
-        return;
-    }
-#ifdef CHECK_NODE_USAGE
-    varmem_sizes[p] = 0;
-#endif
-    if (s < MAX_CHAIN_SIZE) {
-        vlink(p) = free_chain[s];
-        free_chain[s] = p;
-    } else {
-        /* todo ? it is perhaps possible to merge this node with an existing rover */
-        node_size(p) = s;
-        vlink(p) = rover;
-        while (vlink(rover) != vlink(p)) {
-            rover = vlink(rover);
-        }
-        vlink(rover) = p;
-    }
-    /* maintain statistics */
-    var_used -= s;
-}
-
-@ @c
-static void free_node_chain(halfword q, int s)
-{
-    register halfword p = q;
-    while (vlink(p) != null) {
-#ifdef CHECK_NODE_USAGE
-        varmem_sizes[p] = 0;
-#endif
-        var_used -= s;
-        p = vlink(p);
-    }
-    var_used -= s;
-#ifdef CHECK_NODE_USAGE
-    varmem_sizes[p] = 0;
-#endif
-    vlink(p) = free_chain[s];
-    free_chain[s] = q;
-}
-
-@ At the start of the node memory area we reserve some special nodes,
-for instance frequently used glue specifications. We could as well just
-use new_glue here but for the moment we stick to the traditional approach.
-
- at c
-#define initialize_glue(n,wi,st,sh,sto,sho) \
-    vlink(n) = null; \
-    type(n) = glue_spec_node; \
-    width(n) = wi; \
-    stretch(n) = st; \
-    shrink(n) = sh; \
-    stretch_order(n) = sto; \
-    shrink_order(n) = sho;
-
-#define initialize_whatever(n,t) \
-    vinfo(n) = 0; \
-    type(n) = t; \
-    vlink(n) = null; \
-    alink(n) = null;
-
-#define initialize_point(n) \
-    type(n) = glyph_node; \
-    subtype(n) = 0; \
-    vlink(n) = null; \
-    vinfo(n + 1) = null; \
-    alink(n) = null; \
-    font(n) = 0; \
-    character(n) = '.'; \
-    vinfo(n + 3) = 0; \
-    vlink(n + 3) = 0; \
-    vinfo(n + 4) = 0; \
-    vlink(n + 4) = 0;
-
-void init_node_mem(int t)
-{
-    my_prealloc = var_mem_stat_max;
-
-    varmem = (memory_word *) realloc((void *) varmem, sizeof(memory_word) * (unsigned) t);
-    if (varmem == NULL) {
-        overflow("node memory size", (unsigned) var_mem_max);
-    }
-    memset((void *) (varmem), 0, (unsigned) t * sizeof(memory_word));
-#ifdef CHECK_NODE_USAGE
-    varmem_sizes = (char *) realloc(varmem_sizes, sizeof(char) * (unsigned) t);
-    if (varmem_sizes == NULL) {
-        overflow("node memory size", (unsigned) var_mem_max);
-    }
-    memset((void *) varmem_sizes, 0, sizeof(char) * (unsigned) t);
-#endif
-    var_mem_max = t;
-    rover = var_mem_stat_max + 1;
-    vlink(rover) = rover;
-    node_size(rover) = (t - rover);
-    var_used = 0;
-
-    /* initialize static glue specs */
-
-    initialize_glue(zero_glue,0,0,0,0,0);
-    initialize_glue(sfi_glue,0,0,0,sfi,0);
-    initialize_glue(fil_glue,0,unity,0,fil,0);
-    initialize_glue(fill_glue,0,unity,0,fill,0);
-    initialize_glue(ss_glue,0,unity,unity,fil,fil);
-    initialize_glue(fil_neg_glue,0,-unity,0,fil,0);
-
-    /* initialize node list heads */
-
-    initialize_whatever(page_ins_head,temp_node);
-    initialize_whatever(contrib_head,temp_node);
-    initialize_whatever(page_head,temp_node);
-    initialize_whatever(temp_head,temp_node);
-    initialize_whatever(hold_head,temp_node);
-    initialize_whatever(adjust_head,temp_node);
-    initialize_whatever(pre_adjust_head,temp_node);
-    initialize_whatever(align_head,temp_node);
-
-    initialize_whatever(active,unhyphenated_node);
-    initialize_whatever(end_span,span_node);
-
-    initialize_point(begin_point);
-    initialize_point(end_point);
-}
-
-@ @c
-void dump_node_mem(void)
-{
-    dump_int(var_mem_max);
-    dump_int(rover);
-    dump_things(varmem[0], var_mem_max);
-#ifdef CHECK_NODE_USAGE
-    dump_things(varmem_sizes[0], var_mem_max);
-#endif
-    dump_things(free_chain[0], MAX_CHAIN_SIZE);
-    dump_int(var_used);
-    dump_int(my_prealloc);
-}
-
-@ it makes sense to enlarge the varmem array immediately
- at c
-
-void undump_node_mem(void)
-{
-    int x;
-    undump_int(x);
-    undump_int(rover);
-    var_mem_max = (x < 100000 ? 100000 : x);
-    varmem = xmallocarray(memory_word, (unsigned) var_mem_max);
-    undump_things(varmem[0], x);
-#ifdef CHECK_NODE_USAGE
-    varmem_sizes = xmallocarray(char, (unsigned) var_mem_max);
-    memset((void *) varmem_sizes, 0, (unsigned) var_mem_max * sizeof(char));
-    undump_things(varmem_sizes[0], x);
-#endif
-    undump_things(free_chain[0], MAX_CHAIN_SIZE);
-    undump_int(var_used);
-    undump_int(my_prealloc);
-    if (var_mem_max > x) {
-        /* todo ? it is perhaps possible to merge the new node with an existing rover */
-        vlink(x) = rover;
-        node_size(x) = (var_mem_max - x);
-        while (vlink(rover) != vlink(x)) {
-            rover = vlink(rover);
-        }
-        vlink(rover) = x;
-    }
-}
-
-@ @c
-halfword slow_get_node(int s)
-{
-    register int t;
-
-  RETRY:
-    t = node_size(rover);
-    if (vlink(rover) < var_mem_max && vlink(rover) != 0) {
-        if (t > s) {
-            /* allocating from the bottom helps decrease page faults */
-            register halfword r = rover;
-            rover += s;
-            vlink(rover) = vlink(r);
-            node_size(rover) = node_size(r) - s;
-            if (vlink(rover) != r) {        /* list is longer than one */
-                halfword q = r;
-                while (vlink(q) != r) {
-                    q = vlink(q);
-                }
-                vlink(q) += s;
-            } else {
-                vlink(rover) += s;
-            }
-            if (vlink(rover) < var_mem_max) {
-#ifdef CHECK_NODE_USAGE
-                varmem_sizes[r] = (char) (s > 127 ? 127 : s);
-#endif
-                vlink(r) = null;
-                var_used += s;          /* maintain usage statistics */
-                return r;               /* this is the only exit */
-            } else {
-                normal_error("nodes","there is a problem in getting a node, case 2");
-                return null;
-            }
-        } else {
-            /* attempt to keep the free list small */
-            int x;
-            if (vlink(rover) != rover) {
-                if (t < MAX_CHAIN_SIZE) {
-                    halfword l = vlink(rover);
-                    vlink(rover) = free_chain[t];
-                    free_chain[t] = rover;
-                    rover = l;
-                    while (vlink(l) != free_chain[t]) {
-                        l = vlink(l);
-                    }
-                    vlink(l) = rover;
-                    goto RETRY;
-                } else {
-                    halfword l = rover;
-                    while (vlink(rover) != l) {
-                        if (node_size(rover) > s) {
-                            goto RETRY;
-                        }
-                        rover = vlink(rover);
-                    }
-                }
-            }
-            /* if we are still here, it was apparently impossible to get a match */
-            x = (var_mem_max >> 2) + s;
-            varmem = (memory_word *) realloc((void *) varmem, sizeof(memory_word) * (unsigned) (var_mem_max + x));
-            if (varmem == NULL) {
-                overflow("node memory size", (unsigned) var_mem_max);
-            }
-            memset((void *) (varmem + var_mem_max), 0, (unsigned) x * sizeof(memory_word));
-#ifdef CHECK_NODE_USAGE
-            varmem_sizes = (char *) realloc(varmem_sizes, sizeof(char) * (unsigned) (var_mem_max + x));
-            if (varmem_sizes == NULL) {
-                overflow("node memory size", (unsigned) var_mem_max);
-            }
-            memset((void *) (varmem_sizes + var_mem_max), 0, (unsigned) (x) * sizeof(char));
-#endif
-            /* todo ? it is perhaps possible to merge the new memory with an existing rover */
-            vlink(var_mem_max) = rover;
-            node_size(var_mem_max) = x;
-            while (vlink(rover) != vlink(var_mem_max)) {
-                rover = vlink(rover);
-            }
-            vlink(rover) = var_mem_max;
-            rover = var_mem_max;
-            var_mem_max += x;
-            goto RETRY;
-        }
-    } else {
-        normal_error("nodes","there is a problem in getting a node, case 3");
-        return null;
-    }
-}
-
-@ @c
-char *sprint_node_mem_usage(void)
-{
-    char *s;
-#ifdef CHECK_NODE_USAGE
-    char *ss;
-    int i;
-    int b = 0;
-    char msg[256];
-    int node_counts[last_normal_node + last_whatsit_node + 2] = { 0 };
-    s = strdup("");
-    for (i = (var_mem_max - 1); i > my_prealloc; i--) {
-        if (varmem_sizes[i] > 0) {
-            if (type(i) > last_normal_node + last_whatsit_node) {
-                node_counts[last_normal_node + last_whatsit_node + 1]++;
-            } else if (type(i) == whatsit_node) {
-                node_counts[(subtype(i) + last_normal_node + 1)]++;
-            } else {
-                node_counts[type(i)]++;
-            }
-        }
-    }
-    for (i = 0; i < last_normal_node + last_whatsit_node + 2; i++) {
-        if (node_counts[i] > 0) {
-            int j =
-                (i > (last_normal_node + 1) ? (i - last_normal_node - 1) : 0);
-            snprintf(msg, 255, "%s%d %s", (b ? ", " : ""), (int) node_counts[i],
-                     get_node_name((i > last_normal_node ? whatsit_node : i), j));
-            ss = xmalloc((unsigned) (strlen(s) + strlen(msg) + 1));
-            strcpy(ss, s);
-            strcat(ss, msg);
-            free(s);
-            s = ss;
-            b = 1;
-        }
-    }
-#else
-    s = strdup("");
-#endif
-    return s;
-}
-
-@ @c
-halfword list_node_mem_usage(void)
-{
-    halfword q = null;
-#ifdef CHECK_NODE_USAGE
-    halfword p = null;
-    halfword i, j;
-    char *saved_varmem_sizes = xmallocarray(char, (unsigned) var_mem_max);
-    memcpy(saved_varmem_sizes, varmem_sizes, (size_t) var_mem_max);
-    for (i = my_prealloc + 1; i < (var_mem_max - 1); i++) {
-        if (saved_varmem_sizes[i] > 0) {
-            j = copy_node(i);
-            if (p == null) {
-                q = j;
-            } else {
-                vlink(p) = j;
-            }
-            p = j;
-        }
-    }
-    free(saved_varmem_sizes);
-#endif
-    return q;
-}
-
-@ @c
-void print_node_mem_stats(void)
-{
-    int i, b;
-    halfword j;
-    char msg[256];
-    char *s;
-    int free_chain_counts[MAX_CHAIN_SIZE] = { 0 };
-    snprintf(msg, 255, " %d words of node memory still in use:", (int) (var_used + my_prealloc));
-    tprint_nl(msg);
-    s = sprint_node_mem_usage();
-    tprint_nl("   ");
-    tprint(s);
-    free(s);
-    tprint(" nodes");
-    tprint_nl("   avail lists: ");
-    b = 0;
-    for (i = 1; i < MAX_CHAIN_SIZE; i++) {
-        for (j = free_chain[i]; j != null; j = vlink(j))
-            free_chain_counts[i]++;
-        if (free_chain_counts[i] > 0) {
-            snprintf(msg, 255, "%s%d:%d", (b ? "," : ""), i, (int) free_chain_counts[i]);
-            tprint(msg);
-            b = 1;
-        }
-    }
-    /* newline, if needed */
-    print_nlp();
-}
-
-/* this belongs in the web but i couldn't find the correct syntactic place */
-
-halfword new_span_node(halfword n, int s, scaled w)
-{
-    halfword p = new_node(span_node, 0);
-    span_link(p) = n;
-    span_span(p) = s;
-    width(p) = w;
-    return p;
-}
-
-@* Attribute stuff.
-
- at c
-static halfword new_attribute_node(unsigned int i, int v)
-{
-    register halfword r = get_node(attribute_node_size);
-    type(r) = attribute_node;
-    attribute_id(r) = (halfword) i;
-    attribute_value(r) = v;
-    /* not used but nicer in print */
-    subtype(r) = 0;
-    return r;
-}
-
-@ @c
-halfword copy_attribute_list(halfword n)
-{
-    halfword q = get_node(attribute_node_size);
-    register halfword p = q;
-    type(p) = attribute_list_node;
-    attr_list_ref(p) = 0;
-    n = vlink(n);
-    while (n != null) {
-        register halfword r = get_node(attribute_node_size);
-        /* the link will be fixed automatically in the next loop */
-        (void) memcpy((void *) (varmem + r), (void *) (varmem + n),
-                      (sizeof(memory_word) * attribute_node_size));
-        vlink(p) = r;
-        p = r;
-        n = vlink(n);
-    }
-    return q;
-}
-
-@ @c
-void update_attribute_cache(void)
-{
-    halfword p;
-    register int i;
-    attr_list_cache = get_node(attribute_node_size);
-    type(attr_list_cache) = attribute_list_node;
-    attr_list_ref(attr_list_cache) = 0;
-    p = attr_list_cache;
-    for (i = 0; i <= max_used_attr; i++) {
-        register int v = attribute(i);
-        if (v > UNUSED_ATTRIBUTE) {
-            register halfword r = new_attribute_node((unsigned) i, v);
-            vlink(p) = r;
-            p = r;
-        }
-    }
-    if (vlink(attr_list_cache) == null) {
-        free_node(attr_list_cache, attribute_node_size);
-        attr_list_cache = null;
-    }
-    return;
-}
-
-@ @c
-void build_attribute_list(halfword b)
-{
-    if (max_used_attr >= 0) {
-        if (attr_list_cache == cache_disabled|| attr_list_cache == null) {
-            update_attribute_cache();
-            if (attr_list_cache == null)
-                return;
-        }
-        attr_list_ref(attr_list_cache)++;
-        node_attr(b) = attr_list_cache;
-    }
-}
-
-@ @c
-halfword current_attribute_list(void)
-{
-    if (max_used_attr >= 0) {
-      if (attr_list_cache == cache_disabled) {
-            update_attribute_cache();
-      }
-      return attr_list_cache ;
-    }
-    return null ;
-}
-
-
-@ @c
-void reassign_attribute(halfword n, halfword new)
-{
-    halfword old;
-    old = node_attr(n);
-    if (new == null) {
-         /* there is nothing to assign but we need to check for an old value */
-        if (old != null)
-            delete_attribute_ref(old); /* also nulls attr field of n */
-    } else if (old == null) {
-         /* nothing is assigned so we just do that now */
-        assign_attribute_ref(n,new);
-    } else if (old != new) {
-         /* something is assigned so we need to clean up and assign then */
-        delete_attribute_ref(old);
-        assign_attribute_ref(n,new);
-    }
-     /* else: same value so there is no need to assign and change the refcount */
-    node_attr(n) = new ;
-}
-
-@ @c
-void delete_attribute_ref(halfword b)
-{
-    if (b != null) {
-        if (type(b) == attribute_list_node){
-            attr_list_ref(b)--;
-            if (attr_list_ref(b) == 0) {
-                if (b == attr_list_cache)
-                    attr_list_cache = cache_disabled;
-                free_node_chain(b, attribute_node_size);
-            }
-            /* maintain sanity */
-            if (attr_list_ref(b) < 0) {
-                attr_list_ref(b) = 0;
-            }
-        } else {
-            normal_error("nodes","trying to delete an attribute reference of a non attribute node");
-        }
-    }
-}
-
-void reset_node_properties(halfword b)
-{
-    if (b != null) {
-        lua_properties_reset(b);
-    }
-}
-
-@ |p| is an attr list head, or zero
- at c
-halfword do_set_attribute(halfword p, int i, int val)
-{
-    register halfword q;
-    register int j = 0;
-    if (p == null) {            /* add a new head \& node */
-        q = get_node(attribute_node_size);
-        type(q) = attribute_list_node;
-        attr_list_ref(q) = 1;
-        p = new_attribute_node((unsigned) i, val);
-        vlink(q) = p;
-        return q;
-    }
-    q = p;
-    if (vlink(p) != null) {
-        while (vlink(p) != null) {
-            int t = attribute_id(vlink(p));
-            if (t == i && attribute_value(vlink(p)) == val)
-                return q;           /* no need to do anything */
-            if (t >= i)
-                break;
-            j++;
-            p = vlink(p);
-        }
-
-        p = q;
-        while (j-- > 0)
-            p = vlink(p);
-        if (attribute_id(vlink(p)) == i) {
-            attribute_value(vlink(p)) = val;
-        } else {                    /* add a new node */
-            halfword r = new_attribute_node((unsigned) i, val);
-            vlink(r) = vlink(p);
-            vlink(p) = r;
-        }
-        return q;
-    } else {
-        normal_error("nodes","trying to set an attribute fails, case 1");
-        return null ;
-    }
-}
-
-@ @c
-void set_attribute(halfword n, int i, int val)
-{
-    register halfword p;
-    register int j = 0;
-    /* not all nodes can have an attribute list */
-    if (!nodetype_has_attributes(type(n)))
-        return;
-    /* if we have no list, we create one and quit */
-    p = node_attr(n);
-    if (p == null) {            /* add a new head \& node */
-        p = get_node(attribute_node_size);
-        type(p) = attribute_list_node;
-        attr_list_ref(p) = 1;
-        node_attr(n) = p;
-        p = new_attribute_node((unsigned) i, val);
-        vlink(node_attr(n)) = p;
-        return;
-    }
-    /* we check if we have this attribute already and quit if the value stays the same */
-    if (vlink(p) != null) {
-        while (vlink(p) != null) {
-            int t = attribute_id(vlink(p));
-            if (t == i && attribute_value(vlink(p)) == val)
-                return;
-            if (t >= i)
-                break;
-            j++;
-            p = vlink(p);
-        }
-        /* j has now the position (if found) .. we assume a sorted list ! */
-        p = node_attr(n);
-
-        if (attr_list_ref(p) == 0 ) {
-            /* the list is invalid i.e. freed already */
-            formatted_warning("nodes","node %d has an attribute list that is free already, case 1",(int) n);
-            /* the still dangling list gets ref count 1 */
-            attr_list_ref(p) = 1;
-        } else if (attr_list_ref(p) == 1) {
-            /* this can really happen HH-LS */
-            if (p == attr_list_cache) {
-                /* we can invalidate the cache setting */
-                /* attr_list_cache = cache_disabled    */
-                /* or save the list, as done below     */
-                p = copy_attribute_list(p);
-                node_attr(n) = p;
-                /* the copied list gets ref count 1 */
-                attr_list_ref(p) = 1;
-            }
-        } else {
-            /* the list is used multiple times so we make a copy */
-            p = copy_attribute_list(p);
-            /* we decrement the ref count or the original */
-            delete_attribute_ref(node_attr(n));
-            node_attr(n) = p;
-            /* the copied list gets ref count 1 */
-            attr_list_ref(p) = 1;
-        }
-
-
-        /* we go to position j in the list */
-        while (j-- > 0)
-            p = vlink(p);
-        /* if we have a hit we just set the value otherwise we add a new node */
-        if (attribute_id(vlink(p)) == i) {
-            attribute_value(vlink(p)) = val;
-        } else {                    /* add a new node */
-            halfword r = new_attribute_node((unsigned) i, val);
-            vlink(r) = vlink(p);
-            vlink(p) = r;
-        }
-    } else {
-        normal_error("nodes","trying to set an attribute fails, case 2");
-    }
-}
-
-@ @c
-int unset_attribute(halfword n, int i, int val)
-{
-    register halfword p;
-    register int t;
-    register int j = 0;
-
-    if (!nodetype_has_attributes(type(n)))
-        return null;
-    p = node_attr(n);
-    if (p == null)
-        return UNUSED_ATTRIBUTE;
-    if (attr_list_ref(p) == 0) {
-        formatted_warning("nodes","node %d has an attribute list that is free already, case 2", (int) n);
-        return UNUSED_ATTRIBUTE;
-    }
-    if (vlink(p) != null) {
-        while (vlink(p) != null) {
-            t = attribute_id(vlink(p));
-            if (t > i)
-                return UNUSED_ATTRIBUTE;
-            if (t == i) {
-                p = vlink(p);
-                break;
-            }
-            j++;
-            p = vlink(p);
-        }
-        if (attribute_id(p) != i)
-            return UNUSED_ATTRIBUTE;
-        /* if we are still here, the attribute exists */
-        p = node_attr(n);
-        if (attr_list_ref(p) > 1 || p == attr_list_cache) {
-            halfword q = copy_attribute_list(p);
-            if (attr_list_ref(p) > 1) {
-                delete_attribute_ref(node_attr(n));
-            }
-            attr_list_ref(q) = 1;
-            node_attr(n) = q;
-        }
-        p = vlink(node_attr(n));
-        while (j-- > 0)
-            p = vlink(p);
-        t = attribute_value(p);
-        if (val == UNUSED_ATTRIBUTE || t == val) {
-            attribute_value(p) = UNUSED_ATTRIBUTE;
-        }
-        return t;
-    } else {
-        normal_error("nodes","trying to unset an attribute fails");
-        return null;
-    }
-}
-
-@ @c
-int has_attribute(halfword n, int i, int val)
-{
-    register halfword p;
-    if (!nodetype_has_attributes(type(n)))
-        return UNUSED_ATTRIBUTE;
-    p = node_attr(n);
-    if (p == null || vlink(p) == null)
-        return UNUSED_ATTRIBUTE;
-    p = vlink(p);
-    while (p != null) {
-        if (attribute_id(p) == i) {
-            int ret = attribute_value(p);
-            if (val == UNUSED_ATTRIBUTE || val == ret)
-                return ret;
-            return UNUSED_ATTRIBUTE;
-        } else if (attribute_id(p) > i) {
-            return UNUSED_ATTRIBUTE;
-        }
-        p = vlink(p);
-    }
-    return UNUSED_ATTRIBUTE;
-}
-
-@ @c
-void print_short_node_contents(halfword p)
-{
-    switch (type(p)) {
-        case hlist_node:
-        case vlist_node:
-        case ins_node:
-        case whatsit_node:
-        case mark_node:
-        case adjust_node:
-        case unset_node:
-            print_char('[');
-            print_char(']');
-            break;
-        case rule_node:
-            print_char('|');
-            break;
-        case glue_node:
-            if (! glue_is_zero(p))
-                print_char(' ');
-            break;
-        case math_node:
-            print_char('$');
-            break;
-        case disc_node:
-            short_display(vlink(pre_break(p)));
-            short_display(vlink(post_break(p)));
-            break;
-    }
-}
-
-@ @c
-static void show_pdftex_whatsit_rule_spec(int p)
-{
-    tprint("(");
-    print_rule_dimen(height(p));
-    print_char('+');
-    print_rule_dimen(depth(p));
-    tprint(")x");
-    print_rule_dimen(width(p));
-}
-
-@ Each new type of node that appears in our data structure must be capable
-of being displayed, copied, destroyed, and so on. The routines that we
-need for write-oriented whatsits are somewhat like those for mark nodes;
-other extensions might, of course, involve more subtlety here.
-
- at c
-static void print_write_whatsit(const char *s, pointer p)
-{
-    tprint_esc(s);
-    if (write_stream(p) < 16)
-        print_int(write_stream(p));
-    else if (write_stream(p) == 16)
-        print_char('*');
-    else
-        print_char('-');
-}
-
-@ @c
-static void show_node_wrapup_core(int p)
-{
-    switch (subtype(p)) {
-        case open_node:
-            print_write_whatsit("openout", p);
-            print_char('=');
-            print_file_name(open_name(p), open_area(p), open_ext(p));
-            break;
-        case write_node:
-            print_write_whatsit("write", p);
-            print_mark(write_tokens(p));
-            break;
-        case close_node:
-            print_write_whatsit("closeout", p);
-            break;
-        case special_node:
-            tprint_esc("special");
-            print_mark(write_tokens(p));
-            break;
-        case late_lua_node:
-            show_late_lua(p);
-            break;
-        case save_pos_node:
-            tprint_esc("savepos");
-            break;
-        case user_defined_node:
-            tprint_esc("whatsit");
-            print_int(user_node_id(p));
-            print_char('=');
-            switch (user_node_type(p)) {
-            case 'a':
-                tprint("<>");
-                break;
-            case 'n':
-                tprint("[");
-                show_node_list(user_node_value(p));
-                tprint("]");
-                break;
-            case 's':
-                print_char('"');
-                print(user_node_value(p));
-                print_char('"');
-                break;
-            case 't':
-                print_mark(user_node_value(p));
-                break;
-            default:               /* only 'd' */
-                print_int(user_node_value(p));
-                break;
-            }
-            break;
-    }
-}
-
-void show_node_wrapup_dvi(int p)
-{
-}
-
-void show_node_wrapup_pdf(int p)
-{
-    switch (subtype(p)) {
-        case pdf_literal_node:
-            show_pdf_literal(p);
-            break;
-        case pdf_colorstack_node:
-            tprint_esc("pdfcolorstack ");
-            print_int(pdf_colorstack_stack(p));
-            switch (pdf_colorstack_cmd(p)) {
-            case colorstack_set:
-                tprint(" set ");
-                break;
-            case colorstack_push:
-                tprint(" push ");
-                break;
-            case colorstack_pop:
-                tprint(" pop");
-                break;
-            case colorstack_current:
-                tprint(" current");
-                break;
-            default:
-                confusion("colorstack");
-                break;
-            }
-            if (pdf_colorstack_cmd(p) <= colorstack_data)
-                print_mark(pdf_colorstack_data(p));
-            break;
-        case pdf_setmatrix_node:
-            tprint_esc("pdfsetmatrix");
-            print_mark(pdf_setmatrix_data(p));
-            break;
-        case pdf_save_node:
-            tprint_esc("pdfsave");
-            break;
-        case pdf_restore_node:
-            tprint_esc("pdfrestore");
-            break;
-        case pdf_refobj_node:
-            tprint_esc("pdfrefobj");
-            if (obj_obj_is_stream(static_pdf, pdf_obj_objnum(p))) {
-                if (obj_obj_stream_attr(static_pdf, pdf_obj_objnum(p)) != LUA_NOREF) {
-                    tprint(" attr");
-                    lua_rawgeti(Luas, LUA_REGISTRYINDEX,
-                                obj_obj_stream_attr(static_pdf, pdf_obj_objnum(p)));
-                    print_char(' ');
-                    tprint((const char *) lua_tostring(Luas, -1));
-                    lua_pop(Luas, 1);
-                }
-                tprint(" stream");
-            }
-            if (obj_obj_is_file(static_pdf, pdf_obj_objnum(p)))
-                tprint(" file");
-            if (obj_obj_data(static_pdf, pdf_obj_objnum(p)) != LUA_NOREF) {
-                lua_rawgeti(Luas, LUA_REGISTRYINDEX,
-                            obj_obj_data(static_pdf, pdf_obj_objnum(p)));
-                print_char(' ');
-                tprint((const char *) lua_tostring(Luas, -1));
-                lua_pop(Luas, 1);
-            }
-            break;
-        case pdf_annot_node:
-            tprint_esc("pdfannot");
-            show_pdftex_whatsit_rule_spec(p);
-            print_mark(pdf_annot_data(p));
-            break;
-        case pdf_start_link_node:
-            tprint_esc("pdfstartlink");
-            show_pdftex_whatsit_rule_spec(p);
-            if (pdf_link_attr(p) != null) {
-                tprint(" attr");
-                print_mark(pdf_link_attr(p));
-            }
-            tprint(" action");
-            if (pdf_action_type(pdf_link_action(p)) == pdf_action_user) {
-                tprint(" user");
-                print_mark(pdf_action_tokens(pdf_link_action(p)));
-                return;
-            }
-            if (pdf_action_file(pdf_link_action(p)) != null) {
-                tprint(" file");
-                print_mark(pdf_action_file(pdf_link_action(p)));
-            }
-            switch (pdf_action_type(pdf_link_action(p))) {
-            case pdf_action_goto:
-                if (pdf_action_named_id(pdf_link_action(p)) > 0) {
-                    tprint(" goto name");
-                    print_mark(pdf_action_id(pdf_link_action(p)));
-                } else {
-                    tprint(" goto num");
-                    print_int(pdf_action_id(pdf_link_action(p)));
-                }
-                break;
-            case pdf_action_page:
-                tprint(" page");
-                print_int(pdf_action_id(pdf_link_action(p)));
-                print_mark(pdf_action_tokens(pdf_link_action(p)));
-                break;
-            case pdf_action_thread:
-                if (pdf_action_named_id(pdf_link_action(p)) > 0) {
-                    tprint(" thread name");
-                    print_mark(pdf_action_id(pdf_link_action(p)));
-                } else {
-                    tprint(" thread num");
-                    print_int(pdf_action_id(pdf_link_action(p)));
-                }
-                break;
-            default:
-                normal_error("pdf backend", "unknown action type for link");
-                break;
-            }
-            break;
-        case pdf_end_link_node:
-            tprint_esc("pdfendlink");
-            break;
-        case pdf_dest_node:
-            tprint_esc("pdfdest");
-            if (pdf_dest_named_id(p) > 0) {
-                tprint(" name");
-                print_mark(pdf_dest_id(p));
-            } else {
-                tprint(" num");
-                print_int(pdf_dest_id(p));
-            }
-            print_char(' ');
-            switch (pdf_dest_type(p)) {
-            case pdf_dest_xyz:
-                tprint("xyz");
-                if (pdf_dest_xyz_zoom(p) != null) {
-                    tprint(" zoom");
-                    print_int(pdf_dest_xyz_zoom(p));
-                }
-                break;
-            case pdf_dest_fitbh:
-                tprint("fitbh");
-                break;
-            case pdf_dest_fitbv:
-                tprint("fitbv");
-                break;
-            case pdf_dest_fitb:
-                tprint("fitb");
-                break;
-            case pdf_dest_fith:
-                tprint("fith");
-                break;
-            case pdf_dest_fitv:
-                tprint("fitv");
-                break;
-            case pdf_dest_fitr:
-                tprint("fitr");
-                show_pdftex_whatsit_rule_spec(p);
-                break;
-            case pdf_dest_fit:
-                tprint("fit");
-                break;
-            default:
-                tprint("unknown!");
-                break;
-            }
-            break;
-        case pdf_thread_node:
-        case pdf_start_thread_node:
-            if (subtype(p) == pdf_thread_node)
-                tprint_esc("pdfthread");
-            else
-                tprint_esc("pdfstartthread");
-            tprint("(");
-            print_rule_dimen(height(p));
-            print_char('+');
-            print_rule_dimen(depth(p));
-            tprint(")x");
-            print_rule_dimen(width(p));
-            if (pdf_thread_attr(p) != null) {
-                tprint(" attr");
-                print_mark(pdf_thread_attr(p));
-            }
-            if (pdf_thread_named_id(p) > 0) {
-                tprint(" name");
-                print_mark(pdf_thread_id(p));
-            } else {
-                tprint(" num");
-                print_int(pdf_thread_id(p));
-            }
-            break;
-        case pdf_end_thread_node:
-            tprint_esc("pdfendthread");
-            break;
-        default:
-            break;
-    }
-}
-
-@  Now we are ready for |show_node_list| itself. This procedure has been
-  written to be ``extra robust'' in the sense that it should not crash or get
-  into a loop even if the data structures have been messed up by bugs in
-  the rest of the program. You can safely call its parent routine
-  |show_box(p)| for arbitrary values of |p| when you are debugging \TeX.
-  However, in the presence of bad data, the procedure may
-  fetch a |memory_word| whose variant is different from the way it was stored;
-  for example, it might try to read |mem[p].hh| when |mem[p]|
-  contains a scaled integer, if |p| is a pointer that has been
-  clobbered or chosen at random.
-
-
-@ |str_room| need not be checked; see |show_box|
-
-@ Recursive calls on |show_node_list| therefore use the following pattern:
- at c
-#define node_list_display(A) do { \
-    append_char('.');  \
-    show_node_list(A); \
-    flush_char();      \
-} while (0)
-
-#define node_list_display_x(A,B) do { \
-    if ((B) != null) {     \
-        append_char('.');  \
-        append_char(A);    \
-        append_char(' ');  \
-        show_node_list(B); \
-        flush_char();      \
-        flush_char();      \
-        flush_char();      \
-    } \
-} while (0)
-
-/* prints a node list symbolically */
-
-void show_node_list(int p)
-{
-    int n = 0;                  /* the number of items already printed at this level */
-    halfword w;
-    real g;                     /* a glue ratio, as a floating point number */
-    if ((int) cur_length > depth_threshold) {
-        if (p > null)
-            tprint(" []");      /* indicate that there's been some truncation */
-        return;
-    }
-    while (p != null) {
-        print_ln();
-        print_current_string(); /* display the nesting history */
-        if (tracing_online_par < -2)
-            print_int(p);
-        incr(n);
-        if (n > breadth_max) {  /* time to stop */
-            tprint("etc.");
-            return;
-        }
-        /* Display node |p| */
-        if (is_char_node(p)) {
-            print_font_and_char(p);
-            if (is_ligature(p)) {
-                /* Display ligature |p|; */
-                tprint(" (ligature ");
-                if (is_leftboundary(p))
-                    print_char('|');
-                font_in_short_display = font(p);
-                short_display(lig_ptr(p));
-                if (is_rightboundary(p))
-                    print_char('|');
-                print_char(')');
-            }
-        } else {
-            switch (type(p)) {
-            case hlist_node:
-            case vlist_node:
-            case unset_node:
-                /* Display box |p|; */
-                if (type(p) == hlist_node)
-                    tprint_esc("h");
-                else if (type(p) == vlist_node)
-                    tprint_esc("v");
-                else
-                    tprint_esc("unset");
-                tprint("box(");
-                print_scaled(height(p));
-                print_char('+');
-                print_scaled(depth(p));
-                tprint(")x");
-                print_scaled(width(p));
-                if (type(p) == unset_node) {
-                    /* Display special fields of the unset node |p|; */
-                    if (span_count(p) != min_quarterword) {
-                        tprint(" (");
-                        print_int(span_count(p) + 1);
-                        tprint(" columns)");
-                    }
-                    if (glue_stretch(p) != 0) {
-                        tprint(", stretch ");
-                        print_glue(glue_stretch(p), glue_order(p), NULL);
-                    }
-                    if (glue_shrink(p) != 0) {
-                        tprint(", shrink ");
-                        print_glue(glue_shrink(p), glue_sign(p), NULL);
-                    }
-                } else {
-                    /* Display the value of |glue_set(p)| */
-                    /* The code will have to change in this place if |glue_ratio| is
-                       a structured type instead of an ordinary |real|. Note that this routine
-                       should avoid arithmetic errors even if the |glue_set| field holds an
-                       arbitrary random value. The following code assumes that a properly
-                       formed nonzero |real| number has absolute value $2^{20}$ or more when
-                       it is regarded as an integer; this precaution was adequate to prevent
-                       floating point underflow on the author's computer.
-                     */
-
-                    g = (real) (glue_set(p));
-                    if ((g != 0.0) && (glue_sign(p) != normal)) {
-                        tprint(", glue set ");
-                        if (glue_sign(p) == shrinking)
-                            tprint("- ");
-                        if (g > 20000.0 || g < -20000.0) {
-                            if (g > 0.0)
-                                print_char('>');
-                            else
-                                tprint("< -");
-                            print_glue(20000 * unity, glue_order(p), NULL);
-                        } else {
-                            print_glue(round(unity * g), glue_order(p), NULL);
-                        }
-                    }
-
-                    if (shift_amount(p) != 0) {
-                        tprint(", shifted ");
-                        print_scaled(shift_amount(p));
-                    }
-                    tprint(", direction ");
-                    print_dir(box_dir(p));
-                }
-                node_list_display(list_ptr(p)); /* recursive call */
-                break;
-            case rule_node:
-                /* Display rule |p|; */
-                if (subtype(p) == normal_rule) {
-                    tprint_esc("rule(");
-                } else if (subtype(p) == empty_rule) {
-                    tprint_esc("norule(");
-                } else if (subtype(p) == user_rule) {
-                    tprint_esc("userrule(");
-                } else if (subtype(p) == box_rule) {
-                    tprint_esc("box(");
-                } else if (subtype(p) == image_rule) {
-                    tprint_esc("image(");
-                }
-                print_rule_dimen(height(p));
-                print_char('+');
-                print_rule_dimen(depth(p));
-                tprint(")x");
-                print_rule_dimen(width(p));
-                break;
-            case ins_node:
-                /* Display insertion |p|; */
-                tprint_esc("insert");
-                print_int(subtype(p));
-                tprint(", natural size ");
-                print_scaled(height(p));
-                tprint("; split(");
-                print_spec(split_top_ptr(p), NULL);
-                print_char(',');
-                print_scaled(depth(p));
-                tprint("); float cost ");
-                print_int(float_cost(p));
-                node_list_display(ins_ptr(p));  /* recursive call */
-                break;
-            case dir_node:
-                if (dir_dir(p) < 0) {
-                    tprint_esc("enddir");
-                    print_char(' ');
-                    print_dir(dir_dir(p) + dir_swap);
-                } else {
-                    tprint_esc("begindir");
-                    print_char(' ');
-                    print_dir(dir_dir(p));
-                }
-                break;
-            case local_par_node:
-                tprint_esc("localpar");
-                append_char('.');
-                print_ln();
-                print_current_string();
-                tprint_esc("localinterlinepenalty");
-                print_char('=');
-                print_int(local_pen_inter(p));
-                print_ln();
-                print_current_string();
-                tprint_esc("localbrokenpenalty");
-                print_char('=');
-                print_int(local_pen_broken(p));
-                print_ln();
-                print_current_string();
-                tprint_esc("localleftbox");
-                if (local_box_left(p) == null) {
-                    tprint("=null");
-                } else {
-                    append_char('.');
-                    show_node_list(local_box_left(p));
-                    decr(cur_length);
-                }
-                print_ln();
-                print_current_string();
-                tprint_esc("localrightbox");
-                if (local_box_right(p) == null) {
-                    tprint("=null");
-                } else {
-                    append_char('.');
-                    show_node_list(local_box_right(p));
-                    decr(cur_length);
-                }
-                decr(cur_length);
-                break;
-            case boundary_node:
-                if (subtype(p)==0) {
-                    tprint_esc("noboundary");
-                } else {
-                    switch (subtype(p)) {
-                        case 1:
-                            tprint_esc("boundary");
-                            break;
-                        case 2:
-                            tprint_esc("protrusionboundary");
-                            break;
-                        case 3:
-                            tprint_esc("wordboundary");
-                            break;
-                        default:
-                            tprint_esc("boundary");
-                            print_char(':');
-                            print_int(subtype(p));
-                            break;
-                    }
-                    print_char('=');
-                    print_int(boundary_value(p));
-                }
-                break;
-            case whatsit_node:
-                w = subtype(p) ;
-                if (w >= backend_first_pdf_whatsit) {
-                    show_node_wrapup_pdf(p);
-                } else if (w >= backend_first_dvi_whatsit) {
-                    show_node_wrapup_dvi(p);
-                } else {
-                    show_node_wrapup_core(p);
-                }
-                break;
-            case glue_node:
-                /* Display glue |p|; */
-                if (subtype(p) >= a_leaders) {
-                    /* Display leaders |p|; */
-                    tprint_esc("");
-                    switch (subtype(p)) {
-                    case a_leaders:
-                        break;
-                    case c_leaders:
-                        print_char('c');
-                        break;
-                    case x_leaders:
-                        print_char('x');
-                        break;
-                    case g_leaders:
-                        print_char('g');
-                        break;
-                    default:
-                        normal_warning("nodes","weird glue leader subtype ignored");
-                    }
-                    tprint("leaders ");
-                    print_spec(p, NULL);
-                    node_list_display(leader_ptr(p));   /* recursive call */
-                } else {
-                    tprint_esc("glue");
-                    if (subtype(p) != normal) {
-                        print_char('(');
-                        if ((subtype(p) - 1) < thin_mu_skip_code) {
-                            print_cmd_chr(assign_glue_cmd, glue_base + (subtype(p) - 1));
-                        } else if (subtype(p) < cond_math_glue) {
-                            print_cmd_chr(assign_mu_glue_cmd, glue_base + (subtype(p) - 1));
-                        } else if (subtype(p) == cond_math_glue) {
-                            tprint_esc("nonscript");
-                        } else {
-                            tprint_esc("mskip");
-                        }
-                        print_char(')');
-                    }
-                    if (subtype(p) != cond_math_glue) {
-                        print_char(' ');
-                        if (subtype(p) < cond_math_glue)
-                            print_spec(p, NULL);
-                        else
-                            print_spec(p, "mu");
-                    }
-                }
-                break;
-            case margin_kern_node:
-                tprint_esc("kern");
-                print_scaled(width(p));
-                if (subtype(p) == left_side)
-                    tprint(" (left margin)");
-                else
-                    tprint(" (right margin)");
-                break;
-            case kern_node:
-                /* Display kern |p|; */
-                /*  An ``explicit'' kern value is indicated implicitly by an explicit space. */
-                if (subtype(p) != mu_glue) {
-                    tprint_esc("kern");
-                    if (subtype(p) != normal)
-                        print_char(' ');
-                    print_scaled(width(p));
-                    if (subtype(p) == accent_kern)
-                        tprint(" (for accent)");
-                } else {
-                    tprint_esc("mkern");
-                    print_scaled(width(p));
-                    tprint("mu");
-                }
-                break;
-            case math_node:
-                /* Display math node |p|; */
-                tprint_esc("math");
-                if (subtype(p) == before)
-                    tprint("on");
-                else
-                    tprint("off");
-                if (!glue_is_zero(p)) {
-                    tprint(", glued ");
-                    print_spec(p, NULL);
-                } else if (surround(p) != 0) {
-                    tprint(", surrounded ");
-                    print_scaled(surround(p));
-                }
-                break;
-            case penalty_node:
-                /* Display penalty |p|; */
-                tprint_esc("penalty ");
-                print_int(penalty(p));
-                break;
-            case disc_node:
-                /* Display discretionary |p|; */
-                /* The |post_break| list of a discretionary node is indicated by a prefixed
-                   `\.{\char'174}' instead of the `\..' before the |pre_break| list. */
-                /* We're not compatible anyway  so ...
-                    tprint_esc("discretionary");
-                    print_int(disc_penalty(p));
-                    print_char('|');
-                    if (vlink(no_break(p)) != null) {
-                        tprint(" replacing ");
-                        node_list_display(vlink(no_break(p)));
-                    }
-                    node_list_display(vlink(pre_break(p)));
-                    append_char('|');
-                    show_node_list(vlink(post_break(p)));
-                    flush_char();
-                */
-                tprint_esc("discretionary");
-                tprint(" (penalty ");
-                print_int(disc_penalty(p));
-                print_char(')');
-                node_list_display_x('<',vlink(pre_break(p)));
-                node_list_display_x('>',vlink(post_break(p)));
-                node_list_display_x('=',vlink(no_break(p)));
-                break;
-            case mark_node:
-                /* Display mark |p|; */
-                tprint_esc("mark");
-                if (mark_class(p) != 0) {
-                    print_char('s');
-                    print_int(mark_class(p));
-                }
-                print_mark(mark_ptr(p));
-                break;
-            case adjust_node:
-                /* Display adjustment |p|; */
-                tprint_esc("vadjust");
-                if (subtype(p) != 0)
-                    tprint(" pre ");
-                node_list_display(adjust_ptr(p));       /* recursive call */
-                break;
-            case glue_spec_node:
-                tprint("<glue_spec ");
-                print_spec(p, NULL);
-                tprint(">");
-                break;
-            default:
-                show_math_node(p);
-                break;
-            }
-        }
-        p = vlink(p);
-    }
-}
-
-@ This routine finds the 'base' width of a horizontal box, using the same logic
-  that \TeX82 used for \.{\\predisplaywidth} */
-
- at c
-static pointer get_actual_box_width(pointer r,pointer p, scaled initial_width)
-{
-    scaled d;                  /* increment to |v| */
-    scaled w = -max_dimen;     /* calculated |size| */
-    scaled v = initial_width;  /* |w| plus possible glue amount */
-    while (p != null) {
-        if (is_char_node(p)) {
-            d = glyph_width(p);
-            goto FOUND;
-        }
-        switch (type(p)) {
-            case hlist_node:
-            case vlist_node:
-            case rule_node:
-                d = width(p);
-                goto FOUND;
-                break;
-            case margin_kern_node:
-                d = width(p);
-                break;
-            case kern_node:
-                d = width(p);
-                break;
-            case disc_node:
-                /* at the end of the line we should actually take the pre */
-                if (no_break(p) != null) {
-                    d = get_actual_box_width(r,vlink_no_break(p),0);
-                    if (d <= -max_dimen || d >= max_dimen) {
-                        d = 0;
-                    }
-                } else {
-                    d = 0;
-                }
-                goto FOUND;
-                break;
-            case math_node:
-                /* begin mathskip code */
-                if (glue_is_zero(p)) {
-                    d = surround(p);
-                    break;
-                } else {
-                    /* fall through */
-                }
-                /* end mathskip code */
-            case glue_node:
-                /* We need to be careful that |w|, |v|, and |d| do not depend on any |glue_set|
-                   values, since such values are subject to system-dependent rounding.
-                   System-dependent numbers are not allowed to infiltrate parameters like
-                   |pre_display_size|, since \TeX82 is supposed to make the same decisions on all
-                   machines.
-                 */
-                d = width(p);
-                if (glue_sign(r) == stretching) {
-                    if ((glue_order(r) == stretch_order(p)) && (stretch(p) != 0))
-                        v = max_dimen;
-                } else if (glue_sign(r) == shrinking) {
-                    if ((glue_order(r) == shrink_order(p)) && (shrink(p) != 0))
-                        v = max_dimen;
-                }
-                if (subtype(p) >= a_leaders)
-                    goto FOUND;
-                break;
-            default:
-                d = 0;
-                break;
-        }
-        if (v < max_dimen)
-            v = v + d;
-        goto NOT_FOUND;
-      FOUND:
-        if (v < max_dimen) {
-            v = v + d;
-            w = v;
-        } else {
-            w = max_dimen;
-            break;
-        }
-      NOT_FOUND:
-        p = vlink(p);
-    }
-    return w;
-}
-
-pointer actual_box_width(pointer r, scaled base_width)
-{
-    /* often this is the same as:
-        return + shift_amount(r) + base_width +
-            natural_sizes(list_ptr(r),null,(glue_ratio) glue_set(r),glue_sign(r),glue_order(r),box_dir(r));
-    */
-    return get_actual_box_width(r,list_ptr(r),shift_amount(r) + base_width);
-}
-
-@ @c
-halfword tail_of_list(halfword p)
-{
-    halfword q = p;
-    while (vlink(q) != null)
-        q = vlink(q);
-    return q;
-}
-
-
-
-@ @c
-int var_used;
-
-@ Attribute lists need two extra globals to increase processing efficiency.
-|max_used_attr| limits the test loop that checks for set attributes, and
-|attr_list_cache| contains a pointer to an already created attribute list.  It is
-set to the special value |cache_disabled| when the current value can no longer be
-trusted: after an assignment to an attribute register, and after a group has
-ended.
-
- at c
-int max_used_attr;        /* maximum assigned attribute id  */
-halfword attr_list_cache;
-
-@ From the computer's standpoint, \TeX's chief mission is to create
-horizontal and vertical lists. We shall now investigate how the elements
-of these lists are represented internally as nodes in the dynamic memory.
-
-A horizontal or vertical list is linked together by |link| fields in
-the first word of each node. Individual nodes represent boxes, glue,
-penalties, or special things like discretionary hyphens; because of this
-variety, some nodes are longer than others, and we must distinguish different
-kinds of nodes. We do this by putting a `|type|' field in the first word,
-together with the link and an optional `|subtype|'.
-
-@ Character nodes appear only in horizontal lists, never in vertical lists.
-
-An |hlist_node| stands for a box that was made from a horizontal list.
-Each |hlist_node| is seven words long, and contains the following fields
-(in addition to the mandatory |type| and |link|, which we shall not
-mention explicitly when discussing the other node types): The |height| and
-|width| and |depth| are scaled integers denoting the dimensions of the
-box.  There is also a |shift_amount| field, a scaled integer indicating
-how much this box should be lowered (if it appears in a horizontal list),
-or how much it should be moved to the right (if it appears in a vertical
-list). There is a |list_ptr| field, which points to the beginning of the
-list from which this box was fabricated; if |list_ptr| is |null|, the box
-is empty. Finally, there are three fields that represent the setting of
-the glue:  |glue_set(p)| is a word of type |glue_ratio| that represents
-the proportionality constant for glue setting; |glue_sign(p)| is
-|stretching| or |shrinking| or |normal| depending on whether or not the
-glue should stretch or shrink or remain rigid; and |glue_order(p)|
-specifies the order of infinity to which glue setting applies (|normal|,
-|sfi|, |fil|, |fill|, or |filll|). The |subtype| field is not used.
-
-@ The |new_null_box| function returns a pointer to an |hlist_node| in
-which all subfields have the values corresponding to `\.{\\hbox\{\}}'.
-The |subtype| field is set to |min_quarterword|, since that's the desired
-|span_count| value if this |hlist_node| is changed to an |unset_node|.
-
- at c
-halfword new_null_box(void)
-{                               /* creates a new box node */
-    halfword p = new_node(hlist_node, min_quarterword);
-    box_dir(p) = text_direction_par;
-    return p;
-}
-
-@ A |vlist_node| is like an |hlist_node| in all respects except that it
-contains a vertical list.
-
-@ A |rule_node| stands for a solid black rectangle; it has |width|,
-|depth|, and |height| fields just as in an |hlist_node|. However, if
-any of these dimensions is $-2^{30}$, the actual value will be determined
-by running the rule up to the boundary of the innermost enclosing box.
-This is called a ``running dimension.'' The |width| is never running in
-an hlist; the |height| and |depth| are never running in a~vlist.
-
-@ A new rule node is delivered by the |new_rule| function. It
-makes all the dimensions ``running,'' so you have to change the
-ones that are not allowed to run.
-
- at c
-halfword new_rule(int s)
-{
-    halfword p = new_node(rule_node,s);
-    return p;
-}
-
-@ Insertions are represented by |ins_node| records, where the |subtype|
-indicates the corresponding box number. For example, `\.{\\insert 250}'
-leads to an |ins_node| whose |subtype| is |250+min_quarterword|.
-The |height| field of an |ins_node| is slightly misnamed; it actually holds
-the natural height plus depth of the vertical list being inserted.
-The |depth| field holds the |split_max_depth| to be used in case this
-insertion is split, and the |split_top_ptr| points to the corresponding
-|split_top_skip|. The |float_cost| field holds the |floating_penalty| that
-will be used if this insertion floats to a subsequent page after a
-split insertion of the same class.  There is one more field, the
-|ins_ptr|, which points to the beginning of the vlist for the insertion.
-
-@ A |mark_node| has a |mark_ptr| field that points to the reference count
-of a token list that contains the user's \.{\\mark} text.
-In addition there is a |mark_class| field that contains the mark class.
-
-@ An |adjust_node|, which occurs only in horizontal lists,
-specifies material that will be moved out into the surrounding
-vertical list; i.e., it is used to implement \TeX's `\.{\\vadjust}'
-operation.  The |adjust_ptr| field points to the vlist containing this
-material.
-
-@ A |glyph_node|, which occurs only in horizontal lists, specifies a
-glyph in a particular font, along with its attribute list. Older
-versions of \TeX\ could use token memory for characters, because the
-font,char combination would fit in a single word (both values were
-required to be strictly less than $2^{16}$). In LuaTeX, room is
-needed for characters that are larger than that, as well as a pointer
-to a potential attribute list, and the two displacement values.
-
-In turn, that made the node so large that it made sense to merge
-ligature glyphs as well, as that requires only one extra pointer.  A
-few extra classes of glyph nodes will be introduced later.  The
-unification of all those types makes it easier to manipulate lists of
-glyphs. The subtype differentiates various glyph kinds.
-
-First, here is a function that returns a pointer to a glyph node for a given
-glyph in a given font. If that glyph doesn't exist, |null| is returned
-instead.  Nodes of this subtype are directly created only for accents
-and their base (through |make_accent|), and math nucleus items (in the
-conversion from |mlist| to |hlist|).
-
- at c
-halfword new_glyph(int f, int c)
-{
-    halfword p = null;          /* the new node */
-    if ((f == 0) || (char_exists(f, c))) {
-        p = new_glyph_node();
-        set_to_glyph(p);
-        font(p) = f;
-        character(p) = c;
-    }
-    return p;
-}
-
-@ A subset of the glyphs nodes represent ligatures: characters
-fabricated from the interaction of two or more actual characters.  The
-characters that generated the ligature have not been forgotten, since
-they are needed for diagnostic messages; the |lig_ptr| field points to
-a linked list of character nodes for all original characters that have
-been deleted. (This list might be empty if the characters that
-generated the ligature were retained in other nodes.)
-
-The |subtype| field of these |glyph_node|s is 1, plus 2 and/or 1 if
-the original source of the ligature included implicit left and/or
-right boundaries. These nodes are created by the C function |new_ligkern|.
-
-A third general type of glyphs could be called a character, as it
-only appears in lists that are not yet processed by the ligaturing and
-kerning steps of the program.
-
-|main_control| inserts these, and they are later converted to
-|subtype_normal| by |new_ligkern|.
-
- at c
-quarterword norm_min(int h)
-{
-    if (h <= 0)
-        return 1;
-    else if (h >= 255)
-        return 255;
-    else
-        return (quarterword) h;
-}
-
-halfword new_char(int f, int c)
-{
-    halfword p;                 /* the new node */
-    p = new_glyph_node();
-    set_to_character(p);
-    font(p) = f;
-    character(p) = c;
-    lang_data(p) = make_lang_data(uc_hyph_par, cur_lang_par, left_hyphen_min_par, right_hyphen_min_par);
-    return p;
-}
-
-@ Left and right ghost glyph nodes are the result of \.{\\leftghost}
-and \.{\\rightghost}, respectively. They are going to be removed by
-|new_ligkern|, at the end of which they are no longer needed.
-
-@ Here are a few handy helpers used by the list output routines.
-
- at c
-scaled glyph_width(halfword p)
-{
-    scaled w = char_width(font(p), character(p));
-    return w;
-}
-
-scaled glyph_height(halfword p)
-{
-    scaled w = char_height(font(p), character(p)) + y_displace(p);
-    if (w < 0)
-        w = 0;
-    return w;
-}
-
-scaled glyph_depth(halfword p)
-{
-    scaled w = char_depth(font(p), character(p));
-    if (y_displace(p) > 0)
-        w = w - y_displace(p);
-    if (w < 0)
-        w = 0;
-    return w;
-}
-
-@ A |disc_node|, which occurs only in horizontal lists, specifies a
-``dis\-cretion\-ary'' line break. If such a break occurs at node |p|, the text
-that starts at |pre_break(p)| will precede the break, the text that starts at
-|post_break(p)| will follow the break, and text that appears in
-|no_break(p)| nodes will be ignored. For example, an ordinary
-discretionary hyphen, indicated by `\.{\\-}', yields a |disc_node| with
-|pre_break| pointing to a |char_node| containing a hyphen, |post_break=null|,
-and |no_break=null|.
-
-{TODO: Knuth said: All three of the discretionary texts must be lists
-that consist entirely of character, kern, box and rule nodes.}
-
-If |subtype(p)=automatic_disc|, the |ex_hyphen_penalty| will be charged for this
-break.  Otherwise the |hyphen_penalty| will be charged.  The texts will
-actually be substituted into the list by the line-breaking algorithm if it
-decides to make the break, and the discretionary node will disappear at
-that time; thus, the output routine sees only discretionaries that were
-not chosen.
-
- at c
-halfword new_disc(void)
-{                               /* creates an empty |disc_node| */
-    halfword p = new_node(disc_node, 0);
-    disc_penalty(p) = hyphen_penalty_par;
-    return p;
-}
-
-@ A |whatsit_node| is a wild card reserved for extensions to \TeX. The
-|subtype| field in its first word says what `\\{whatsit}' it is, and
-implicitly determines the node size (which must be 2 or more) and the
-format of the remaining words. When a |whatsit_node| is encountered
-in a list, special actions are invoked; knowledgeable people who are
-careful not to mess up the rest of \TeX\ are able to make \TeX\ do new
-things by adding code at the end of the program. For example, there
-might be a `\TeX nicolor' extension to specify different colors of ink,
-@^extensions to \TeX@>
-and the whatsit node might contain the desired parameters.
-
-The present implementation of \TeX\ treats the features associated with
-`\.{\\write}' and `\.{\\special}' as if they were extensions, in order to
-illustrate how such routines might be coded. We shall defer further
-discussion of extensions until the end of this program.
-
-@ A |math_node|, which occurs only in horizontal lists, appears before and
-after mathematical formulas. The |subtype| field is |before| before the
-formula and |after| after it. There is a |surround| field, which represents
-the amount of surrounding space inserted by \.{\\mathsurround}.
-
- at c
-halfword new_math(scaled w, int s)
-{
-    halfword p = new_node(math_node, s);
-    surround(p) = w;
-    return p;
-}
-
-@ \TeX\ makes use of the fact that |hlist_node|, |vlist_node|,
-|rule_node|, |ins_node|, |mark_node|, |adjust_node|,
-|disc_node|, |whatsit_node|, and |math_node| are at the low end of the
-type codes, by permitting a break at glue in a list if and only if the
-|type| of the previous node is less than |math_node|. Furthermore, a
-node is discarded after a break if its type is |math_node| or~more.
-
-@ A |glue_node| represents glue in a list. However, it is really only
-a pointer to a separate glue specification, since \TeX\ makes use of the
-fact that many essentially identical nodes of glue are usually present.
-If |p| points to a |glue_node|, |glue_ptr(p)| points to
-another packet of words that specify the stretch and shrink components, etc.
-
-Glue nodes also serve to represent leaders; the |subtype| is used to
-distinguish between ordinary glue (which is called |normal|) and the three
-kinds of leaders (which are called |a_leaders|, |c_leaders|, and |x_leaders|).
-The |leader_ptr| field points to a rule node or to a box node containing the
-leaders; it is set to |null| in ordinary glue nodes.
-
-Many kinds of glue are computed from \TeX's ``skip'' parameters, and
-it is helpful to know which parameter has led to a particular glue node.
-Therefore the |subtype| is set to indicate the source of glue, whenever
-it originated as a parameter. We will be defining symbolic names for the
-parameter numbers later (e.g., |line_skip_code=0|, |baseline_skip_code=1|,
-etc.); it suffices for now to say that the |subtype| of parametric glue
-will be the same as the parameter number, plus~one.
-
-@ In math formulas there are two more possibilities for the |subtype| in a
-glue node: |mu_glue| denotes an \.{\\mskip} (where the units are scaled \.{mu}
-instead of scaled \.{pt}); and |cond_math_glue| denotes the `\.{\\nonscript}'
-feature that cancels the glue node immediately following if it appears
-in a subscript.
-
-@ A glue specification has a halfword reference count in its first word,
-@^reference counts@>
-representing |null| plus the number of glue nodes that point to it (less one).
-Note that the reference count appears in the same position as
-the |link| field in list nodes; this is the field that is initialized
-to |null| when a node is allocated, and it is also the field that is flagged
-by |empty_flag| in empty nodes.
-
-Glue specifications also contain three |scaled| fields, for the |width|,
-|stretch|, and |shrink| dimensions. Finally, there are two one-byte
-fields called |stretch_order| and |shrink_order|; these contain the
-orders of infinity (|normal|, |sfi|, |fil|, |fill|, or |filll|)
-corresponding to the stretch and shrink values.
-
-@ Here is a function that returns a pointer to a copy of a glue spec.
-The reference count in the copy is |null|, because there is assumed
-to be exactly one reference to the new specification.
-
- at c
-halfword new_spec(halfword q) /* safeguard for copying a glue node */
-{
-    if (q == null) {
-        return copy_node(zero_glue);
-    } else if (type(q) == glue_spec_node) {
-        return copy_node(q);
-    } else if (type(q) == glue_node) {
-        halfword p = copy_node(zero_glue);
-        width(p) = width(q);
-        stretch(p) = stretch(q);
-        shrink(p) = shrink(q);
-        stretch_order(p) = stretch_order(q);
-        shrink_order(p) = shrink_order(q);
-        return p;
-    } else {
-        /* alternatively we can issue a warning */
-        return copy_node(zero_glue);
-    }
-}
-
-@ And here's a function that creates a glue node for a given parameter
-identified by its code number; for example,
-|new_param_glue(line_skip_code)| returns a pointer to a glue node for the
-current \.{\\lineskip}.
-
- at c
-halfword new_param_glue(int n)
-{
-    halfword p = new_node(glue_node, n + 1);
-    halfword q = glue_par(n);
-    width(p) = width(q);
-    stretch(p) = stretch(q);
-    shrink(p) = shrink(q);
-    stretch_order(p) = stretch_order(q);
-    shrink_order(p) = shrink_order(q);
-    return p;
-}
-
-@ Glue nodes that are more or less anonymous are created by |new_glue|,
-whose argument points to a glue specification.
-
- at c
-halfword new_glue(halfword q)
-{
-    halfword p = new_node(glue_node, normal);
-    width(p) = width(q);
-    stretch(p) = stretch(q);
-    shrink(p) = shrink(q);
-    stretch_order(p) = stretch_order(q);
-    shrink_order(p) = shrink_order(q);
-    return p;
-}
-
-@ Still another subroutine is needed: This one is sort of a combination
-of |new_param_glue| and |new_glue|. It creates a glue node for one of
-the current glue parameters, but it makes a fresh copy of the glue
-specification, since that specification will probably be subject to change,
-while the parameter will stay put.
-
-/*
-    The global variable |temp_ptr| is set to the address of the new spec.
-*/
-
- at c
-halfword new_skip_param(int n)
-{
-    halfword p = new_node(glue_node, n + 1);
-    halfword q = glue_par(n);
-    width(p) = width(q);
-    stretch(p) = stretch(q);
-    shrink(p) = shrink(q);
-    stretch_order(p) = stretch_order(q);
-    shrink_order(p) = shrink_order(q);
-    return p;
-}
-
-@ A |kern_node| has a |width| field to specify a (normally negative)
-amount of spacing. This spacing correction appears in horizontal lists
-between letters like A and V when the font designer said that it looks
-better to move them closer together or further apart. A kern node can
-also appear in a vertical list, when its `|width|' denotes additional
-spacing in the vertical direction. The |subtype| is either |normal| (for
-kerns inserted from font information or math mode calculations) or |explicit|
-(for kerns inserted from \.{\\kern} and \.{\\/} commands) or |acc_kern|
-(for kerns inserted from non-math accents) or |mu_glue| (for kerns
-inserted from \.{\\mkern} specifications in math formulas).
-
-@ The |new_kern| function creates a kern node having a given width.
-
- at c
-halfword new_kern(scaled w)
-{
-    halfword p = new_node(kern_node, normal);
-    width(p) = w;
-    return p;
-}
-
-@ A |penalty_node| specifies the penalty associated with line or page
-breaking, in its |penalty| field. This field is a fullword integer, but
-the full range of integer values is not used: Any penalty |>=10000| is
-treated as infinity, and no break will be allowed for such high values.
-Similarly, any penalty |<=-10000| is treated as negative infinity, and a
-break will be forced.
-
-@ Anyone who has been reading the last few sections of the program will
-be able to guess what comes next.
-
- at c
-halfword new_penalty(int m)
-{
-    halfword p = new_node(penalty_node, 0); /* the |subtype| is not used */
-    penalty(p) = m;
-    return p;
-}
-
-@ You might think that we have introduced enough node types by now. Well,
-almost, but there is one more: An |unset_node| has nearly the same format
-as an |hlist_node| or |vlist_node|; it is used for entries in \.{\\halign}
-or \.{\\valign} that are not yet in their final form, since the box
-dimensions are their ``natural'' sizes before any glue adjustment has been
-made. The |glue_set| word is not present; instead, we have a |glue_stretch|
-field, which contains the total stretch of order |glue_order| that is
-present in the hlist or vlist being boxed.
-Similarly, the |shift_amount| field is replaced by a |glue_shrink| field,
-containing the total shrink of order |glue_sign| that is present.
-The |subtype| field is called |span_count|; an unset box typically
-contains the data for |qo(span_count)+1| columns.
-Unset nodes will be changed to box nodes when alignment is completed.
-
-In fact, there are still more types coming. When we get to math formula
-processing we will see that a |style_node| has |type=14|; and a number
-of larger type codes will also be defined, for use in math mode only.
-
-Warning: If any changes are made to these data structure layouts, such as
-changing any of the node sizes or even reordering the words of nodes,
-the |copy_node_list| procedure and the memory initialization code
-below may have to be changed. Such potentially dangerous parts of the
-program are listed in the index under `data structure assumptions'.
-@!@^data structure assumptions@>
-However, other references to the nodes are made symbolically in terms of
-the \.{WEB} macro definitions above, so that format changes will leave
-\TeX's other algorithms intact.
-@^system dependencies@>
-
-@ This function creates a |local_paragraph| node
-
- at c
-
-halfword make_local_par_node(int mode)
-{
-    int callback_id;
-    halfword q;
-    halfword p = new_node(local_par_node,0);
-    local_pen_inter(p) = local_inter_line_penalty_par;
-    local_pen_broken(p) = local_broken_penalty_par;
-    if (local_left_box_par != null) {
-        q = copy_node_list(local_left_box_par);
-        local_box_left(p) = q;
-        local_box_left_width(p) = width(local_left_box_par);
-    }
-    if (local_right_box_par != null) {
-        q = copy_node_list(local_right_box_par);
-        local_box_right(p) = q;
-        local_box_right_width(p) = width(local_right_box_par);
-    }
-    local_par_dir(p) = par_direction_par;
-    /* callback with node passed */
-    callback_id = callback_defined(insert_local_par_callback);
-    if (callback_id > 0) {
-        int sfix = lua_gettop(Luas);
-        if (!get_callback(Luas, callback_id)) {
-            lua_settop(Luas, sfix);
-            return p;
-        }
-        nodelist_to_lua(Luas, p);
-        lua_push_local_par_mode(Luas,mode)
-        if (lua_pcall(Luas, 2, 0, 0) != 0) { /* 2 arg, 0 result */
-            char errmsg[256]; /* temp hack ... we will have a formatted error */
-            snprintf(errmsg, 255, "error: %s\n", lua_tostring(Luas, -1));
-            errmsg[255]='\0';
-            lua_settop(Luas, sfix);
-            normal_error("insert_local_par",errmsg); /* to be done */
-            return p;
-        }
-        lua_settop(Luas, sfix);
-    }
-    /* done */
-    return p;
-}
+% texnodes.w
+%
+% Copyright 2006-2010 Taco Hoekwater <taco@@luatex.org>
+%
+% 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 <http://www.gnu.org/licenses/>.
+
+@ @c
+
+#include "ptexlib.h"
+#include "lua/luatex-api.h"
+
+/* we can consider less mode sizes: 2 4 6 8 */
+
+@
+This module started out using NDEBUG to trigger checking invalid node usage,
+something that is needed because users can mess up nodes in Lua. At some point
+that code was always enabled so it is now always on but still can be recognized
+as additional code. And as the performance hit is close to zero so disabling
+makes no sense, not even to make it configureable. There is a little more memory
+used but that is neglectable compared to other memory usage.
+
+ at c
+#define MAX_CHAIN_SIZE   13 /* why not a bit larger */
+#define CHECK_NODE_USAGE  1 /* this triggers checking */
+
+memory_word *volatile varmem = NULL;
+
+#ifdef CHECK_NODE_USAGE
+    char *varmem_sizes = NULL;
+#endif
+
+halfword var_mem_max = 0;
+halfword rover = 0;
+
+halfword free_chain[MAX_CHAIN_SIZE] = { null };
+
+static int my_prealloc = 0;
+
+int fix_node_lists = 1; /* used in font and lang */
+
+halfword slow_get_node(int s);  /* defined below */
+
+#define fake_node       100
+#define fake_node_size  2
+#define fake_node_name "fake"
+
+#define variable_node_size 2
+
+/* core nodes */
+
+const char *node_fields_list[] = {
+    "attr", "width", "depth", "height", "dir", "shift", "glue_order", "glue_sign",
+    "glue_set", "head", NULL
+};
+const char *node_fields_rule[] = {
+    "attr", "width", "depth", "height", "dir", "index", NULL
+};
+const char *node_fields_insert[] = {
+    "attr", "cost", "depth", "height", "spec", "head", NULL
+};
+const char *node_fields_mark[] = {
+    "attr", "class", "mark", NULL
+};
+const char *node_fields_adjust[] = {
+    "attr", "head", NULL
+};
+const char *node_fields_disc[] = {
+    "attr", "pre", "post", "replace", "penalty", NULL
+};
+const char *node_fields_math[] = {
+    "attr", "surround", "width", "stretch", "shrink", "stretch_order", "shrink_order", NULL
+};
+const char *node_fields_glue[] = {
+    "attr", "leader", "width", "stretch", "shrink", "stretch_order", "shrink_order", NULL
+};
+const char *node_fields_kern[] = {
+    "attr", "kern", "expansion_factor", NULL
+};
+const char *node_fields_penalty[] = {
+    "attr", "penalty", NULL
+};
+const char *node_fields_unset[] = {
+    "attr", "width", "depth", "height", "dir", "shrink", "glue_order",
+    "glue_sign", "stretch", "span", "head", NULL
+};
+const char *node_fields_margin_kern[]  = {
+    "attr", "width", "glyph", NULL
+};
+const char *node_fields_glyph[] = {
+    "attr", "char", "font", "lang", "left", "right", "uchyph", "components",
+    "xoffset", "yoffset", "width", "height", "depth", "expansion_factor", NULL
+};
+const char *node_fields_inserting[] = {
+    "height", "last_ins_ptr", "best_ins_ptr",
+    "width", "stretch", "shrink", "stretch_order", "shrink_order", NULL
+};
+const char *node_fields_splitup[] = {
+    "height", "last_ins_ptr", "best_ins_ptr", "broken_ptr", "broken_ins", NULL
+};
+const char *node_fields_attribute[] = {
+    "number", "value", NULL
+};
+const char *node_fields_glue_spec[] = {
+    "width", "stretch", "shrink", "stretch_order", "shrink_order", NULL
+};
+const char *node_fields_attribute_list[] = {
+    NULL
+};
+const char *node_fields_local_par[] = {
+    "attr", "pen_inter", "pen_broken", "dir", "box_left", "box_left_width",
+    "box_right", "box_right_width", NULL
+};
+const char *node_fields_dir[] = {
+    "attr", "dir", "level", NULL
+};
+const char *node_fields_boundary[] = {
+    "attr", "value", NULL
+};
+
+/* math nodes */
+
+const char *node_fields_noad[] = {
+    "attr", "nucleus", "sub", "sup", NULL
+};
+
+const char *node_fields_style[] = {
+    "attr", "style", NULL
+};
+const char *node_fields_choice[] = {
+    "attr", "display", "text", "script", "scriptscript", NULL
+};
+const char *node_fields_radical[] = {
+    "attr", "nucleus", "sub", "sup", "left", "degree", "width", "options", NULL
+};
+const char *node_fields_fraction[] = {
+    "attr", "width", "num", "denom", "left", "right", "middle", "options", NULL
+};
+const char *node_fields_accent[] = {
+    "attr", "nucleus", "sub", "sup", "accent", "bot_accent", "top_accent",
+    "overlay_accent", "fraction", NULL
+};
+const char *node_fields_fence[] = {
+    "attr", "delim", "italic", "height", "depth", "options", "class", NULL
+};
+const char *node_fields_math_char[] = {
+    "attr", "fam", "char", NULL
+};
+const char *node_fields_sub_box[] = {
+    "attr", "head", NULL
+};
+const char *node_fields_sub_mlist[] = {
+    "attr", "head", NULL
+};
+const char *node_fields_math_text_char[] = {
+    "attr", "fam", "char", NULL
+};
+const char *node_fields_delim[] = {
+    "attr", "small_fam", "small_char", "large_fam", "large_char", NULL
+};
+
+/* whatsit nodes */
+
+const char *node_fields_whatsit_open[] = {
+    "attr", "stream", "name", "area", "ext", NULL
+};
+const char *node_fields_whatsit_write[] = {
+    "attr", "stream", "data", NULL
+};
+const char *node_fields_whatsit_close[] = {
+    "attr", "stream", NULL
+};
+const char *node_fields_whatsit_special[] = {
+    "attr", "data", NULL
+};
+const char *node_fields_whatsit_save_pos[] = {
+    "attr", NULL
+};
+const char *node_fields_whatsit_late_lua[] = {
+    "attr", "reg", "data", "name", "string", NULL
+};
+const char *node_fields_whatsit_user_defined[] = {
+    "attr", "user_id", "type", "value", NULL
+};
+
+/* pdf backend whatsit nodes */
+
+const char *node_fields_whatsit_pdf_literal[] = {
+    "attr", "mode", "data", NULL
+};
+const char *node_fields_whatsit_pdf_refobj[] = {
+    "attr", "objnum", NULL
+};
+const char *node_fields_whatsit_pdf_annot[] = {
+    "attr", "width", "depth", "height", "objnum", "data", NULL
+};
+const char *node_fields_whatsit_pdf_start_link[] = {
+    "attr", "width", "depth", "height", "objnum", "link_attr", "action", NULL
+};
+const char *node_fields_whatsit_pdf_end_link[] = {
+    "attr", NULL
+};
+const char *node_fields_whatsit_pdf_dest[] = {
+    "attr", "width", "depth", "height", "named_id", "dest_id", "dest_type",
+    "xyz_zoom", "objnum", NULL
+};
+const char *node_fields_whatsit_pdf_action[] = {
+    "action_type", "named_id", "action_id", "file", "new_window", "data", NULL
+};
+const char *node_fields_whatsit_pdf_thread[] = {
+    "attr", "width", "depth", "height",  "named_id", "thread_id", "thread_attr", NULL
+};
+const char *node_fields_whatsit_pdf_start_thread[] = {
+    "attr", "width", "depth", "height", "named_id", "thread_id", "thread_attr", NULL
+};
+const char *node_fields_whatsit_pdf_end_thread[] = {
+    "attr", NULL
+};
+const char *node_fields_whatsit_pdf_colorstack[] = {
+    "attr", "stack", "cmd", "data", NULL
+};
+const char *node_fields_whatsit_pdf_setmatrix[] = {
+    "attr", "data", NULL
+};
+const char *node_fields_whatsit_pdf_save[] = {
+    "attr", NULL
+};
+const char *node_fields_whatsit_pdf_restore[] = {
+    "attr", NULL
+};
+
+/* subtypes */
+
+const char *node_subtypes_glue[] = {
+    "userskip", "lineskip", "baselineskip", "parskip", "abovedisplayskip", "belowdisplayskip",
+    "abovedisplayshortskip", "belowdisplayshortskip", "leftskip", "rightskip", "topskip",
+    "splittopskip", "tabskip", "spaceskip", "xspaceskip", "parfillskip",
+    "mathskip", "thinmuskip", "medmuskip", "thickmuskip", NULL
+};
+const char *node_subtypes_mathglue[] = { /* 98+ */
+    "conditionalmathskip", "muglue", NULL
+};
+const char *node_subtypes_leader[] = { /* 100+ */
+    "leaders", "cleaders", "xleaders", "gleaders", NULL
+};
+const char *node_subtypes_fill[] = {
+    "stretch", "fi", "fil", "fill", "filll", NULL
+};
+const char *node_subtypes_boundary[] = {
+    "cancel", "user", "protrusion", "word", NULL
+};
+const char *node_subtypes_penalty[] = {
+    "userpenalty", "linebreakpenalty", "linepenalty", "wordpenalty", "finalpenalty",
+    "noadpenalty", "beforedisplaypenalty", "afterdisplaypenalty", "equationnumberpenalty", NULL
+};
+const char *node_subtypes_kern[] = {
+    "fontkern", "userkern", "accentkern", "italiccorrection", NULL
+};
+const char *node_subtypes_rule[] = {
+    "normal", "box", "image", "empty", "user", "over", "under", "fraction", "radical", NULL
+};
+const char *node_subtypes_glyph[] = {
+    "character", "glyph", "ligature", "ghost", "left", "right", NULL
+};
+const char *node_subtypes_disc[] = {
+    "discretionary", "explicit", "automatic", "regular", "first", "second", NULL
+};
+const char *node_subtypes_marginkern[] = {
+    "left", "right", NULL
+};
+const char *node_subtypes_list[] = {
+    "unknown", "line", "box", "indent", "alignment", "cell", "equation", "equationnumber", NULL
+};
+const char *node_subtypes_adjust[] = {
+    "normal", "pre", NULL
+};
+const char *node_subtypes_math[] = {
+    "beginmath", "endmath", NULL
+};
+const char *node_subtypes_noad[] = {
+    "ord", "opdisplaylimits", "oplimits", "opnolimits", "bin", "rel", "open", "close",
+    "punct", "inner", "under", "over", "vcenter", NULL
+};
+const char *node_subtypes_radical[] = {
+    "radical", "uradical", "uroot", "uunderdelimiter", "uoverdelimiter", "udelimiterunder",
+    "udelimiterover", NULL
+};
+const char *node_subtypes_accent[] = {
+    "bothflexible", "fixedtop", "fixedbottom", "fixedboth", NULL,
+};
+const char *node_subtypes_fence[] = {
+    "unset", "left", "middle", "right", NULL
+};
+
+node_info node_data[] = { /* the last entry in a row is the etex number */
+    { hlist_node,          box_node_size,         node_fields_list,                          "hlist",           1 },
+    { vlist_node,          box_node_size,         node_fields_list,                          "vlist",           2 },
+    { rule_node,           rule_node_size,        node_fields_rule,                          "rule",            3 },
+    { ins_node,            ins_node_size,         node_fields_insert,                        "ins",             4 },
+    { mark_node,           mark_node_size,        node_fields_mark,                          "mark",            5 },
+    { adjust_node,         adjust_node_size,      node_fields_adjust,                        "adjust",          6 },
+    { boundary_node,       boundary_node_size,    node_fields_boundary,                      "boundary",       -1 },
+    { disc_node,           disc_node_size,        node_fields_disc,                          "disc",            8 },
+    { whatsit_node,        -1,                    NULL,                                      "whatsit",         9 },
+    { local_par_node,      local_par_size,        node_fields_local_par,                     "local_par",      -1 },
+    { dir_node,            dir_node_size,         node_fields_dir,                           "dir",            -1 },
+    { math_node,           math_node_size,        node_fields_math,                          "math",           10 },
+    { glue_node,           glue_node_size,        node_fields_glue,                          "glue",           11 },
+    { kern_node,           kern_node_size,        node_fields_kern,                          "kern",           12 },
+    { penalty_node,        penalty_node_size,     node_fields_penalty,                       "penalty",        13 },
+    { unset_node,          box_node_size,         node_fields_unset,                         "unset",          14 },
+    { style_node,          style_node_size,       node_fields_style,                         "style",          15 },
+    { choice_node,         style_node_size,       node_fields_choice,                        "choice",         15 },
+    { simple_noad,         noad_size,             node_fields_noad,                          "noad",           15 },
+    { radical_noad,        radical_noad_size,     node_fields_radical,                       "radical",        15 },
+    { fraction_noad,       fraction_noad_size,    node_fields_fraction,                      "fraction",       15 },
+    { accent_noad,         accent_noad_size,      node_fields_accent,                        "accent",         15 },
+    { fence_noad,          fence_noad_size,       node_fields_fence,                         "fence",          15 },
+    { math_char_node,      math_kernel_node_size, node_fields_math_char,                     "math_char",      15 },
+    { sub_box_node,        math_kernel_node_size, node_fields_sub_box,                       "sub_box",        15 },
+    { sub_mlist_node,      math_kernel_node_size, node_fields_sub_mlist,                     "sub_mlist",      15 },
+    { math_text_char_node, math_kernel_node_size, node_fields_math_text_char,                "math_text_char", 15 },
+    { delim_node,          math_shield_node_size, node_fields_delim,                         "delim",          15 },
+    { margin_kern_node,    margin_kern_node_size, node_fields_margin_kern,                   "margin_kern",    -1 },
+    { glyph_node,          glyph_node_size,       node_fields_glyph,                         "glyph",           0 },
+    { align_record_node,   box_node_size,         NULL,                                      "align_record",   -1 },
+    { pseudo_file_node,    pseudo_file_node_size, NULL,                                      "pseudo_file",    -1 },
+    { pseudo_line_node,    variable_node_size,    NULL,                                      "pseudo_line",    -1 },
+    { inserting_node,      page_ins_node_size,    node_fields_inserting,                     "page_insert",    -1 },
+    { split_up_node,       page_ins_node_size,    node_fields_splitup,                       "split_insert",   -1 },
+    { expr_node,           expr_node_size,        NULL,                                      "expr_stack",     -1 },
+    { nesting_node,        nesting_node_size,     NULL,                                      "nested_list",    -1 },
+    { span_node,           span_node_size,        NULL,                                      "span",           -1 },
+    { attribute_node,      attribute_node_size,   node_fields_attribute,                     "attribute",      -1 },
+    { glue_spec_node,      glue_spec_size,        node_fields_glue_spec,                     "glue_spec",      -1 },
+    { attribute_list_node, attribute_node_size,   node_fields_attribute_list,                "attribute_list", -1 },
+    { temp_node,           temp_node_size,        NULL,                                      "temp",           -1 },
+    { align_stack_node,    align_stack_node_size, NULL,                                      "align_stack",    -1 },
+    { movement_node,       movement_node_size,    NULL,                                      "movement_stack", -1 },
+    { if_node,             if_node_size,          NULL,                                      "if_stack",       -1 },
+    { unhyphenated_node,   active_node_size,      NULL,                                      "unhyphenated",   -1 },
+    { hyphenated_node,     active_node_size,      NULL,                                      "hyphenated",     -1 },
+    { delta_node,          delta_node_size,       NULL,                                      "delta",          -1 },
+    { passive_node,        passive_node_size,     NULL,                                      "passive",        -1 },
+    { shape_node,          variable_node_size,    NULL,                                      "shape",          -1 },
+    { -1,                 -1,                     NULL,                                      NULL,             -1 },
+};
+
+const char *node_subtypes_pdf_destination[] = {
+    "xyz", "fit", "fith", "fitv", "fitb", "fitbh", "fitbv", "fitr", NULL
+};
+const char *node_subtypes_pdf_literal[] = {
+    "origin", "page", "direct", NULL
+};
+
+node_info whatsit_node_data[] = {
+    { open_node,         open_node_size,               node_fields_whatsit_open,             "open",             -1 },
+    { write_node,        write_node_size,              node_fields_whatsit_write,            "write",            -1 },
+    { close_node,        close_node_size,              node_fields_whatsit_close,            "close",            -1 },
+    { special_node,      special_node_size,            node_fields_whatsit_special,          "special",          -1 },
+    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
+    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
+    { save_pos_node,     save_pos_node_size,           node_fields_whatsit_save_pos,         "save_pos",         -1 },
+    { late_lua_node,     late_lua_node_size,           node_fields_whatsit_late_lua,         "late_lua",         -1 },
+    { user_defined_node, user_defined_node_size,       node_fields_whatsit_user_defined,     "user_defined",     -1 },
+    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
+    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
+    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
+    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
+    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
+    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
+    { fake_node,         fake_node_size,               NULL,                                 fake_node_name,     -1 },
+    /* here starts the dvi backend section, todo: a separate list  */
+    /* nothing for dvi */
+    /* here starts the pdf backend section, todo: a separate list  */
+    { pdf_literal_node,      write_node_size,          node_fields_whatsit_pdf_literal,      "pdf_literal",      -1 },
+    { pdf_refobj_node,       pdf_refobj_node_size,     node_fields_whatsit_pdf_refobj,       "pdf_refobj",       -1 },
+    { pdf_annot_node,        pdf_annot_node_size,      node_fields_whatsit_pdf_annot,        "pdf_annot",        -1 },
+    { pdf_start_link_node,   pdf_annot_node_size,      node_fields_whatsit_pdf_start_link,   "pdf_start_link",   -1 },
+    { pdf_end_link_node,     pdf_end_link_node_size,   node_fields_whatsit_pdf_end_link,     "pdf_end_link",     -1 },
+    { pdf_dest_node,         pdf_dest_node_size,       node_fields_whatsit_pdf_dest,         "pdf_dest",         -1 },
+    { pdf_action_node,       pdf_action_size,          node_fields_whatsit_pdf_action,       "pdf_action",       -1 },
+    { pdf_thread_node,       pdf_thread_node_size,     node_fields_whatsit_pdf_thread,       "pdf_thread",       -1 },
+    { pdf_start_thread_node, pdf_thread_node_size,     node_fields_whatsit_pdf_start_thread, "pdf_start_thread", -1 },
+    { pdf_end_thread_node,   pdf_end_thread_node_size, node_fields_whatsit_pdf_end_thread,   "pdf_end_thread",   -1 },
+    { pdf_thread_data_node,  pdf_thread_node_size,     NULL,                                 "pdf_thread_data",  -1 },
+    { pdf_link_data_node,    pdf_annot_node_size,      NULL,                                 "pdf_link_data",    -1 },
+    { pdf_colorstack_node,   pdf_colorstack_node_size, node_fields_whatsit_pdf_colorstack,   "pdf_colorstack",   -1 },
+    { pdf_setmatrix_node,    pdf_setmatrix_node_size,  node_fields_whatsit_pdf_setmatrix,    "pdf_setmatrix",    -1 },
+    { pdf_save_node,         pdf_save_node_size,       node_fields_whatsit_pdf_save,         "pdf_save",         -1 },
+    { pdf_restore_node,      pdf_restore_node_size,    node_fields_whatsit_pdf_restore,      "pdf_restore",      -1 },
+    /* done */
+    { -1,                    -1,                       NULL,                                 NULL,               -1 },
+};
+
+#define last_whatsit_node pdf_restore_node
+
+@
+When we copy a node list, there are several possibilities: we do the same as a new node,
+we copy the entry to the table in properties (a reference), we do a deep copy of a table
+in the properties, we create a new table and give it the original one as a metatable.
+After some experiments (that also included timing) with these scenarios I decided that a
+deep copy made no sense, nor did nilling. In the end both the shallow copy and the metatable
+variant were both ok, although the second ons is slower. The most important aspect to keep
+in mind is that references to other nodes in properties no longer can be valid for that
+copy. We could use two tables (one unique and one shared) or metatables but that only
+complicates matters.
+
+When defining a new node, we could already allocate a table but it is rather easy to do
+that at the lua end e.g. using a metatable __index method. That way it is under macro
+package control.
+
+When deleting a node, we could keep the slot (e.g. setting it to false) but it could make
+memory consumption raise unneeded when we have temporary large node lists and after that
+only small lists.
+
+So, in the end this is what we ended up with. For the record, I also experimented with the
+following:
+
+- copy attributes to the properties so that we have fast access at the lua end: in the end
+  the overhead is not compensated by speed and convenience, in fact, attributes are not
+  that slow when it comes to accessing them
+
+- a bitset in the node but again the gain compared to attributes is neglectable and it also
+  demands a pretty string agreement over what bit represents what, and this is unlikely to
+  succeed in the tex community (I could use it for font handling, which is cross package,
+  but decided that it doesn't pay off
+
+In case one wonders why properties make sense then, well, it is not so much speed that we
+gain, but more convenience: storing all kind of (temporary) data in attributes is no fun and
+this mechanism makes sure that properties are cleaned up when a node is freed. Also, the
+advantage of a more or less global properties table is that we stay at the lua end. An
+alternative is to store a reference in the node itself but that is complicated by the fact
+that the register has some limitations (no numeric keys) and we also don't want to mess with
+it too much.
+
+ at c
+int lua_properties_level         = 0 ; /* can be private */
+int lua_properties_enabled       = 0 ;
+int lua_properties_use_metatable = 0 ;
+
+@
+We keep track of nesting so that we don't oveflow the stack, and, what is more
+important, don't keep resolving the registry index.
+
+ at c
+#define lua_properties_push do { \
+    if (lua_properties_enabled) { \
+        lua_properties_level = lua_properties_level + 1 ; \
+        if (lua_properties_level == 1) { \
+            lua_get_metatablelua_l(Luas,node_properties); \
+        } \
+    } \
+} while(0)
+
+#define lua_properties_pop do { \
+    if (lua_properties_enabled) { \
+        if (lua_properties_level == 1) \
+            lua_pop(Luas,1); \
+        lua_properties_level = lua_properties_level - 1 ; \
+    } \
+} while(0)
+
+/* No setting is needed: */
+
+#define lua_properties_set(target) do { \
+} while(0)
+
+/* Resetting boils down to nilling. */
+
+#define lua_properties_reset(target) do { \
+    if (lua_properties_enabled) { \
+        if (lua_properties_level == 0) { \
+            lua_get_metatablelua_l(Luas,node_properties); \
+            lua_pushnil(Luas); \
+            lua_rawseti(Luas,-2,target); \
+            lua_pop(Luas,1); \
+        } else { \
+            lua_pushnil(Luas); \
+            lua_rawseti(Luas,-2,target); \
+        } \
+    } \
+} while(0)
+
+/*
+    For a moment I considered supporting all kind of data types but in practice
+    that makes no sense. So we stick to a cheap shallow copy with as option a
+    metatable. Btw, a deep copy would look like this:
+
+    static void copy_lua_table(lua_State* L, int index) {
+        lua_newtable(L);
+        lua_pushnil(L);
+        while(lua_next(L, index-1) != 0) {
+            lua_pushvalue(L, -2);
+            lua_insert(L, -2);
+            if (lua_type(L,-1)==LUA_TTABLE)
+                copy_lua_table(L,-1);
+            lua_settable(L, -4);
+        }
+        lua_pop(L,1);
+    }
+
+    #define lua_properties_copy(target, source) do { \
+        if (lua_properties_enabled) { \
+            lua_pushinteger(Luas,source); \
+            lua_rawget(Luas,-2); \
+            if (lua_type(Luas,-1)==LUA_TTABLE) { \
+                copy_lua_table(Luas,-1); \
+                lua_pushinteger(Luas,target); \
+                lua_insert(Luas,-2); \
+                lua_rawset(Luas,-3); \
+            } else { \
+                lua_pop(Luas,1); \
+            } \
+        } \
+    } while(0)
+
+*/
+
+/* isn't there a faster way to metatable? */
+
+/*
+
+#define lua_properties_copy(target,source) do { \
+    if (lua_properties_enabled) { \
+        if (lua_properties_level == 0) { \
+            lua_get_metatablelua_l(Luas,node_properties); \
+            lua_rawgeti(Luas,-1,source); \
+            if (lua_type(Luas,-1)==LUA_TTABLE) { \
+                if (lua_properties_use_metatable) { \
+                    lua_newtable(Luas); \
+                    lua_insert(Luas,-2); \
+                    lua_setfield(Luas,-2,"__index"); \
+                    lua_newtable(Luas); \
+                    lua_insert(Luas,-2); \
+                    lua_setmetatable(Luas,-2); \
+                } \
+                lua_rawseti(Luas,-2,target); \
+            } else { \
+                lua_pop(Luas,1); \
+            } \
+            lua_pop(Luas,1); \
+        } else { \
+            lua_rawgeti(Luas,-1,source); \
+            if (lua_type(Luas,-1)==LUA_TTABLE) { \
+                if (lua_properties_use_metatable) { \
+                    lua_newtable(Luas); \
+                    lua_insert(Luas,-2); \
+                    lua_setfield(Luas,-2,"__index"); \
+                    lua_newtable(Luas); \
+                    lua_insert(Luas,-2); \
+                    lua_setmetatable(Luas,-2); \
+                } \
+                lua_rawseti(Luas,-2,target); \
+            } else { \
+                lua_pop(Luas,1); \
+            } \
+        } \
+    } \
+} while(0)
+
+*/
+
+/*
+    A simple testrun on many pages of dumb text shows 1% gain (of course it depends
+    on how properties are used but some other tests confirm it).
+*/
+
+#define lua_properties_copy(target,source) do { \
+    if (lua_properties_enabled) { \
+        if (lua_properties_level == 0) { \
+            lua_get_metatablelua_l(Luas,node_properties); \
+            lua_rawgeti(Luas,-1,source); \
+            if (lua_type(Luas,-1)==LUA_TTABLE) { \
+                if (lua_properties_use_metatable) { \
+                    lua_newtable(Luas); \
+                    lua_insert(Luas,-2); \
+                    lua_push_string_by_name(Luas,__index); \
+                    lua_insert(Luas,-2); \
+                    lua_rawset(Luas, -3); \
+                    lua_newtable(Luas); \
+                    lua_insert(Luas,-2); \
+                    lua_setmetatable(Luas,-2); \
+                } \
+                lua_rawseti(Luas,-2,target); \
+            } else { \
+                lua_pop(Luas,1); \
+            } \
+            lua_pop(Luas,1); \
+        } else { \
+            lua_rawgeti(Luas,-1,source); \
+            if (lua_type(Luas,-1)==LUA_TTABLE) { \
+                if (lua_properties_use_metatable) { \
+                    lua_newtable(Luas); \
+                    lua_insert(Luas,-2); \
+                    lua_push_string_by_name(Luas,__index); \
+                    lua_insert(Luas,-2); \
+                    lua_rawset(Luas, -3); \
+                    lua_newtable(Luas); \
+                    lua_insert(Luas,-2); \
+                    lua_setmetatable(Luas,-2); \
+                } \
+                lua_rawseti(Luas,-2,target); \
+            } else { \
+                lua_pop(Luas,1); \
+            } \
+        } \
+    } \
+} while(0)
+
+/* Here end the property handlers. */
+
+@ @c
+int valid_node(halfword p)
+{
+    if (p > my_prealloc && p < var_mem_max) {
+#ifdef CHECK_NODE_USAGE
+        if (varmem_sizes[p] > 0) {
+            return 1;
+        }
+#else
+        return 1;
+#endif
+    }
+    return 0;
+}
+
+@ @c
+static int test_count = 1;
+
+#define dorangetest(a,b,c)  do {                                 \
+    if (!(b>=0 && b<c)) {                                        \
+        fprintf(stdout,"For node p:=%d, 0<=%d<%d (l.%d,r.%d)\n", \
+            (int)a, (int)b, (int)c, __LINE__,test_count);        \
+        confusion("node range test failed");                     \
+    } } while (0)
+
+#define dotest(a,b,c) do {                                     \
+    if (b!=c) {                                                \
+        fprintf(stdout,"For node p:=%d, %d==%d (l.%d,r.%d)\n", \
+            (int)a, (int)b, (int)c, __LINE__,test_count);      \
+        confusion("node test failed");                         \
+    } } while (0)
+
+#define check_action_ref(a)    { dorangetest(p,a,var_mem_max); }
+#define check_attribute_ref(a) { dorangetest(p,a,var_mem_max); }
+#define check_token_ref(a) { \
+    if (type(p) == whatsit_node) { \
+        formatted_error("nodes","fuzzy token cleanup in whatsit node with id %i and subtype %i",type(p),subtype(p)); \
+    } else { \
+        formatted_error("nodes","fuzzy token cleanup in node with id %i",type(p)); \
+    } \
+}
+
+#ifdef CHECK_NODE_USAGE
+
+static void check_static_node_mem(void)
+{
+    dotest(zero_glue, width(zero_glue), 0);
+    dotest(zero_glue, type(zero_glue), glue_spec_node);
+    dotest(zero_glue, vlink(zero_glue), null);
+    dotest(zero_glue, stretch(zero_glue), 0);
+    dotest(zero_glue, stretch_order(zero_glue), normal);
+    dotest(zero_glue, shrink(zero_glue), 0);
+    dotest(zero_glue, shrink_order(zero_glue), normal);
+
+    dotest(sfi_glue, width(sfi_glue), 0);
+    dotest(sfi_glue, type(sfi_glue), glue_spec_node);
+    dotest(sfi_glue, vlink(sfi_glue), null);
+    dotest(sfi_glue, stretch(sfi_glue), 0);
+    dotest(sfi_glue, stretch_order(sfi_glue), sfi);
+    dotest(sfi_glue, shrink(sfi_glue), 0);
+    dotest(sfi_glue, shrink_order(sfi_glue), normal);
+
+    dotest(fil_glue, width(fil_glue), 0);
+    dotest(fil_glue, type(fil_glue), glue_spec_node);
+    dotest(fil_glue, vlink(fil_glue), null);
+    dotest(fil_glue, stretch(fil_glue), unity);
+    dotest(fil_glue, stretch_order(fil_glue), fil);
+    dotest(fil_glue, shrink(fil_glue), 0);
+    dotest(fil_glue, shrink_order(fil_glue), normal);
+
+    dotest(fill_glue, width(fill_glue), 0);
+    dotest(fill_glue, type(fill_glue), glue_spec_node);
+    dotest(fill_glue, vlink(fill_glue), null);
+    dotest(fill_glue, stretch(fill_glue), unity);
+    dotest(fill_glue, stretch_order(fill_glue), fill);
+    dotest(fill_glue, shrink(fill_glue), 0);
+    dotest(fill_glue, shrink_order(fill_glue), normal);
+
+    dotest(ss_glue, width(ss_glue), 0);
+    dotest(ss_glue, type(ss_glue), glue_spec_node);
+    dotest(ss_glue, vlink(ss_glue), null);
+    dotest(ss_glue, stretch(ss_glue), unity);
+    dotest(ss_glue, stretch_order(ss_glue), fil);
+    dotest(ss_glue, shrink(ss_glue), unity);
+    dotest(ss_glue, shrink_order(ss_glue), fil);
+
+    dotest(fil_neg_glue, width(fil_neg_glue), 0);
+    dotest(fil_neg_glue, type(fil_neg_glue), glue_spec_node);
+    dotest(fil_neg_glue, vlink(fil_neg_glue), null);
+    dotest(fil_neg_glue, stretch(fil_neg_glue), -unity);
+    dotest(fil_neg_glue, stretch_order(fil_neg_glue), fil);
+    dotest(fil_neg_glue, shrink(fil_neg_glue), 0);
+    dotest(fil_neg_glue, shrink_order(fil_neg_glue), normal);
+}
+
+static void node_mem_dump(halfword p)
+{
+    halfword r;
+    for (r = my_prealloc + 1; r < var_mem_max; r++) {
+        if (vlink(r) == p) {
+            halfword s = r;
+            while (s > my_prealloc && varmem_sizes[s] == 0) {
+                s--;
+            }
+            if (s != null
+                && s != my_prealloc
+                && s != var_mem_max
+                && (r - s) < get_node_size(type(s), subtype(s))
+                && alink(s) != p) {
+                if (type(s) == disc_node) {
+                    fprintf(stdout,"  pointed to from %s node %d (vlink %d, alink %d): ",
+                            get_node_name(type(s), subtype(s)), (int) s,
+                            (int) vlink(s), (int) alink(s));
+                    fprintf(stdout, "pre_break(%d,%d,%d), ",
+                            (int) vlink_pre_break(s), (int) tlink(pre_break(s)),
+                            (int) alink(pre_break(s)));
+                    fprintf(stdout, "post_break(%d,%d,%d), ",
+                            (int) vlink_post_break(s),
+                            (int) tlink(post_break(s)),
+                            (int) alink(post_break(s)));
+                    fprintf(stdout, "no_break(%d,%d,%d)",
+                            (int) vlink_no_break(s), (int) tlink(no_break(s)),
+                            (int) alink(no_break(s)));
+                    fprintf(stdout, "\n");
+                } else {
+                    if (vlink(s) == p
+                        || (type(s) == glyph_node && lig_ptr (s) == p)
+                        || (type(s) == vlist_node && list_ptr(s) == p)
+                        || (type(s) == hlist_node && list_ptr(s) == p)
+                        || (type(s) == unset_node && list_ptr(s) == p)
+                        || (type(s) == ins_node   && ins_ptr (s) == p)
+                        ) {
+                        fprintf(stdout,"  pointed to from %s node %d (vlink %d, alink %d): ",
+                                get_node_name(type(s), subtype(s)), (int) s,
+                                (int) vlink(s), (int) alink(s));
+                        if (type(s) == glyph_node) {
+                            fprintf(stdout, "lig_ptr(%d)", (int) lig_ptr(s));
+                        } else if (type(s) == vlist_node || type(s) == hlist_node) {
+                            fprintf(stdout, "list_ptr(%d)", (int) list_ptr(s));
+                        }
+                        fprintf(stdout, "\n");
+                    } else {
+                        if ((type(s) != penalty_node) && (type(s) != math_node) && (type(s) != kern_node)) {
+                            fprintf(stdout, "  pointed to from %s node %d\n",
+                                get_node_name(type(s), subtype(s)), (int) s);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+#endif
+
+static int free_error(halfword p)
+{
+    if (p > my_prealloc && p < var_mem_max) {
+#ifdef CHECK_NODE_USAGE
+        int i;
+        if (varmem_sizes[p] == 0) {
+            check_static_node_mem();
+            for (i = (my_prealloc + 1); i < var_mem_max; i++) {
+                if (varmem_sizes[i] > 0) {
+                    check_node(i);
+                }
+            }
+            test_count++;
+            if (type(p) == glyph_node) {
+                formatted_error("nodes", "attempt to double-free glyph (%c) node %d, ignored", (int) character(p), (int) p);
+            } else {
+                formatted_error("nodes", "attempt to double-free %s node %d, ignored", get_node_name(type(p), subtype(p)), (int) p);
+            }
+            node_mem_dump(p);
+            return 1;
+        }
+#endif
+    } else {
+        formatted_error("nodes", "attempt to free an impossible node %d", (int) p);
+        return 1;
+    }
+    return 0;
+}
+
+@ @c
+static int copy_error(halfword p)
+{
+    if (p >= 0 && p < var_mem_max) {
+#ifdef CHECK_NODE_USAGE
+        if (p > my_prealloc && varmem_sizes[p] == 0) {
+            if (type(p) == glyph_node) {
+                formatted_warning("nodes", "attempt to copy free glyph (%c) node %d, ignored", (int) character(p), (int) p);
+            } else {
+                formatted_warning("nodes", "attempt to copy free %s node %d, ignored", get_node_name(type(p), subtype(p)), (int) p);
+            }
+            return 1;
+        }
+#endif
+    } else {
+        formatted_error("nodes", "attempt to copy an impossible node %d", (int) p);
+        return 1;
+    }
+    return 0;
+}
+
+@ @c
+halfword new_node(int i, int j)
+{
+    int s = get_node_size(i, j);
+    halfword n = get_node(s);
+    /*
+        It should be possible to do this memset at |free_node()|.
+
+        Both type() and subtype() will be set below, and vlink() is
+        set to null by |get_node()|, so we can do we clearing one
+        word less than |s|
+    */
+    (void) memset((void *) (varmem + n + 1), 0, (sizeof(memory_word) * ((unsigned) s - 1)));
+    switch (i) {
+        case glyph_node:
+            init_lang_data(n);
+            break;
+        case hlist_node:
+        case vlist_node:
+            box_dir(n) = -1;
+            break;
+        case disc_node:
+            pre_break(n) = pre_break_head(n);
+            type(pre_break(n)) = nesting_node;
+            subtype(pre_break(n)) = pre_break_head(0);
+            post_break(n) = post_break_head(n);
+            type(post_break(n)) = nesting_node;
+            subtype(post_break(n)) = post_break_head(0);
+            no_break(n) = no_break_head(n);
+            type(no_break(n)) = nesting_node;
+            subtype(no_break(n)) = no_break_head(0);
+            break;
+        case rule_node:
+            depth(n) = null_flag;
+            height(n) = null_flag;
+            width(n) = null_flag;
+            rule_dir(n) = -1;
+            rule_index(n) = 0;
+            rule_transform(n) = 0;
+            break;
+        case whatsit_node:
+            if (j == open_node) {
+                open_name(n) = get_nullstr();
+                open_area(n) = open_name(n);
+                open_ext(n) = open_name(n);
+            }
+            break;
+        case unset_node:
+            width(n) = null_flag;
+            break;
+        case pseudo_line_node:
+        case shape_node:
+            /* this is a trick that makes |pseudo_files| slightly slower,
+             but the overall allocation faster then an explicit test
+             at the top of |new_node()|.
+             */
+            if (j>0) {
+              free_node(n, variable_node_size);
+              n = slow_get_node(j);
+              (void) memset((void *) (varmem + n + 1), 0, (sizeof(memory_word) * ((unsigned) j - 1)));
+            }
+            break;
+        default:
+            break;
+    }
+    if (synctex_par) {
+        /* handle synctex extension */
+        switch (i) {
+            case math_node:
+                synctex_tag_math(n) = cur_input.synctex_tag_field;
+                synctex_line_math(n) = line;
+                break;
+            case glue_node:
+                synctex_tag_glue(n) = cur_input.synctex_tag_field;
+                synctex_line_glue(n) = line;
+                break;
+            case kern_node:
+                if (j != 0) {
+                    synctex_tag_kern(n) = cur_input.synctex_tag_field;
+                    synctex_line_kern(n) = line;
+                }
+                break;
+            case hlist_node:
+            case vlist_node:
+            case unset_node:
+                synctex_tag_box(n) = cur_input.synctex_tag_field;
+                synctex_line_box(n) = line;
+                break;
+            case rule_node:
+                synctex_tag_rule(n) = cur_input.synctex_tag_field;
+                synctex_line_rule(n) = line;
+                break;
+        }
+    }
+    /* take care of attributes */
+    if (nodetype_has_attributes(i)) {
+        build_attribute_list(n);
+        /* lua_properties_set */
+    }
+    type(n) = (quarterword) i;
+    subtype(n) = (quarterword) j;
+    return n;
+}
+
+halfword raw_glyph_node(void)
+{
+    register halfword n = get_node(glyph_node_size);
+    (void) memset((void *) (varmem + n + 1), 0, (sizeof(memory_word) * (glyph_node_size - 1)));
+    type(n) = glyph_node;
+    subtype(n) = 0;
+    return n;
+}
+
+halfword new_glyph_node(void)
+{
+    register halfword n = get_node(glyph_node_size);
+    (void) memset((void *) (varmem + n + 1), 0, (sizeof(memory_word) * (glyph_node_size - 1)));
+    type(n) = glyph_node;
+    subtype(n) = 0;
+    build_attribute_list(n);
+    /* lua_properties_set */
+    return n;
+}
+
+@ makes a duplicate of the node list that starts at |p| and returns a
+pointer to the new list
+
+ at c
+halfword do_copy_node_list(halfword p, halfword end)
+{
+    halfword q = null; /* previous position in new list */
+    halfword h = null; /* head of the list */
+    register halfword s ;
+    lua_properties_push; /* saves stack and time */
+    while (p != end) {
+        s = copy_node(p);
+        if (h == null) {
+            h = s;
+        } else {
+            couple_nodes(q, s);
+        }
+        q = s;
+        p = vlink(p);
+    }
+    lua_properties_pop; /* saves stack and time */
+    return h;
+}
+
+halfword copy_node_list(halfword p)
+{
+    return do_copy_node_list(p, null);
+}
+
+#define copy_sub_list(target,source) do { \
+     if (source != null) { \
+         s = do_copy_node_list(source, null); \
+         target = s; \
+     } else { \
+         target = null; \
+     } \
+ } while (0)
+
+#define copy_sub_node(target,source) do { \
+    if (source != null) { \
+        s = copy_node(source); \
+        target = s ; \
+    } else { \
+        target = null; \
+    } \
+} while (0)
+
+@ make a dupe of a single node
+
+ at c
+static void copy_node_wrapup_core(halfword p, halfword r)
+{
+    halfword s ;
+    switch (subtype(p)) {
+        case write_node:
+        case special_node:
+            add_token_ref(write_tokens(p));
+            break;
+        case late_lua_node:
+            copy_late_lua(r, p);
+            break;
+        case user_defined_node:
+            switch (user_node_type(p)) {
+            case 'a':
+                add_node_attr_ref(user_node_value(p));
+                break;
+            case 'l':
+                copy_user_lua(r, p);
+                break;
+            case 'n':
+                s = copy_node_list(user_node_value(p));
+                user_node_value(r) = s;
+                break;
+            case 's':
+                /* |add_string_ref(user_node_value(p));| */
+                break;
+            case 't':
+                add_token_ref(user_node_value(p));
+                break;
+            }
+            break;
+        default:
+            break ;
+    }
+}
+
+void copy_node_wrapup_dvi(halfword p, halfword r)
+{
+}
+
+void copy_node_wrapup_pdf(halfword p, halfword r)
+{
+    switch(subtype(p)) {
+        case pdf_literal_node:
+            copy_pdf_literal(r, p);
+            break;
+        case pdf_colorstack_node:
+            if (pdf_colorstack_cmd(p) <= colorstack_data)
+                add_token_ref(pdf_colorstack_data(p));
+            break;
+        case pdf_setmatrix_node:
+            add_token_ref(pdf_setmatrix_data(p));
+            break;
+        case pdf_annot_node:
+            add_token_ref(pdf_annot_data(p));
+            break;
+        case pdf_start_link_node:
+            if (pdf_link_attr(r) != null)
+                add_token_ref(pdf_link_attr(r));
+            add_action_ref(pdf_link_action(r));
+            break;
+        case pdf_dest_node:
+            if (pdf_dest_named_id(p) > 0)
+                add_token_ref(pdf_dest_id(p));
+            break;
+        case pdf_thread_node:
+        case pdf_start_thread_node:
+            if (pdf_thread_named_id(p) > 0)
+                add_token_ref(pdf_thread_id(p));
+            if (pdf_thread_attr(p) != null)
+                add_token_ref(pdf_thread_attr(p));
+            break;
+        default:
+            break;
+    }
+}
+
+halfword copy_node(const halfword p)
+{
+    halfword r;                 /* current node being fabricated for new list */
+    halfword w;                 /* whatsit subtype */
+    halfword t;                 /* type of node */
+    register halfword s;        /* a helper variable for copying into variable mem  */
+    register int i;
+    if (copy_error(p)) {
+        r = new_node(temp_node, 0);
+        return r;
+    }
+    t = type(p);
+    i = get_node_size(t,subtype(p));
+    r = get_node(i);
+
+    (void) memcpy((void *) (varmem + r), (void *) (varmem + p), (sizeof(memory_word) * (unsigned) i));
+
+    /* possible speedup: */
+    /*
+        if t == glue_spec) {
+            return r;
+        }
+    */
+
+    if (synctex_par) {
+        /* handle synctex extension */
+        switch (t) {
+            case math_node:
+                synctex_tag_math(r) = cur_input.synctex_tag_field;
+                synctex_line_math(r) = line;
+                break;
+            case kern_node:
+                synctex_tag_kern(r) = cur_input.synctex_tag_field;
+                synctex_line_kern(r) = line;
+                break;
+        }
+    }
+    if (nodetype_has_attributes(t)) {
+        add_node_attr_ref(node_attr(p));
+        alink(r) = null;
+        lua_properties_copy(r,p);
+    }
+    vlink(r) = null;
+
+    switch (t) {
+        case glyph_node:
+            copy_sub_list(lig_ptr(r),lig_ptr(p)) ;
+            break;
+        case glue_node:
+            copy_sub_list(leader_ptr(r),leader_ptr(p)) ;
+            break;
+        case hlist_node:
+        case vlist_node:
+        case unset_node:
+            copy_sub_list(list_ptr(r),list_ptr(p)) ;
+            break;
+        case disc_node:
+            pre_break(r) = pre_break_head(r);
+            if (vlink_pre_break(p) != null) {
+                s = copy_node_list(vlink_pre_break(p));
+                alink(s) = pre_break(r);
+                tlink_pre_break(r) = tail_of_list(s);
+                vlink_pre_break(r) = s;
+            } else {
+                assert(tlink(pre_break(r)) == null);
+            }
+            post_break(r) = post_break_head(r);
+            if (vlink_post_break(p) != null) {
+                s = copy_node_list(vlink_post_break(p));
+                alink(s) = post_break(r);
+                tlink_post_break(r) = tail_of_list(s);
+                vlink_post_break(r) = s;
+            } else {
+                assert(tlink_post_break(r) == null);
+            }
+            no_break(r) = no_break_head(r);
+            if (vlink(no_break(p)) != null) {
+                s = copy_node_list(vlink_no_break(p));
+                alink(s) = no_break(r);
+                tlink_no_break(r) = tail_of_list(s);
+                vlink_no_break(r) = s;
+            } else {
+                assert(tlink_no_break(r) == null);
+            }
+            break;
+        case math_node:
+            break;
+        case ins_node:
+            copy_sub_list(ins_ptr(r),ins_ptr(p)) ;
+            break;
+        case margin_kern_node:
+            copy_sub_node(margin_char(r),margin_char(p));
+            break;
+        case mark_node:
+            add_token_ref(mark_ptr(p));
+            break;
+        case adjust_node:
+            copy_sub_list(adjust_ptr(r),adjust_ptr(p));
+            break;
+        case choice_node:
+            copy_sub_list(display_mlist(r),display_mlist(p)) ;
+            copy_sub_list(text_mlist(r),text_mlist(p)) ;
+            copy_sub_list(script_mlist(r),script_mlist(p)) ;
+            copy_sub_list(script_script_mlist(r),script_script_mlist(p)) ;
+            break;
+        case simple_noad:
+            copy_sub_list(nucleus(r),nucleus(p)) ;
+            copy_sub_list(subscr(r),subscr(p)) ;
+            copy_sub_list(supscr(r),supscr(p)) ;
+            break;
+        case radical_noad:
+            copy_sub_list(nucleus(r),nucleus(p)) ;
+            copy_sub_list(subscr(r),subscr(p)) ;
+            copy_sub_list(supscr(r),supscr(p)) ;
+            copy_sub_node(left_delimiter(r),left_delimiter(p)) ;
+            copy_sub_list(degree(r),degree(p)) ;
+            break;
+        case accent_noad:
+            copy_sub_list(nucleus(r),nucleus(p)) ;
+            copy_sub_list(subscr(r),subscr(p)) ;
+            copy_sub_list(supscr(r),supscr(p)) ;
+            copy_sub_list(top_accent_chr(r),top_accent_chr(p)) ;
+            copy_sub_list(bot_accent_chr(r),bot_accent_chr(p)) ;
+            copy_sub_list(overlay_accent_chr(r),overlay_accent_chr(p)) ;
+            break;
+        case fence_noad:
+            copy_sub_node(delimiter(r),delimiter(p)) ;
+            break;
+        case sub_box_node:
+        case sub_mlist_node:
+            copy_sub_list(math_list(r),math_list(p)) ;
+            break;
+        case fraction_noad:
+            copy_sub_list(numerator(r),numerator(p)) ;
+            copy_sub_list(denominator(r),denominator(p)) ;
+            copy_sub_node(left_delimiter(r),left_delimiter(p)) ;
+            copy_sub_node(right_delimiter(r),right_delimiter(p)) ;
+            break;
+        case glue_spec_node:
+        case dir_node:
+        case local_par_node:
+        case boundary_node:
+            break;
+        case whatsit_node:
+            w = subtype(p) ;
+            if (w >= backend_first_pdf_whatsit) {
+                copy_node_wrapup_pdf(p,r);
+            } else if (w >= backend_first_dvi_whatsit) {
+                copy_node_wrapup_dvi(p,r);
+            } else {
+                copy_node_wrapup_core(p,r);
+            }
+            break;
+    }
+    return r;
+}
+
+/* x */
+
+#define free_sub_list(source) if (source != null) flush_node_list(source);
+#define free_sub_node(source) if (source != null) flush_node(source);
+
+@ @c
+
+static void flush_node_wrapup_core(halfword p)
+{
+    switch (subtype(p)) {
+        case open_node:
+        case write_node:
+        case close_node:
+        case save_pos_node:
+            break;
+        case special_node:
+            delete_token_ref(write_tokens(p));
+            break;
+        case late_lua_node:
+            free_late_lua(p);
+            break;
+        case user_defined_node:
+            switch (user_node_type(p)) {
+            case 'a':
+                delete_attribute_ref(user_node_value(p));
+                break;
+            case 'd':
+                break;
+            case 'l':
+                free_user_lua(user_node_value(p));
+                break;
+            case 'n':
+                flush_node_list(user_node_value(p));
+                break;
+            case 's':
+                /* |delete_string_ref(user_node_value(p));| *//* if this was mpost .. */
+                break;
+            case 't':
+                delete_token_ref(user_node_value(p));
+                break;
+            default:
+                {
+                    const char *hlp[] = {
+                        "The type of the value in a user defined whatsit node should be one",
+                        "of 'a' (attribute list), 'd' (number), 'n' (node list), 's' (string),",
+                        "or 't' (tokenlist). Yours has an unknown type, and therefore I don't",
+                        "know how to free the node's value. A memory leak may result.",
+                        NULL
+                    };
+                    tex_error("Unidentified user defined whatsit", hlp);
+                }
+                break;
+            }
+            break;
+    }
+}
+
+void flush_node_wrapup_dvi(halfword p)
+{
+}
+
+void flush_node_wrapup_pdf(halfword p)
+{
+    switch(subtype(p)) {
+        case pdf_save_node:
+        case pdf_restore_node:
+        case pdf_refobj_node:
+        case pdf_end_link_node:
+        case pdf_end_thread_node:
+            break;
+        case pdf_literal_node:
+            free_pdf_literal(p);
+            break;
+        case pdf_colorstack_node:
+            if (pdf_colorstack_cmd(p) <= colorstack_data)
+                delete_token_ref(pdf_colorstack_data(p));
+            break;
+        case pdf_setmatrix_node:
+            delete_token_ref(pdf_setmatrix_data(p));
+            break;
+        case pdf_annot_node:
+            delete_token_ref(pdf_annot_data(p));
+            break;
+        case pdf_link_data_node:
+            break;
+        case pdf_start_link_node:
+            if (pdf_link_attr(p) != null)
+                delete_token_ref(pdf_link_attr(p));
+            delete_action_ref(pdf_link_action(p));
+            break;
+        case pdf_dest_node:
+            if (pdf_dest_named_id(p) > 0)
+                delete_token_ref(pdf_dest_id(p));
+            break;
+        case pdf_action_node:
+            if (pdf_action_type(p) == pdf_action_user) {
+                delete_token_ref(pdf_action_tokens(p));
+            } else {
+                if (pdf_action_file(p) != null)
+                    delete_token_ref(pdf_action_file(p));
+                if (pdf_action_type(p) == pdf_action_page)
+                    delete_token_ref(pdf_action_tokens(p));
+                else if (pdf_action_named_id(p) > 0)
+                    delete_token_ref(pdf_action_id(p));
+            }
+            break;
+        case pdf_thread_data_node:
+            break;
+        case pdf_thread_node:
+        case pdf_start_thread_node:
+            if (pdf_thread_named_id(p) > 0)
+                delete_token_ref(pdf_thread_id(p));
+            if (pdf_thread_attr(p) != null)
+                delete_token_ref(pdf_thread_attr(p));
+            break;
+    }
+}
+
+void flush_node(halfword p)
+{
+    halfword w;
+    if (p == null)              /* legal, but no-op */
+        return;
+    if (free_error(p))
+        return;
+    switch (type(p)) {
+        case glyph_node:
+            free_sub_list(lig_ptr(p));
+            break;
+        case glue_node:
+            free_sub_list(leader_ptr(p));
+            break;
+        case hlist_node:
+        case vlist_node:
+        case unset_node:
+            free_sub_list(list_ptr(p));
+            break;
+        case disc_node:
+            /* watch the start at temp node hack */
+            free_sub_list(vlink(pre_break(p)));
+            free_sub_list(vlink(post_break(p)));
+            free_sub_list(vlink(no_break(p)));
+            break;
+        case rule_node:
+        case kern_node:
+        case penalty_node:
+        case math_node:
+            break;
+        case glue_spec_node:
+            /* this allows free-ing of lua-allocated glue specs */
+//if (valid_node(p)) {
+//    free_node(p, subtype(p));
+//}
+//            return ;
+            break ;
+        case dir_node:
+        case local_par_node:
+        case boundary_node:
+            break;
+        case whatsit_node:
+            w = subtype(p) ;
+            if (w >= backend_first_pdf_whatsit) {
+                flush_node_wrapup_pdf(p);
+            } else if (w >= backend_first_dvi_whatsit) {
+                flush_node_wrapup_dvi(p);
+            } else {
+                flush_node_wrapup_core(p);
+            }
+            break;
+        case ins_node:
+            flush_node_list(ins_ptr(p));
+            break;
+        case margin_kern_node:
+            flush_node(margin_char(p));
+            break;
+        case mark_node:
+            delete_token_ref(mark_ptr(p));
+            break;
+        case adjust_node:
+            flush_node_list(adjust_ptr(p));
+            break;
+        case style_node:           /* nothing to do */
+            break;
+        case choice_node:
+            free_sub_list(display_mlist(p));
+            free_sub_list(text_mlist(p));
+            free_sub_list(script_mlist(p));
+            free_sub_list(script_script_mlist(p));
+            break;
+        case simple_noad:
+            free_sub_list(nucleus(p));
+            free_sub_list(subscr(p));
+            free_sub_list(supscr(p));
+            break;
+        case radical_noad:
+            free_sub_list(nucleus(p));
+            free_sub_list(subscr(p));
+            free_sub_list(supscr(p));
+            free_sub_node(left_delimiter(p));
+            free_sub_list(degree(p));
+            break;
+        case accent_noad:
+            free_sub_list(nucleus(p));
+            free_sub_list(subscr(p));
+            free_sub_list(supscr(p));
+            free_sub_list(top_accent_chr(p));
+            free_sub_list(bot_accent_chr(p));
+            free_sub_list(overlay_accent_chr(p));
+            break;
+        case fence_noad:
+            free_sub_list(delimiter(p));
+            break;
+        case delim_node:           /* nothing to do */
+        case math_char_node:
+        case math_text_char_node:
+            break;
+        case sub_box_node:
+        case sub_mlist_node:
+            free_sub_list(math_list(p));
+            break;
+        case fraction_noad:
+            free_sub_list(numerator(p));
+            free_sub_list(denominator(p));
+            free_sub_node(left_delimiter(p));
+            free_sub_node(right_delimiter(p));
+            break;
+        case pseudo_file_node:
+            free_sub_list(pseudo_lines(p));
+            break;
+        case pseudo_line_node:
+        case shape_node:
+            free_node(p, subtype(p));
+            return;
+            break;
+        case align_stack_node:
+        case span_node:
+        case movement_node:
+        case if_node:
+        case nesting_node:
+        case unhyphenated_node:
+        case hyphenated_node:
+        case delta_node:
+        case passive_node:
+        case inserting_node:
+        case split_up_node:
+        case expr_node:
+        case attribute_node:
+        case attribute_list_node:
+        case temp_node:
+            break;
+        default:
+            formatted_error("nodes","flushing weird node type %d", type(p));
+            return;
+    }
+    if (nodetype_has_attributes(type(p))) {
+        delete_attribute_ref(node_attr(p));
+        lua_properties_reset(p);
+    }
+    free_node(p, get_node_size(type(p), subtype(p)));
+    return;
+}
+
+@ @c
+void flush_node_list(halfword pp)
+{                               /* erase list of nodes starting at |p| */
+    register halfword p = pp;
+    if (p == null)              /* legal, but no-op */
+        return;
+    if (free_error(p))
+        return;
+    lua_properties_push; /* saves stack and time */
+    while (p != null) {
+        register halfword q = vlink(p);
+        flush_node(p);
+        p = q;
+    }
+    lua_properties_pop; /* saves stack and time */
+}
+
+@ @c
+static void check_node_wrapup_core(halfword p)
+{
+    switch (subtype(p)) {
+        /* frontend code */
+        case special_node:
+            check_token_ref(write_tokens(p));
+            break;
+        case user_defined_node:
+            switch (user_node_type(p)) {
+                case 'a':
+                    check_attribute_ref(user_node_value(p));
+                    break;
+                case 't':
+                    check_token_ref(user_node_value(p));
+                    break;
+                case 'n':
+                    dorangetest(p, user_node_value(p), var_mem_max);
+                    break;
+                case 's':
+                case 'd':
+                    break;
+                default:
+                    confusion("unknown user node type");
+                    break;
+            }
+            break;
+        case open_node:
+        case write_node:
+        case close_node:
+        case save_pos_node:
+            break;
+    }
+}
+
+void check_node_wrapup_dvi(halfword p)
+{
+}
+
+void check_node_wrapup_pdf(halfword p)
+{
+    switch (subtype(p)) {
+        case pdf_literal_node:
+            if (pdf_literal_type(p) == normal)
+                check_token_ref(pdf_literal_data(p));
+            break;
+        case pdf_colorstack_node:
+            if (pdf_colorstack_cmd(p) <= colorstack_data)
+                check_token_ref(pdf_colorstack_data(p));
+            break;
+        case pdf_setmatrix_node:
+            check_token_ref(pdf_setmatrix_data(p));
+            break;
+        case late_lua_node:
+            if (late_lua_name(p) > 0)
+                check_token_ref(late_lua_name(p));
+            if (late_lua_type(p) == normal)
+                check_token_ref(late_lua_data(p));
+            break;
+        case pdf_annot_node:
+            check_token_ref(pdf_annot_data(p));
+            break;
+        case pdf_start_link_node:
+            if (pdf_link_attr(p) != null)
+                check_token_ref(pdf_link_attr(p));
+            check_action_ref(pdf_link_action(p));
+            break;
+        case pdf_dest_node:
+            if (pdf_dest_named_id(p) > 0)
+                check_token_ref(pdf_dest_id(p));
+            break;
+        case pdf_thread_node:
+        case pdf_start_thread_node:
+            if (pdf_thread_named_id(p) > 0)
+                check_token_ref(pdf_thread_id(p));
+            if (pdf_thread_attr(p) != null)
+                check_token_ref(pdf_thread_attr(p));
+            break;
+        case pdf_save_node:
+        case pdf_restore_node:
+        case pdf_refobj_node:
+        case pdf_end_link_node:
+        case pdf_end_thread_node:
+            break;
+        default:
+            confusion("wrapup pdf nodes");
+            break;
+    }
+}
+
+void check_node(halfword p)
+{
+    halfword w ;
+    switch (type(p)) {
+        case glyph_node:
+            dorangetest(p, lig_ptr(p), var_mem_max);
+            break;
+        case glue_node:
+            dorangetest(p, leader_ptr(p), var_mem_max);
+            break;
+        case hlist_node:
+        case vlist_node:
+        case unset_node:
+        case align_record_node:
+            dorangetest(p, list_ptr(p), var_mem_max);
+            break;
+        case ins_node:
+            dorangetest(p, ins_ptr(p), var_mem_max);
+            break;
+        case whatsit_node:
+            w = subtype(p) ;
+            if (w >= backend_first_pdf_whatsit) {
+                check_node_wrapup_pdf(p);
+            } else if (w >= backend_first_dvi_whatsit) {
+                check_node_wrapup_dvi(p);
+            } else {
+                check_node_wrapup_core(p);
+            }
+            break;
+        case margin_kern_node:
+            check_node(margin_char(p));
+            break;
+        case math_node:
+            break;
+        case disc_node:
+            dorangetest(p, vlink(pre_break(p)), var_mem_max);
+            dorangetest(p, vlink(post_break(p)), var_mem_max);
+            dorangetest(p, vlink(no_break(p)), var_mem_max);
+            break;
+        case adjust_node:
+            dorangetest(p, adjust_ptr(p), var_mem_max);
+            break;
+        case pseudo_file_node:
+            dorangetest(p, pseudo_lines(p), var_mem_max);
+            break;
+        case pseudo_line_node:
+        case shape_node:
+            break;
+        case choice_node:
+            dorangetest(p, display_mlist(p), var_mem_max);
+            dorangetest(p, text_mlist(p), var_mem_max);
+            dorangetest(p, script_mlist(p), var_mem_max);
+            dorangetest(p, script_script_mlist(p), var_mem_max);
+            break;
+        case fraction_noad:
+            dorangetest(p, numerator(p), var_mem_max);
+            dorangetest(p, denominator(p), var_mem_max);
+            dorangetest(p, left_delimiter(p), var_mem_max);
+            dorangetest(p, right_delimiter(p), var_mem_max);
+            break;
+        case simple_noad:
+            dorangetest(p, nucleus(p), var_mem_max);
+            dorangetest(p, subscr(p), var_mem_max);
+            dorangetest(p, supscr(p), var_mem_max);
+            break;
+        case radical_noad:
+            dorangetest(p, nucleus(p), var_mem_max);
+            dorangetest(p, subscr(p), var_mem_max);
+            dorangetest(p, supscr(p), var_mem_max);
+            dorangetest(p, degree(p), var_mem_max);
+            dorangetest(p, left_delimiter(p), var_mem_max);
+            break;
+        case accent_noad:
+            dorangetest(p, nucleus(p), var_mem_max);
+            dorangetest(p, subscr(p), var_mem_max);
+            dorangetest(p, supscr(p), var_mem_max);
+            dorangetest(p, top_accent_chr(p), var_mem_max);
+            dorangetest(p, bot_accent_chr(p), var_mem_max);
+            dorangetest(p, overlay_accent_chr(p), var_mem_max);
+            break;
+        case fence_noad:
+            dorangetest(p, delimiter(p), var_mem_max);
+            break;
+        /*
+        case rule_node:
+        case kern_node:
+        case penalty_node:
+        case mark_node:
+        case style_node:
+        case attribute_list_node:
+        case attribute_node:
+        case glue_spec_node:
+        case temp_node:
+        case align_stack_node:
+        case movement_node:
+        case if_node:
+        case nesting_node:
+        case span_node:
+        case unhyphenated_node:
+        case hyphenated_node:
+        case delta_node:
+        case passive_node:
+        case expr_node:
+        case dir_node:
+        case boundary_node:
+        case local_par_node:
+            break;
+        default:
+            fprintf(stdout, "check_node: type is %d\n", type(p));
+        */
+    }
+}
+
+@ @c
+void fix_node_list(halfword head)
+{
+    halfword p, q;
+    if (head == null)
+        return;
+    p = head;
+    q = vlink(p);
+    while (q != null) {
+        alink(q) = p;
+        p = q;
+        q = vlink(p);
+    }
+}
+
+@ @c
+halfword get_node(int s)
+{
+    register halfword r;
+
+    if (s < MAX_CHAIN_SIZE) {
+        r = free_chain[s];
+        if (r != null) {
+            free_chain[s] = vlink(r);
+#ifdef CHECK_NODE_USAGE
+            varmem_sizes[r] = (char) s;
+#endif
+            vlink(r) = null;
+            var_used += s; /* maintain usage statistics */
+            return r;
+        }
+        /* this is the end of the 'inner loop' */
+        return slow_get_node(s);
+    } else {
+        normal_error("nodes","there is a problem in getting a node, case 1");
+        return null;
+    }
+}
+
+@ @c
+void free_node(halfword p, int s)
+{
+    if (p <= my_prealloc) {
+        formatted_error("nodes", "node number %d of type %d should not be freed", (int) p, type(p));
+        return;
+    }
+#ifdef CHECK_NODE_USAGE
+    varmem_sizes[p] = 0;
+#endif
+    if (s < MAX_CHAIN_SIZE) {
+        vlink(p) = free_chain[s];
+        free_chain[s] = p;
+    } else {
+        /* todo ? it is perhaps possible to merge this node with an existing rover */
+        node_size(p) = s;
+        vlink(p) = rover;
+        while (vlink(rover) != vlink(p)) {
+            rover = vlink(rover);
+        }
+        vlink(rover) = p;
+    }
+    /* maintain statistics */
+    var_used -= s;
+}
+
+@ @c
+static void free_node_chain(halfword q, int s)
+{
+    register halfword p = q;
+    while (vlink(p) != null) {
+#ifdef CHECK_NODE_USAGE
+        varmem_sizes[p] = 0;
+#endif
+        var_used -= s;
+        p = vlink(p);
+    }
+    var_used -= s;
+#ifdef CHECK_NODE_USAGE
+    varmem_sizes[p] = 0;
+#endif
+    vlink(p) = free_chain[s];
+    free_chain[s] = q;
+}
+
+@ At the start of the node memory area we reserve some special nodes,
+for instance frequently used glue specifications. We could as well just
+use new_glue here but for the moment we stick to the traditional approach.
+
+ at c
+#define initialize_glue(n,wi,st,sh,sto,sho) \
+    vlink(n) = null; \
+    type(n) = glue_spec_node; \
+    width(n) = wi; \
+    stretch(n) = st; \
+    shrink(n) = sh; \
+    stretch_order(n) = sto; \
+    shrink_order(n) = sho;
+
+#define initialize_whatever(n,t) \
+    vinfo(n) = 0; \
+    type(n) = t; \
+    vlink(n) = null; \
+    alink(n) = null;
+
+#define initialize_point(n) \
+    type(n) = glyph_node; \
+    subtype(n) = 0; \
+    vlink(n) = null; \
+    vinfo(n + 1) = null; \
+    alink(n) = null; \
+    font(n) = 0; \
+    character(n) = '.'; \
+    vinfo(n + 3) = 0; \
+    vlink(n + 3) = 0; \
+    vinfo(n + 4) = 0; \
+    vlink(n + 4) = 0;
+
+void init_node_mem(int t)
+{
+    my_prealloc = var_mem_stat_max;
+
+    varmem = (memory_word *) realloc((void *) varmem, sizeof(memory_word) * (unsigned) t);
+    if (varmem == NULL) {
+        overflow("node memory size", (unsigned) var_mem_max);
+    }
+    memset((void *) (varmem), 0, (unsigned) t * sizeof(memory_word));
+#ifdef CHECK_NODE_USAGE
+    varmem_sizes = (char *) realloc(varmem_sizes, sizeof(char) * (unsigned) t);
+    if (varmem_sizes == NULL) {
+        overflow("node memory size", (unsigned) var_mem_max);
+    }
+    memset((void *) varmem_sizes, 0, sizeof(char) * (unsigned) t);
+#endif
+    var_mem_max = t;
+    rover = var_mem_stat_max + 1;
+    vlink(rover) = rover;
+    node_size(rover) = (t - rover);
+    var_used = 0;
+
+    /* initialize static glue specs */
+
+    initialize_glue(zero_glue,0,0,0,0,0);
+    initialize_glue(sfi_glue,0,0,0,sfi,0);
+    initialize_glue(fil_glue,0,unity,0,fil,0);
+    initialize_glue(fill_glue,0,unity,0,fill,0);
+    initialize_glue(ss_glue,0,unity,unity,fil,fil);
+    initialize_glue(fil_neg_glue,0,-unity,0,fil,0);
+
+    /* initialize node list heads */
+
+    initialize_whatever(page_ins_head,temp_node);
+    initialize_whatever(contrib_head,temp_node);
+    initialize_whatever(page_head,temp_node);
+    initialize_whatever(temp_head,temp_node);
+    initialize_whatever(hold_head,temp_node);
+    initialize_whatever(adjust_head,temp_node);
+    initialize_whatever(pre_adjust_head,temp_node);
+    initialize_whatever(align_head,temp_node);
+
+    initialize_whatever(active,unhyphenated_node);
+    initialize_whatever(end_span,span_node);
+
+    initialize_point(begin_point);
+    initialize_point(end_point);
+}
+
+@ @c
+void dump_node_mem(void)
+{
+    dump_int(var_mem_max);
+    dump_int(rover);
+    dump_things(varmem[0], var_mem_max);
+#ifdef CHECK_NODE_USAGE
+    dump_things(varmem_sizes[0], var_mem_max);
+#endif
+    dump_things(free_chain[0], MAX_CHAIN_SIZE);
+    dump_int(var_used);
+    dump_int(my_prealloc);
+}
+
+@ it makes sense to enlarge the varmem array immediately
+ at c
+
+void undump_node_mem(void)
+{
+    int x;
+    undump_int(x);
+    undump_int(rover);
+    var_mem_max = (x < 100000 ? 100000 : x);
+    varmem = xmallocarray(memory_word, (unsigned) var_mem_max);
+    undump_things(varmem[0], x);
+#ifdef CHECK_NODE_USAGE
+    varmem_sizes = xmallocarray(char, (unsigned) var_mem_max);
+    memset((void *) varmem_sizes, 0, (unsigned) var_mem_max * sizeof(char));
+    undump_things(varmem_sizes[0], x);
+#endif
+    undump_things(free_chain[0], MAX_CHAIN_SIZE);
+    undump_int(var_used);
+    undump_int(my_prealloc);
+    if (var_mem_max > x) {
+        /* todo ? it is perhaps possible to merge the new node with an existing rover */
+        vlink(x) = rover;
+        node_size(x) = (var_mem_max - x);
+        while (vlink(rover) != vlink(x)) {
+            rover = vlink(rover);
+        }
+        vlink(rover) = x;
+    }
+}
+
+@ @c
+halfword slow_get_node(int s)
+{
+    register int t;
+
+  RETRY:
+    t = node_size(rover);
+    if (vlink(rover) < var_mem_max && vlink(rover) != 0) {
+        if (t > s) {
+            /* allocating from the bottom helps decrease page faults */
+            register halfword r = rover;
+            rover += s;
+            vlink(rover) = vlink(r);
+            node_size(rover) = node_size(r) - s;
+            if (vlink(rover) != r) {        /* list is longer than one */
+                halfword q = r;
+                while (vlink(q) != r) {
+                    q = vlink(q);
+                }
+                vlink(q) += s;
+            } else {
+                vlink(rover) += s;
+            }
+            if (vlink(rover) < var_mem_max) {
+#ifdef CHECK_NODE_USAGE
+                varmem_sizes[r] = (char) (s > 127 ? 127 : s);
+#endif
+                vlink(r) = null;
+                var_used += s;          /* maintain usage statistics */
+                return r;               /* this is the only exit */
+            } else {
+                normal_error("nodes","there is a problem in getting a node, case 2");
+                return null;
+            }
+        } else {
+            /* attempt to keep the free list small */
+            int x;
+            if (vlink(rover) != rover) {
+                if (t < MAX_CHAIN_SIZE) {
+                    halfword l = vlink(rover);
+                    vlink(rover) = free_chain[t];
+                    free_chain[t] = rover;
+                    rover = l;
+                    while (vlink(l) != free_chain[t]) {
+                        l = vlink(l);
+                    }
+                    vlink(l) = rover;
+                    goto RETRY;
+                } else {
+                    halfword l = rover;
+                    while (vlink(rover) != l) {
+                        if (node_size(rover) > s) {
+                            goto RETRY;
+                        }
+                        rover = vlink(rover);
+                    }
+                }
+            }
+            /* if we are still here, it was apparently impossible to get a match */
+            x = (var_mem_max >> 2) + s;
+            varmem = (memory_word *) realloc((void *) varmem, sizeof(memory_word) * (unsigned) (var_mem_max + x));
+            if (varmem == NULL) {
+                overflow("node memory size", (unsigned) var_mem_max);
+            }
+            memset((void *) (varmem + var_mem_max), 0, (unsigned) x * sizeof(memory_word));
+#ifdef CHECK_NODE_USAGE
+            varmem_sizes = (char *) realloc(varmem_sizes, sizeof(char) * (unsigned) (var_mem_max + x));
+            if (varmem_sizes == NULL) {
+                overflow("node memory size", (unsigned) var_mem_max);
+            }
+            memset((void *) (varmem_sizes + var_mem_max), 0, (unsigned) (x) * sizeof(char));
+#endif
+            /* todo ? it is perhaps possible to merge the new memory with an existing rover */
+            vlink(var_mem_max) = rover;
+            node_size(var_mem_max) = x;
+            while (vlink(rover) != vlink(var_mem_max)) {
+                rover = vlink(rover);
+            }
+            vlink(rover) = var_mem_max;
+            rover = var_mem_max;
+            var_mem_max += x;
+            goto RETRY;
+        }
+    } else {
+        normal_error("nodes","there is a problem in getting a node, case 3");
+        return null;
+    }
+}
+
+@ @c
+char *sprint_node_mem_usage(void)
+{
+    char *s;
+#ifdef CHECK_NODE_USAGE
+    char *ss;
+    int i;
+    int b = 0;
+    char msg[256];
+    int node_counts[last_normal_node + last_whatsit_node + 2] = { 0 };
+    s = strdup("");
+    for (i = (var_mem_max - 1); i > my_prealloc; i--) {
+        if (varmem_sizes[i] > 0) {
+            if (type(i) > last_normal_node + last_whatsit_node) {
+                node_counts[last_normal_node + last_whatsit_node + 1]++;
+            } else if (type(i) == whatsit_node) {
+                node_counts[(subtype(i) + last_normal_node + 1)]++;
+            } else {
+                node_counts[type(i)]++;
+            }
+        }
+    }
+    for (i = 0; i < last_normal_node + last_whatsit_node + 2; i++) {
+        if (node_counts[i] > 0) {
+            int j =
+                (i > (last_normal_node + 1) ? (i - last_normal_node - 1) : 0);
+            snprintf(msg, 255, "%s%d %s", (b ? ", " : ""), (int) node_counts[i],
+                     get_node_name((i > last_normal_node ? whatsit_node : i), j));
+            ss = xmalloc((unsigned) (strlen(s) + strlen(msg) + 1));
+            strcpy(ss, s);
+            strcat(ss, msg);
+            free(s);
+            s = ss;
+            b = 1;
+        }
+    }
+#else
+    s = strdup("");
+#endif
+    return s;
+}
+
+@ @c
+halfword list_node_mem_usage(void)
+{
+    halfword q = null;
+#ifdef CHECK_NODE_USAGE
+    halfword p = null;
+    halfword i, j;
+    char *saved_varmem_sizes = xmallocarray(char, (unsigned) var_mem_max);
+    memcpy(saved_varmem_sizes, varmem_sizes, (size_t) var_mem_max);
+    for (i = my_prealloc + 1; i < (var_mem_max - 1); i++) {
+        if (saved_varmem_sizes[i] > 0) {
+            j = copy_node(i);
+            if (p == null) {
+                q = j;
+            } else {
+                vlink(p) = j;
+            }
+            p = j;
+        }
+    }
+    free(saved_varmem_sizes);
+#endif
+    return q;
+}
+
+@ @c
+void print_node_mem_stats(void)
+{
+    int i, b;
+    halfword j;
+    char msg[256];
+    char *s;
+    int free_chain_counts[MAX_CHAIN_SIZE] = { 0 };
+    snprintf(msg, 255, " %d words of node memory still in use:", (int) (var_used + my_prealloc));
+    tprint_nl(msg);
+    s = sprint_node_mem_usage();
+    tprint_nl("   ");
+    tprint(s);
+    free(s);
+    tprint(" nodes");
+    tprint_nl("   avail lists: ");
+    b = 0;
+    for (i = 1; i < MAX_CHAIN_SIZE; i++) {
+        for (j = free_chain[i]; j != null; j = vlink(j))
+            free_chain_counts[i]++;
+        if (free_chain_counts[i] > 0) {
+            snprintf(msg, 255, "%s%d:%d", (b ? "," : ""), i, (int) free_chain_counts[i]);
+            tprint(msg);
+            b = 1;
+        }
+    }
+    /* newline, if needed */
+    print_nlp();
+}
+
+/* this belongs in the web but i couldn't find the correct syntactic place */
+
+halfword new_span_node(halfword n, int s, scaled w)
+{
+    halfword p = new_node(span_node, 0);
+    span_link(p) = n;
+    span_span(p) = s;
+    width(p) = w;
+    return p;
+}
+
+@* Attribute stuff.
+
+ at c
+static halfword new_attribute_node(unsigned int i, int v)
+{
+    register halfword r = get_node(attribute_node_size);
+    type(r) = attribute_node;
+    attribute_id(r) = (halfword) i;
+    attribute_value(r) = v;
+    /* not used but nicer in print */
+    subtype(r) = 0;
+    return r;
+}
+
+@ @c
+halfword copy_attribute_list(halfword n)
+{
+    halfword q = get_node(attribute_node_size);
+    register halfword p = q;
+    type(p) = attribute_list_node;
+    attr_list_ref(p) = 0;
+    n = vlink(n);
+    while (n != null) {
+        register halfword r = get_node(attribute_node_size);
+        /* the link will be fixed automatically in the next loop */
+        (void) memcpy((void *) (varmem + r), (void *) (varmem + n),
+                      (sizeof(memory_word) * attribute_node_size));
+        vlink(p) = r;
+        p = r;
+        n = vlink(n);
+    }
+    return q;
+}
+
+@ @c
+void update_attribute_cache(void)
+{
+    halfword p;
+    register int i;
+    attr_list_cache = get_node(attribute_node_size);
+    type(attr_list_cache) = attribute_list_node;
+    attr_list_ref(attr_list_cache) = 0;
+    p = attr_list_cache;
+    for (i = 0; i <= max_used_attr; i++) {
+        register int v = attribute(i);
+        if (v > UNUSED_ATTRIBUTE) {
+            register halfword r = new_attribute_node((unsigned) i, v);
+            vlink(p) = r;
+            p = r;
+        }
+    }
+    if (vlink(attr_list_cache) == null) {
+        free_node(attr_list_cache, attribute_node_size);
+        attr_list_cache = null;
+    }
+    return;
+}
+
+@ @c
+void build_attribute_list(halfword b)
+{
+    if (max_used_attr >= 0) {
+        if (attr_list_cache == cache_disabled|| attr_list_cache == null) {
+            update_attribute_cache();
+            if (attr_list_cache == null)
+                return;
+        }
+        attr_list_ref(attr_list_cache)++;
+        node_attr(b) = attr_list_cache;
+    }
+}
+
+@ @c
+halfword current_attribute_list(void)
+{
+    if (max_used_attr >= 0) {
+      if (attr_list_cache == cache_disabled) {
+            update_attribute_cache();
+      }
+      return attr_list_cache ;
+    }
+    return null ;
+}
+
+
+@ @c
+void reassign_attribute(halfword n, halfword new)
+{
+    halfword old;
+    old = node_attr(n);
+    if (new == null) {
+         /* there is nothing to assign but we need to check for an old value */
+        if (old != null)
+            delete_attribute_ref(old); /* also nulls attr field of n */
+    } else if (old == null) {
+         /* nothing is assigned so we just do that now */
+        assign_attribute_ref(n,new);
+    } else if (old != new) {
+         /* something is assigned so we need to clean up and assign then */
+        delete_attribute_ref(old);
+        assign_attribute_ref(n,new);
+    }
+     /* else: same value so there is no need to assign and change the refcount */
+    node_attr(n) = new ;
+}
+
+@ @c
+void delete_attribute_ref(halfword b)
+{
+    if (b != null) {
+        if (type(b) == attribute_list_node){
+            attr_list_ref(b)--;
+            if (attr_list_ref(b) == 0) {
+                if (b == attr_list_cache)
+                    attr_list_cache = cache_disabled;
+                free_node_chain(b, attribute_node_size);
+            }
+            /* maintain sanity */
+            if (attr_list_ref(b) < 0) {
+                attr_list_ref(b) = 0;
+            }
+        } else {
+            normal_error("nodes","trying to delete an attribute reference of a non attribute node");
+        }
+    }
+}
+
+void reset_node_properties(halfword b)
+{
+    if (b != null) {
+        lua_properties_reset(b);
+    }
+}
+
+@ |p| is an attr list head, or zero
+ at c
+halfword do_set_attribute(halfword p, int i, int val)
+{
+    register halfword q;
+    register int j = 0;
+    if (p == null) {            /* add a new head \& node */
+        q = get_node(attribute_node_size);
+        type(q) = attribute_list_node;
+        attr_list_ref(q) = 1;
+        p = new_attribute_node((unsigned) i, val);
+        vlink(q) = p;
+        return q;
+    }
+    q = p;
+    if (vlink(p) != null) {
+        while (vlink(p) != null) {
+            int t = attribute_id(vlink(p));
+            if (t == i && attribute_value(vlink(p)) == val)
+                return q;           /* no need to do anything */
+            if (t >= i)
+                break;
+            j++;
+            p = vlink(p);
+        }
+
+        p = q;
+        while (j-- > 0)
+            p = vlink(p);
+        if (attribute_id(vlink(p)) == i) {
+            attribute_value(vlink(p)) = val;
+        } else {                    /* add a new node */
+            halfword r = new_attribute_node((unsigned) i, val);
+            vlink(r) = vlink(p);
+            vlink(p) = r;
+        }
+        return q;
+    } else {
+        normal_error("nodes","trying to set an attribute fails, case 1");
+        return null ;
+    }
+}
+
+@ @c
+void set_attribute(halfword n, int i, int val)
+{
+    register halfword p;
+    register int j = 0;
+    /* not all nodes can have an attribute list */
+    if (!nodetype_has_attributes(type(n)))
+        return;
+    /* if we have no list, we create one and quit */
+    p = node_attr(n);
+    if (p == null) {            /* add a new head \& node */
+        p = get_node(attribute_node_size);
+        type(p) = attribute_list_node;
+        attr_list_ref(p) = 1;
+        node_attr(n) = p;
+        p = new_attribute_node((unsigned) i, val);
+        vlink(node_attr(n)) = p;
+        return;
+    }
+    /* we check if we have this attribute already and quit if the value stays the same */
+    if (vlink(p) != null) {
+        while (vlink(p) != null) {
+            int t = attribute_id(vlink(p));
+            if (t == i && attribute_value(vlink(p)) == val)
+                return;
+            if (t >= i)
+                break;
+            j++;
+            p = vlink(p);
+        }
+        /* j has now the position (if found) .. we assume a sorted list ! */
+        p = node_attr(n);
+
+        if (attr_list_ref(p) == 0 ) {
+            /* the list is invalid i.e. freed already */
+            formatted_warning("nodes","node %d has an attribute list that is free already, case 1",(int) n);
+            /* the still dangling list gets ref count 1 */
+            attr_list_ref(p) = 1;
+        } else if (attr_list_ref(p) == 1) {
+            /* this can really happen HH-LS */
+            if (p == attr_list_cache) {
+                /* we can invalidate the cache setting */
+                /* attr_list_cache = cache_disabled    */
+                /* or save the list, as done below     */
+                p = copy_attribute_list(p);
+                node_attr(n) = p;
+                /* the copied list gets ref count 1 */
+                attr_list_ref(p) = 1;
+            }
+        } else {
+            /* the list is used multiple times so we make a copy */
+            p = copy_attribute_list(p);
+            /* we decrement the ref count or the original */
+            delete_attribute_ref(node_attr(n));
+            node_attr(n) = p;
+            /* the copied list gets ref count 1 */
+            attr_list_ref(p) = 1;
+        }
+
+
+        /* we go to position j in the list */
+        while (j-- > 0)
+            p = vlink(p);
+        /* if we have a hit we just set the value otherwise we add a new node */
+        if (attribute_id(vlink(p)) == i) {
+            attribute_value(vlink(p)) = val;
+        } else {                    /* add a new node */
+            halfword r = new_attribute_node((unsigned) i, val);
+            vlink(r) = vlink(p);
+            vlink(p) = r;
+        }
+    } else {
+        normal_error("nodes","trying to set an attribute fails, case 2");
+    }
+}
+
+@ @c
+int unset_attribute(halfword n, int i, int val)
+{
+    register halfword p;
+    register int t;
+    register int j = 0;
+
+    if (!nodetype_has_attributes(type(n)))
+        return null;
+    p = node_attr(n);
+    if (p == null)
+        return UNUSED_ATTRIBUTE;
+    if (attr_list_ref(p) == 0) {
+        formatted_warning("nodes","node %d has an attribute list that is free already, case 2", (int) n);
+        return UNUSED_ATTRIBUTE;
+    }
+    if (vlink(p) != null) {
+        while (vlink(p) != null) {
+            t = attribute_id(vlink(p));
+            if (t > i)
+                return UNUSED_ATTRIBUTE;
+            if (t == i) {
+                p = vlink(p);
+                break;
+            }
+            j++;
+            p = vlink(p);
+        }
+        if (attribute_id(p) != i)
+            return UNUSED_ATTRIBUTE;
+        /* if we are still here, the attribute exists */
+        p = node_attr(n);
+        if (attr_list_ref(p) > 1 || p == attr_list_cache) {
+            halfword q = copy_attribute_list(p);
+            if (attr_list_ref(p) > 1) {
+                delete_attribute_ref(node_attr(n));
+            }
+            attr_list_ref(q) = 1;
+            node_attr(n) = q;
+        }
+        p = vlink(node_attr(n));
+        while (j-- > 0)
+            p = vlink(p);
+        t = attribute_value(p);
+        if (val == UNUSED_ATTRIBUTE || t == val) {
+            attribute_value(p) = UNUSED_ATTRIBUTE;
+        }
+        return t;
+    } else {
+        normal_error("nodes","trying to unset an attribute fails");
+        return null;
+    }
+}
+
+@ @c
+int has_attribute(halfword n, int i, int val)
+{
+    register halfword p;
+    if (!nodetype_has_attributes(type(n)))
+        return UNUSED_ATTRIBUTE;
+    p = node_attr(n);
+    if (p == null || vlink(p) == null)
+        return UNUSED_ATTRIBUTE;
+    p = vlink(p);
+    while (p != null) {
+        if (attribute_id(p) == i) {
+            int ret = attribute_value(p);
+            if (val == UNUSED_ATTRIBUTE || val == ret)
+                return ret;
+            return UNUSED_ATTRIBUTE;
+        } else if (attribute_id(p) > i) {
+            return UNUSED_ATTRIBUTE;
+        }
+        p = vlink(p);
+    }
+    return UNUSED_ATTRIBUTE;
+}
+
+@ @c
+void print_short_node_contents(halfword p)
+{
+    switch (type(p)) {
+        case hlist_node:
+        case vlist_node:
+        case ins_node:
+        case whatsit_node:
+        case mark_node:
+        case adjust_node:
+        case unset_node:
+            print_char('[');
+            print_char(']');
+            break;
+        case rule_node:
+            print_char('|');
+            break;
+        case glue_node:
+            if (! glue_is_zero(p))
+                print_char(' ');
+            break;
+        case math_node:
+            print_char('$');
+            break;
+        case disc_node:
+            short_display(vlink(pre_break(p)));
+            short_display(vlink(post_break(p)));
+            break;
+    }
+}
+
+@ @c
+static void show_pdftex_whatsit_rule_spec(int p)
+{
+    tprint("(");
+    print_rule_dimen(height(p));
+    print_char('+');
+    print_rule_dimen(depth(p));
+    tprint(")x");
+    print_rule_dimen(width(p));
+}
+
+@ Each new type of node that appears in our data structure must be capable
+of being displayed, copied, destroyed, and so on. The routines that we
+need for write-oriented whatsits are somewhat like those for mark nodes;
+other extensions might, of course, involve more subtlety here.
+
+ at c
+static void print_write_whatsit(const char *s, pointer p)
+{
+    tprint_esc(s);
+    if (write_stream(p) < 16)
+        print_int(write_stream(p));
+    else if (write_stream(p) == 16)
+        print_char('*');
+    else
+        print_char('-');
+}
+
+@ @c
+static void show_node_wrapup_core(int p)
+{
+    switch (subtype(p)) {
+        case open_node:
+            print_write_whatsit("openout", p);
+            print_char('=');
+            print_file_name(open_name(p), open_area(p), open_ext(p));
+            break;
+        case write_node:
+            print_write_whatsit("write", p);
+            print_mark(write_tokens(p));
+            break;
+        case close_node:
+            print_write_whatsit("closeout", p);
+            break;
+        case special_node:
+            tprint_esc("special");
+            print_mark(write_tokens(p));
+            break;
+        case late_lua_node:
+            show_late_lua(p);
+            break;
+        case save_pos_node:
+            tprint_esc("savepos");
+            break;
+        case user_defined_node:
+            tprint_esc("whatsit");
+            print_int(user_node_id(p));
+            print_char('=');
+            switch (user_node_type(p)) {
+            case 'a':
+                tprint("<>");
+                break;
+            case 'n':
+                tprint("[");
+                show_node_list(user_node_value(p));
+                tprint("]");
+                break;
+            case 's':
+                print_char('"');
+                print(user_node_value(p));
+                print_char('"');
+                break;
+            case 't':
+                print_mark(user_node_value(p));
+                break;
+            default:               /* only 'd' */
+                print_int(user_node_value(p));
+                break;
+            }
+            break;
+    }
+}
+
+void show_node_wrapup_dvi(int p)
+{
+}
+
+void show_node_wrapup_pdf(int p)
+{
+    switch (subtype(p)) {
+        case pdf_literal_node:
+            show_pdf_literal(p);
+            break;
+        case pdf_colorstack_node:
+            tprint_esc("pdfcolorstack ");
+            print_int(pdf_colorstack_stack(p));
+            switch (pdf_colorstack_cmd(p)) {
+            case colorstack_set:
+                tprint(" set ");
+                break;
+            case colorstack_push:
+                tprint(" push ");
+                break;
+            case colorstack_pop:
+                tprint(" pop");
+                break;
+            case colorstack_current:
+                tprint(" current");
+                break;
+            default:
+                confusion("colorstack");
+                break;
+            }
+            if (pdf_colorstack_cmd(p) <= colorstack_data)
+                print_mark(pdf_colorstack_data(p));
+            break;
+        case pdf_setmatrix_node:
+            tprint_esc("pdfsetmatrix");
+            print_mark(pdf_setmatrix_data(p));
+            break;
+        case pdf_save_node:
+            tprint_esc("pdfsave");
+            break;
+        case pdf_restore_node:
+            tprint_esc("pdfrestore");
+            break;
+        case pdf_refobj_node:
+            tprint_esc("pdfrefobj");
+            if (obj_obj_is_stream(static_pdf, pdf_obj_objnum(p))) {
+                if (obj_obj_stream_attr(static_pdf, pdf_obj_objnum(p)) != LUA_NOREF) {
+                    tprint(" attr");
+                    lua_rawgeti(Luas, LUA_REGISTRYINDEX,
+                                obj_obj_stream_attr(static_pdf, pdf_obj_objnum(p)));
+                    print_char(' ');
+                    tprint((const char *) lua_tostring(Luas, -1));
+                    lua_pop(Luas, 1);
+                }
+                tprint(" stream");
+            }
+            if (obj_obj_is_file(static_pdf, pdf_obj_objnum(p)))
+                tprint(" file");
+            if (obj_obj_data(static_pdf, pdf_obj_objnum(p)) != LUA_NOREF) {
+                lua_rawgeti(Luas, LUA_REGISTRYINDEX,
+                            obj_obj_data(static_pdf, pdf_obj_objnum(p)));
+                print_char(' ');
+                tprint((const char *) lua_tostring(Luas, -1));
+                lua_pop(Luas, 1);
+            }
+            break;
+        case pdf_annot_node:
+            tprint_esc("pdfannot");
+            show_pdftex_whatsit_rule_spec(p);
+            print_mark(pdf_annot_data(p));
+            break;
+        case pdf_start_link_node:
+            tprint_esc("pdfstartlink");
+            show_pdftex_whatsit_rule_spec(p);
+            if (pdf_link_attr(p) != null) {
+                tprint(" attr");
+                print_mark(pdf_link_attr(p));
+            }
+            tprint(" action");
+            if (pdf_action_type(pdf_link_action(p)) == pdf_action_user) {
+                tprint(" user");
+                print_mark(pdf_action_tokens(pdf_link_action(p)));
+                return;
+            }
+            if (pdf_action_file(pdf_link_action(p)) != null) {
+                tprint(" file");
+                print_mark(pdf_action_file(pdf_link_action(p)));
+            }
+            switch (pdf_action_type(pdf_link_action(p))) {
+            case pdf_action_goto:
+                if (pdf_action_named_id(pdf_link_action(p)) > 0) {
+                    tprint(" goto name");
+                    print_mark(pdf_action_id(pdf_link_action(p)));
+                } else {
+                    tprint(" goto num");
+                    print_int(pdf_action_id(pdf_link_action(p)));
+                }
+                break;
+            case pdf_action_page:
+                tprint(" page");
+                print_int(pdf_action_id(pdf_link_action(p)));
+                print_mark(pdf_action_tokens(pdf_link_action(p)));
+                break;
+            case pdf_action_thread:
+                if (pdf_action_named_id(pdf_link_action(p)) > 0) {
+                    tprint(" thread name");
+                    print_mark(pdf_action_id(pdf_link_action(p)));
+                } else {
+                    tprint(" thread num");
+                    print_int(pdf_action_id(pdf_link_action(p)));
+                }
+                break;
+            default:
+                normal_error("pdf backend", "unknown action type for link");
+                break;
+            }
+            break;
+        case pdf_end_link_node:
+            tprint_esc("pdfendlink");
+            break;
+        case pdf_dest_node:
+            tprint_esc("pdfdest");
+            if (pdf_dest_named_id(p) > 0) {
+                tprint(" name");
+                print_mark(pdf_dest_id(p));
+            } else {
+                tprint(" num");
+                print_int(pdf_dest_id(p));
+            }
+            print_char(' ');
+            switch (pdf_dest_type(p)) {
+            case pdf_dest_xyz:
+                tprint("xyz");
+                if (pdf_dest_xyz_zoom(p) != null) {
+                    tprint(" zoom");
+                    print_int(pdf_dest_xyz_zoom(p));
+                }
+                break;
+            case pdf_dest_fitbh:
+                tprint("fitbh");
+                break;
+            case pdf_dest_fitbv:
+                tprint("fitbv");
+                break;
+            case pdf_dest_fitb:
+                tprint("fitb");
+                break;
+            case pdf_dest_fith:
+                tprint("fith");
+                break;
+            case pdf_dest_fitv:
+                tprint("fitv");
+                break;
+            case pdf_dest_fitr:
+                tprint("fitr");
+                show_pdftex_whatsit_rule_spec(p);
+                break;
+            case pdf_dest_fit:
+                tprint("fit");
+                break;
+            default:
+                tprint("unknown!");
+                break;
+            }
+            break;
+        case pdf_thread_node:
+        case pdf_start_thread_node:
+            if (subtype(p) == pdf_thread_node)
+                tprint_esc("pdfthread");
+            else
+                tprint_esc("pdfstartthread");
+            tprint("(");
+            print_rule_dimen(height(p));
+            print_char('+');
+            print_rule_dimen(depth(p));
+            tprint(")x");
+            print_rule_dimen(width(p));
+            if (pdf_thread_attr(p) != null) {
+                tprint(" attr");
+                print_mark(pdf_thread_attr(p));
+            }
+            if (pdf_thread_named_id(p) > 0) {
+                tprint(" name");
+                print_mark(pdf_thread_id(p));
+            } else {
+                tprint(" num");
+                print_int(pdf_thread_id(p));
+            }
+            break;
+        case pdf_end_thread_node:
+            tprint_esc("pdfendthread");
+            break;
+        default:
+            break;
+    }
+}
+
+@  Now we are ready for |show_node_list| itself. This procedure has been
+  written to be ``extra robust'' in the sense that it should not crash or get
+  into a loop even if the data structures have been messed up by bugs in
+  the rest of the program. You can safely call its parent routine
+  |show_box(p)| for arbitrary values of |p| when you are debugging \TeX.
+  However, in the presence of bad data, the procedure may
+  fetch a |memory_word| whose variant is different from the way it was stored;
+  for example, it might try to read |mem[p].hh| when |mem[p]|
+  contains a scaled integer, if |p| is a pointer that has been
+  clobbered or chosen at random.
+
+
+@ |str_room| need not be checked; see |show_box|
+
+@ Recursive calls on |show_node_list| therefore use the following pattern:
+ at c
+#define node_list_display(A) do { \
+    append_char('.');  \
+    show_node_list(A); \
+    flush_char();      \
+} while (0)
+
+#define node_list_display_x(A,B) do { \
+    if ((B) != null) {     \
+        append_char('.');  \
+        append_char(A);    \
+        append_char(' ');  \
+        show_node_list(B); \
+        flush_char();      \
+        flush_char();      \
+        flush_char();      \
+    } \
+} while (0)
+
+/* prints a node list symbolically */
+
+void show_node_list(int p)
+{
+    int n = 0;                  /* the number of items already printed at this level */
+    halfword w;
+    real g;                     /* a glue ratio, as a floating point number */
+    if ((int) cur_length > depth_threshold) {
+        if (p > null)
+            tprint(" []");      /* indicate that there's been some truncation */
+        return;
+    }
+    while (p != null) {
+        print_ln();
+        print_current_string(); /* display the nesting history */
+        if (tracing_online_par < -2)
+            print_int(p);
+        incr(n);
+        if (n > breadth_max) {  /* time to stop */
+            tprint("etc.");
+            return;
+        }
+        /* Display node |p| */
+        if (is_char_node(p)) {
+            print_font_and_char(p);
+            if (is_ligature(p)) {
+                /* Display ligature |p|; */
+                tprint(" (ligature ");
+                if (is_leftboundary(p))
+                    print_char('|');
+                font_in_short_display = font(p);
+                short_display(lig_ptr(p));
+                if (is_rightboundary(p))
+                    print_char('|');
+                print_char(')');
+            }
+        } else {
+            switch (type(p)) {
+            case hlist_node:
+            case vlist_node:
+            case unset_node:
+                /* Display box |p|; */
+                if (type(p) == hlist_node)
+                    tprint_esc("h");
+                else if (type(p) == vlist_node)
+                    tprint_esc("v");
+                else
+                    tprint_esc("unset");
+                tprint("box(");
+                print_scaled(height(p));
+                print_char('+');
+                print_scaled(depth(p));
+                tprint(")x");
+                print_scaled(width(p));
+                if (type(p) == unset_node) {
+                    /* Display special fields of the unset node |p|; */
+                    if (span_count(p) != min_quarterword) {
+                        tprint(" (");
+                        print_int(span_count(p) + 1);
+                        tprint(" columns)");
+                    }
+                    if (glue_stretch(p) != 0) {
+                        tprint(", stretch ");
+                        print_glue(glue_stretch(p), glue_order(p), NULL);
+                    }
+                    if (glue_shrink(p) != 0) {
+                        tprint(", shrink ");
+                        print_glue(glue_shrink(p), glue_sign(p), NULL);
+                    }
+                } else {
+                    /* Display the value of |glue_set(p)| */
+                    /* The code will have to change in this place if |glue_ratio| is
+                       a structured type instead of an ordinary |real|. Note that this routine
+                       should avoid arithmetic errors even if the |glue_set| field holds an
+                       arbitrary random value. The following code assumes that a properly
+                       formed nonzero |real| number has absolute value $2^{20}$ or more when
+                       it is regarded as an integer; this precaution was adequate to prevent
+                       floating point underflow on the author's computer.
+                     */
+
+                    g = (real) (glue_set(p));
+                    if ((g != 0.0) && (glue_sign(p) != normal)) {
+                        tprint(", glue set ");
+                        if (glue_sign(p) == shrinking)
+                            tprint("- ");
+                        if (g > 20000.0 || g < -20000.0) {
+                            if (g > 0.0)
+                                print_char('>');
+                            else
+                                tprint("< -");
+                            print_glue(20000 * unity, glue_order(p), NULL);
+                        } else {
+                            print_glue(round(unity * g), glue_order(p), NULL);
+                        }
+                    }
+
+                    if (shift_amount(p) != 0) {
+                        tprint(", shifted ");
+                        print_scaled(shift_amount(p));
+                    }
+                    tprint(", direction ");
+                    print_dir(box_dir(p));
+                }
+                node_list_display(list_ptr(p)); /* recursive call */
+                break;
+            case rule_node:
+                /* Display rule |p|; */
+                if (subtype(p) == normal_rule) {
+                    tprint_esc("rule(");
+                } else if (subtype(p) == empty_rule) {
+                    tprint_esc("norule(");
+                } else if (subtype(p) == user_rule) {
+                    tprint_esc("userrule(");
+                } else if (subtype(p) == box_rule) {
+                    tprint_esc("box(");
+                } else if (subtype(p) == image_rule) {
+                    tprint_esc("image(");
+                }
+                print_rule_dimen(height(p));
+                print_char('+');
+                print_rule_dimen(depth(p));
+                tprint(")x");
+                print_rule_dimen(width(p));
+                break;
+            case ins_node:
+                /* Display insertion |p|; */
+                tprint_esc("insert");
+                print_int(subtype(p));
+                tprint(", natural size ");
+                print_scaled(height(p));
+                tprint("; split(");
+                print_spec(split_top_ptr(p), NULL);
+                print_char(',');
+                print_scaled(depth(p));
+                tprint("); float cost ");
+                print_int(float_cost(p));
+                node_list_display(ins_ptr(p));  /* recursive call */
+                break;
+            case dir_node:
+                if (dir_dir(p) < 0) {
+                    tprint_esc("enddir");
+                    print_char(' ');
+                    print_dir(dir_dir(p) + dir_swap);
+                } else {
+                    tprint_esc("begindir");
+                    print_char(' ');
+                    print_dir(dir_dir(p));
+                }
+                break;
+            case local_par_node:
+                tprint_esc("localpar");
+                append_char('.');
+                print_ln();
+                print_current_string();
+                tprint_esc("localinterlinepenalty");
+                print_char('=');
+                print_int(local_pen_inter(p));
+                print_ln();
+                print_current_string();
+                tprint_esc("localbrokenpenalty");
+                print_char('=');
+                print_int(local_pen_broken(p));
+                print_ln();
+                print_current_string();
+                tprint_esc("localleftbox");
+                if (local_box_left(p) == null) {
+                    tprint("=null");
+                } else {
+                    append_char('.');
+                    show_node_list(local_box_left(p));
+                    decr(cur_length);
+                }
+                print_ln();
+                print_current_string();
+                tprint_esc("localrightbox");
+                if (local_box_right(p) == null) {
+                    tprint("=null");
+                } else {
+                    append_char('.');
+                    show_node_list(local_box_right(p));
+                    decr(cur_length);
+                }
+                decr(cur_length);
+                break;
+            case boundary_node:
+                if (subtype(p)==0) {
+                    tprint_esc("noboundary");
+                } else {
+                    switch (subtype(p)) {
+                        case 1:
+                            tprint_esc("boundary");
+                            break;
+                        case 2:
+                            tprint_esc("protrusionboundary");
+                            break;
+                        case 3:
+                            tprint_esc("wordboundary");
+                            break;
+                        default:
+                            tprint_esc("boundary");
+                            print_char(':');
+                            print_int(subtype(p));
+                            break;
+                    }
+                    print_char('=');
+                    print_int(boundary_value(p));
+                }
+                break;
+            case whatsit_node:
+                w = subtype(p) ;
+                if (w >= backend_first_pdf_whatsit) {
+                    show_node_wrapup_pdf(p);
+                } else if (w >= backend_first_dvi_whatsit) {
+                    show_node_wrapup_dvi(p);
+                } else {
+                    show_node_wrapup_core(p);
+                }
+                break;
+            case glue_node:
+                /* Display glue |p|; */
+                if (subtype(p) >= a_leaders) {
+                    /* Display leaders |p|; */
+                    tprint_esc("");
+                    switch (subtype(p)) {
+                    case a_leaders:
+                        break;
+                    case c_leaders:
+                        print_char('c');
+                        break;
+                    case x_leaders:
+                        print_char('x');
+                        break;
+                    case g_leaders:
+                        print_char('g');
+                        break;
+                    default:
+                        normal_warning("nodes","weird glue leader subtype ignored");
+                    }
+                    tprint("leaders ");
+                    print_spec(p, NULL);
+                    node_list_display(leader_ptr(p));   /* recursive call */
+                } else {
+                    tprint_esc("glue");
+                    if (subtype(p) != normal) {
+                        print_char('(');
+                        if ((subtype(p) - 1) < thin_mu_skip_code) {
+                            print_cmd_chr(assign_glue_cmd, glue_base + (subtype(p) - 1));
+                        } else if (subtype(p) < cond_math_glue) {
+                            print_cmd_chr(assign_mu_glue_cmd, glue_base + (subtype(p) - 1));
+                        } else if (subtype(p) == cond_math_glue) {
+                            tprint_esc("nonscript");
+                        } else {
+                            tprint_esc("mskip");
+                        }
+                        print_char(')');
+                    }
+                    if (subtype(p) != cond_math_glue) {
+                        print_char(' ');
+                        if (subtype(p) < cond_math_glue)
+                            print_spec(p, NULL);
+                        else
+                            print_spec(p, "mu");
+                    }
+                }
+                break;
+            case margin_kern_node:
+                tprint_esc("kern");
+                print_scaled(width(p));
+                if (subtype(p) == left_side)
+                    tprint(" (left margin)");
+                else
+                    tprint(" (right margin)");
+                break;
+            case kern_node:
+                /* Display kern |p|; */
+                /*  An ``explicit'' kern value is indicated implicitly by an explicit space. */
+                if (subtype(p) != mu_glue) {
+                    tprint_esc("kern");
+                    if (subtype(p) != normal)
+                        print_char(' ');
+                    print_scaled(width(p));
+                    if (subtype(p) == accent_kern)
+                        tprint(" (for accent)");
+                } else {
+                    tprint_esc("mkern");
+                    print_scaled(width(p));
+                    tprint("mu");
+                }
+                break;
+            case math_node:
+                /* Display math node |p|; */
+                tprint_esc("math");
+                if (subtype(p) == before)
+                    tprint("on");
+                else
+                    tprint("off");
+                if (!glue_is_zero(p)) {
+                    tprint(", glued ");
+                    print_spec(p, NULL);
+                } else if (surround(p) != 0) {
+                    tprint(", surrounded ");
+                    print_scaled(surround(p));
+                }
+                break;
+            case penalty_node:
+                /* Display penalty |p|; */
+                tprint_esc("penalty ");
+                print_int(penalty(p));
+                break;
+            case disc_node:
+                /* Display discretionary |p|; */
+                /* The |post_break| list of a discretionary node is indicated by a prefixed
+                   `\.{\char'174}' instead of the `\..' before the |pre_break| list. */
+                /* We're not compatible anyway  so ...
+                    tprint_esc("discretionary");
+                    print_int(disc_penalty(p));
+                    print_char('|');
+                    if (vlink(no_break(p)) != null) {
+                        tprint(" replacing ");
+                        node_list_display(vlink(no_break(p)));
+                    }
+                    node_list_display(vlink(pre_break(p)));
+                    append_char('|');
+                    show_node_list(vlink(post_break(p)));
+                    flush_char();
+                */
+                tprint_esc("discretionary");
+                tprint(" (penalty ");
+                print_int(disc_penalty(p));
+                print_char(')');
+                node_list_display_x('<',vlink(pre_break(p)));
+                node_list_display_x('>',vlink(post_break(p)));
+                node_list_display_x('=',vlink(no_break(p)));
+                break;
+            case mark_node:
+                /* Display mark |p|; */
+                tprint_esc("mark");
+                if (mark_class(p) != 0) {
+                    print_char('s');
+                    print_int(mark_class(p));
+                }
+                print_mark(mark_ptr(p));
+                break;
+            case adjust_node:
+                /* Display adjustment |p|; */
+                tprint_esc("vadjust");
+                if (subtype(p) != 0)
+                    tprint(" pre ");
+                node_list_display(adjust_ptr(p));       /* recursive call */
+                break;
+            case glue_spec_node:
+                tprint("<glue_spec ");
+                print_spec(p, NULL);
+                tprint(">");
+                break;
+            default:
+                show_math_node(p);
+                break;
+            }
+        }
+        p = vlink(p);
+    }
+}
+
+@ This routine finds the 'base' width of a horizontal box, using the same logic
+  that \TeX82 used for \.{\\predisplaywidth} */
+
+ at c
+static pointer get_actual_box_width(pointer r,pointer p, scaled initial_width)
+{
+    scaled d;                  /* increment to |v| */
+    scaled w = -max_dimen;     /* calculated |size| */
+    scaled v = initial_width;  /* |w| plus possible glue amount */
+    while (p != null) {
+        if (is_char_node(p)) {
+            d = glyph_width(p);
+            goto FOUND;
+        }
+        switch (type(p)) {
+            case hlist_node:
+            case vlist_node:
+            case rule_node:
+                d = width(p);
+                goto FOUND;
+                break;
+            case margin_kern_node:
+                d = width(p);
+                break;
+            case kern_node:
+                d = width(p);
+                break;
+            case disc_node:
+                /* at the end of the line we should actually take the pre */
+                if (no_break(p) != null) {
+                    d = get_actual_box_width(r,vlink_no_break(p),0);
+                    if (d <= -max_dimen || d >= max_dimen) {
+                        d = 0;
+                    }
+                } else {
+                    d = 0;
+                }
+                goto FOUND;
+                break;
+            case math_node:
+                /* begin mathskip code */
+                if (glue_is_zero(p)) {
+                    d = surround(p);
+                    break;
+                } else {
+                    /* fall through */
+                }
+                /* end mathskip code */
+            case glue_node:
+                /* We need to be careful that |w|, |v|, and |d| do not depend on any |glue_set|
+                   values, since such values are subject to system-dependent rounding.
+                   System-dependent numbers are not allowed to infiltrate parameters like
+                   |pre_display_size|, since \TeX82 is supposed to make the same decisions on all
+                   machines.
+                 */
+                d = width(p);
+                if (glue_sign(r) == stretching) {
+                    if ((glue_order(r) == stretch_order(p)) && (stretch(p) != 0))
+                        v = max_dimen;
+                } else if (glue_sign(r) == shrinking) {
+                    if ((glue_order(r) == shrink_order(p)) && (shrink(p) != 0))
+                        v = max_dimen;
+                }
+                if (subtype(p) >= a_leaders)
+                    goto FOUND;
+                break;
+            default:
+                d = 0;
+                break;
+        }
+        if (v < max_dimen)
+            v = v + d;
+        goto NOT_FOUND;
+      FOUND:
+        if (v < max_dimen) {
+            v = v + d;
+            w = v;
+        } else {
+            w = max_dimen;
+            break;
+        }
+      NOT_FOUND:
+        p = vlink(p);
+    }
+    return w;
+}
+
+pointer actual_box_width(pointer r, scaled base_width)
+{
+    /* often this is the same as:
+        return + shift_amount(r) + base_width +
+            natural_sizes(list_ptr(r),null,(glue_ratio) glue_set(r),glue_sign(r),glue_order(r),box_dir(r));
+    */
+    return get_actual_box_width(r,list_ptr(r),shift_amount(r) + base_width);
+}
+
+@ @c
+halfword tail_of_list(halfword p)
+{
+    halfword q = p;
+    while (vlink(q) != null)
+        q = vlink(q);
+    return q;
+}
+
+
+
+@ @c
+int var_used;
+
+@ Attribute lists need two extra globals to increase processing efficiency.
+|max_used_attr| limits the test loop that checks for set attributes, and
+|attr_list_cache| contains a pointer to an already created attribute list.  It is
+set to the special value |cache_disabled| when the current value can no longer be
+trusted: after an assignment to an attribute register, and after a group has
+ended.
+
+ at c
+int max_used_attr;        /* maximum assigned attribute id  */
+halfword attr_list_cache;
+
+@ From the computer's standpoint, \TeX's chief mission is to create
+horizontal and vertical lists. We shall now investigate how the elements
+of these lists are represented internally as nodes in the dynamic memory.
+
+A horizontal or vertical list is linked together by |link| fields in
+the first word of each node. Individual nodes represent boxes, glue,
+penalties, or special things like discretionary hyphens; because of this
+variety, some nodes are longer than others, and we must distinguish different
+kinds of nodes. We do this by putting a `|type|' field in the first word,
+together with the link and an optional `|subtype|'.
+
+@ Character nodes appear only in horizontal lists, never in vertical lists.
+
+An |hlist_node| stands for a box that was made from a horizontal list.
+Each |hlist_node| is seven words long, and contains the following fields
+(in addition to the mandatory |type| and |link|, which we shall not
+mention explicitly when discussing the other node types): The |height| and
+|width| and |depth| are scaled integers denoting the dimensions of the
+box.  There is also a |shift_amount| field, a scaled integer indicating
+how much this box should be lowered (if it appears in a horizontal list),
+or how much it should be moved to the right (if it appears in a vertical
+list). There is a |list_ptr| field, which points to the beginning of the
+list from which this box was fabricated; if |list_ptr| is |null|, the box
+is empty. Finally, there are three fields that represent the setting of
+the glue:  |glue_set(p)| is a word of type |glue_ratio| that represents
+the proportionality constant for glue setting; |glue_sign(p)| is
+|stretching| or |shrinking| or |normal| depending on whether or not the
+glue should stretch or shrink or remain rigid; and |glue_order(p)|
+specifies the order of infinity to which glue setting applies (|normal|,
+|sfi|, |fil|, |fill|, or |filll|). The |subtype| field is not used.
+
+@ The |new_null_box| function returns a pointer to an |hlist_node| in
+which all subfields have the values corresponding to `\.{\\hbox\{\}}'.
+The |subtype| field is set to |min_quarterword|, since that's the desired
+|span_count| value if this |hlist_node| is changed to an |unset_node|.
+
+ at c
+halfword new_null_box(void)
+{                               /* creates a new box node */
+    halfword p = new_node(hlist_node, min_quarterword);
+    box_dir(p) = text_direction_par;
+    return p;
+}
+
+@ A |vlist_node| is like an |hlist_node| in all respects except that it
+contains a vertical list.
+
+@ A |rule_node| stands for a solid black rectangle; it has |width|,
+|depth|, and |height| fields just as in an |hlist_node|. However, if
+any of these dimensions is $-2^{30}$, the actual value will be determined
+by running the rule up to the boundary of the innermost enclosing box.
+This is called a ``running dimension.'' The |width| is never running in
+an hlist; the |height| and |depth| are never running in a~vlist.
+
+@ A new rule node is delivered by the |new_rule| function. It
+makes all the dimensions ``running,'' so you have to change the
+ones that are not allowed to run.
+
+ at c
+halfword new_rule(int s)
+{
+    halfword p = new_node(rule_node,s);
+    return p;
+}
+
+@ Insertions are represented by |ins_node| records, where the |subtype|
+indicates the corresponding box number. For example, `\.{\\insert 250}'
+leads to an |ins_node| whose |subtype| is |250+min_quarterword|.
+The |height| field of an |ins_node| is slightly misnamed; it actually holds
+the natural height plus depth of the vertical list being inserted.
+The |depth| field holds the |split_max_depth| to be used in case this
+insertion is split, and the |split_top_ptr| points to the corresponding
+|split_top_skip|. The |float_cost| field holds the |floating_penalty| that
+will be used if this insertion floats to a subsequent page after a
+split insertion of the same class.  There is one more field, the
+|ins_ptr|, which points to the beginning of the vlist for the insertion.
+
+@ A |mark_node| has a |mark_ptr| field that points to the reference count
+of a token list that contains the user's \.{\\mark} text.
+In addition there is a |mark_class| field that contains the mark class.
+
+@ An |adjust_node|, which occurs only in horizontal lists,
+specifies material that will be moved out into the surrounding
+vertical list; i.e., it is used to implement \TeX's `\.{\\vadjust}'
+operation.  The |adjust_ptr| field points to the vlist containing this
+material.
+
+@ A |glyph_node|, which occurs only in horizontal lists, specifies a
+glyph in a particular font, along with its attribute list. Older
+versions of \TeX\ could use token memory for characters, because the
+font,char combination would fit in a single word (both values were
+required to be strictly less than $2^{16}$). In LuaTeX, room is
+needed for characters that are larger than that, as well as a pointer
+to a potential attribute list, and the two displacement values.
+
+In turn, that made the node so large that it made sense to merge
+ligature glyphs as well, as that requires only one extra pointer.  A
+few extra classes of glyph nodes will be introduced later.  The
+unification of all those types makes it easier to manipulate lists of
+glyphs. The subtype differentiates various glyph kinds.
+
+First, here is a function that returns a pointer to a glyph node for a given
+glyph in a given font. If that glyph doesn't exist, |null| is returned
+instead.  Nodes of this subtype are directly created only for accents
+and their base (through |make_accent|), and math nucleus items (in the
+conversion from |mlist| to |hlist|).
+
+ at c
+halfword new_glyph(int f, int c)
+{
+    halfword p = null;          /* the new node */
+    if ((f == 0) || (char_exists(f, c))) {
+        p = new_glyph_node();
+        set_to_glyph(p);
+        font(p) = f;
+        character(p) = c;
+    }
+    return p;
+}
+
+@ A subset of the glyphs nodes represent ligatures: characters
+fabricated from the interaction of two or more actual characters.  The
+characters that generated the ligature have not been forgotten, since
+they are needed for diagnostic messages; the |lig_ptr| field points to
+a linked list of character nodes for all original characters that have
+been deleted. (This list might be empty if the characters that
+generated the ligature were retained in other nodes.)
+
+The |subtype| field of these |glyph_node|s is 1, plus 2 and/or 1 if
+the original source of the ligature included implicit left and/or
+right boundaries. These nodes are created by the C function |new_ligkern|.
+
+A third general type of glyphs could be called a character, as it
+only appears in lists that are not yet processed by the ligaturing and
+kerning steps of the program.
+
+|main_control| inserts these, and they are later converted to
+|subtype_normal| by |new_ligkern|.
+
+ at c
+quarterword norm_min(int h)
+{
+    if (h <= 0)
+        return 1;
+    else if (h >= 255)
+        return 255;
+    else
+        return (quarterword) h;
+}
+
+halfword new_char(int f, int c)
+{
+    halfword p;                 /* the new node */
+    p = new_glyph_node();
+    set_to_character(p);
+    font(p) = f;
+    character(p) = c;
+    lang_data(p) = make_lang_data(uc_hyph_par, cur_lang_par, left_hyphen_min_par, right_hyphen_min_par);
+    return p;
+}
+
+@ Left and right ghost glyph nodes are the result of \.{\\leftghost}
+and \.{\\rightghost}, respectively. They are going to be removed by
+|new_ligkern|, at the end of which they are no longer needed.
+
+@ Here are a few handy helpers used by the list output routines.
+
+ at c
+scaled glyph_width(halfword p)
+{
+    scaled w = char_width(font(p), character(p));
+    return w;
+}
+
+scaled glyph_height(halfword p)
+{
+    scaled w = char_height(font(p), character(p)) + y_displace(p);
+    if (w < 0)
+        w = 0;
+    return w;
+}
+
+scaled glyph_depth(halfword p)
+{
+    scaled w = char_depth(font(p), character(p));
+    if (y_displace(p) > 0)
+        w = w - y_displace(p);
+    if (w < 0)
+        w = 0;
+    return w;
+}
+
+@ A |disc_node|, which occurs only in horizontal lists, specifies a
+``dis\-cretion\-ary'' line break. If such a break occurs at node |p|, the text
+that starts at |pre_break(p)| will precede the break, the text that starts at
+|post_break(p)| will follow the break, and text that appears in
+|no_break(p)| nodes will be ignored. For example, an ordinary
+discretionary hyphen, indicated by `\.{\\-}', yields a |disc_node| with
+|pre_break| pointing to a |char_node| containing a hyphen, |post_break=null|,
+and |no_break=null|.
+
+{TODO: Knuth said: All three of the discretionary texts must be lists
+that consist entirely of character, kern, box and rule nodes.}
+
+If |subtype(p)=automatic_disc|, the |ex_hyphen_penalty| will be charged for this
+break.  Otherwise the |hyphen_penalty| will be charged.  The texts will
+actually be substituted into the list by the line-breaking algorithm if it
+decides to make the break, and the discretionary node will disappear at
+that time; thus, the output routine sees only discretionaries that were
+not chosen.
+
+ at c
+halfword new_disc(void)
+{                               /* creates an empty |disc_node| */
+    halfword p = new_node(disc_node, 0);
+    disc_penalty(p) = hyphen_penalty_par;
+    return p;
+}
+
+@ A |whatsit_node| is a wild card reserved for extensions to \TeX. The
+|subtype| field in its first word says what `\\{whatsit}' it is, and
+implicitly determines the node size (which must be 2 or more) and the
+format of the remaining words. When a |whatsit_node| is encountered
+in a list, special actions are invoked; knowledgeable people who are
+careful not to mess up the rest of \TeX\ are able to make \TeX\ do new
+things by adding code at the end of the program. For example, there
+might be a `\TeX nicolor' extension to specify different colors of ink,
+@^extensions to \TeX@>
+and the whatsit node might contain the desired parameters.
+
+The present implementation of \TeX\ treats the features associated with
+`\.{\\write}' and `\.{\\special}' as if they were extensions, in order to
+illustrate how such routines might be coded. We shall defer further
+discussion of extensions until the end of this program.
+
+@ A |math_node|, which occurs only in horizontal lists, appears before and
+after mathematical formulas. The |subtype| field is |before| before the
+formula and |after| after it. There is a |surround| field, which represents
+the amount of surrounding space inserted by \.{\\mathsurround}.
+
+ at c
+halfword new_math(scaled w, int s)
+{
+    halfword p = new_node(math_node, s);
+    surround(p) = w;
+    return p;
+}
+
+@ \TeX\ makes use of the fact that |hlist_node|, |vlist_node|,
+|rule_node|, |ins_node|, |mark_node|, |adjust_node|,
+|disc_node|, |whatsit_node|, and |math_node| are at the low end of the
+type codes, by permitting a break at glue in a list if and only if the
+|type| of the previous node is less than |math_node|. Furthermore, a
+node is discarded after a break if its type is |math_node| or~more.
+
+@ A |glue_node| represents glue in a list. However, it is really only
+a pointer to a separate glue specification, since \TeX\ makes use of the
+fact that many essentially identical nodes of glue are usually present.
+If |p| points to a |glue_node|, |glue_ptr(p)| points to
+another packet of words that specify the stretch and shrink components, etc.
+
+Glue nodes also serve to represent leaders; the |subtype| is used to
+distinguish between ordinary glue (which is called |normal|) and the three
+kinds of leaders (which are called |a_leaders|, |c_leaders|, and |x_leaders|).
+The |leader_ptr| field points to a rule node or to a box node containing the
+leaders; it is set to |null| in ordinary glue nodes.
+
+Many kinds of glue are computed from \TeX's ``skip'' parameters, and
+it is helpful to know which parameter has led to a particular glue node.
+Therefore the |subtype| is set to indicate the source of glue, whenever
+it originated as a parameter. We will be defining symbolic names for the

@@ Diff output truncated at 1234567 characters. @@


More information about the tex-live-commits mailing list