From b9f09946a8fe2f81468fb7bf624e3f8ce2d26888 Mon Sep 17 00:00:00 2001
From: Karl Berry <karl@freefriends.org>
Date: Wed, 30 Sep 2015 21:17:21 +0000
Subject: unravel (30sep15)

git-svn-id: svn://tug.org/texlive/trunk@38506 c570f23f-e606-0410-a88d-b1316a301751
---
 Master/texmf-dist/source/latex/unravel/unravel.dtx | 893 ++++++++++++++++-----
 1 file changed, 712 insertions(+), 181 deletions(-)

(limited to 'Master/texmf-dist/source/latex/unravel/unravel.dtx')

diff --git a/Master/texmf-dist/source/latex/unravel/unravel.dtx b/Master/texmf-dist/source/latex/unravel/unravel.dtx
index 87170f4286c..328b20512ed 100644
--- a/Master/texmf-dist/source/latex/unravel/unravel.dtx
+++ b/Master/texmf-dist/source/latex/unravel/unravel.dtx
@@ -23,9 +23,9 @@
 %
 % \title{The \textsf{unravel} package: \\
 %   watching TeX digest tokens\thanks{This
-%     file has version number 0.1, last revised 2015/09/22.}}
+%     file has version number 0.2, last revised 2015/09/30.}}
 % \author{Bruno Le Floch}
-% \date{2015/09/22}
+% \date{2015/09/30}
 %
 % \maketitle
 % \tableofcontents
@@ -202,9 +202,21 @@
 % \item Some primitives are not implemented yet: alignments
 %   (\tn{halign}, \tn{valign}, \tn{noalign}, \tn{omit}, \tn{span},
 %   \tn{cr}, \tn{crcr},~|&|), many math mode primitives, and
-%   \cs{pdfprimitive}, \cs{aftergroup}, \cs{discretionary}, as well as
+%   \tn{pdfprimitive}, \tn{discretionary}, as well as
 %   all primitives specific to engines other than \pdfTeX{}.
-%   This list is sadly incomplete!
+%   This list may sadly be incomplete!
+% \item \tn{aftergroup} is only partially implemented.
+% \item \tn{everyhbox}, \tn{everyvbox}, \tn{everymath},
+%   \tn{everydisplay}, \tn{lastkern}, \tn{lastnodetype},
+%   \tn{lastpenalty}, \tn{lastskip}, \tn{currentiflevel} and
+%   \tn{currentiftype} may have wrong values.  Perhaps
+%   \tn{currentgrouplevel} and \tn{currentgrouptype} too.
+% \item Tokens passed to \tn{afterassignment} are not yet kept after
+%   \pkg{unravel} is done even if there has been no assignment.
+% \item Tokens passed to \tn{aftergroup} are lost when \pkg{unravel} is
+%   done.
+% \item In \XeTeX{}, characters beyond the basic multilingual plane may
+%   break \pkg{unravel} (not tested).
 % \item For \pkg{unravel}, category codes are fixed when a file is read
 %   using \tn{input},
 %   while \TeX{} only fixes category codes when the corresponding
@@ -227,6 +239,16 @@
 % \item Allow to replay steps that have already been run.
 % \item Fix the display for \tn{if} and \tn{ifcat} (remove extraneous
 %   \cs{exp_not:N}).
+% \item Use the |file-error| fatal error message: first implement
+%   \cs{@@_file_if_exist:nTF} and use it to determine whether
+%   \tn{input} will throw a fatal error in \tn{batchmode} and
+%   \tn{nonstopmode}.
+% \item Use the |interwoven-preambles| fatal error message once
+%   alignments are implemented.
+% \item Look at all places where \TeX{}'s procedure |prepare_mag| is
+%   called.
+% \item Find out why so many input levels are used (see the log of the
+%   |unravel003| testfile for instance)
 % \end{itemize}
 %
 % \end{documentation}
@@ -243,21 +265,251 @@
 %    \end{macrocode}
 %
 %    \begin{macrocode}
-\RequirePackage{expl3,xparse}[2015/09/11]
-\RequirePackage{gtl}[2015/09/21]
-\ProvidesExplPackage
-  {unravel} {2015/09/22} {0.1} {Watching TeX digest tokens}
+%<@@=unravel>
+%    \end{macrocode}
+%
+% Catcode settings.  In a group, set |\c| to be a synonym of |\catcode|
+% for short, set the catcode of space to be 10 (using |\fam| to avoid
+% needing a space or an equal sign to separate the two integer
+% arguments of |\catcode|) and that of |%| to be 14 (using |\fam| again
+% to avoid needing the digit 7 to have catcode other: we need the digit
+% 5 anyway in two steps).  Then make |-|, |6|, |7|, |8|, |9| other (we
+% must assume that |0| through |5| are already other), and make |:|,
+% |_|, |h|, |j|, |k|, |q|, |s|, |w|, |x|, |y|, |z| letters (other
+% lowercase letters already need to be letters in the rest of the
+% code).  Make sure there is no |\endlinechar|.  We are finally ready
+% to safely test whether the package has already been loaded and bail
+% out in case it has.  Expanding |\fi| before ending the group ensures
+% that the whole line has been read by \TeX{} before restoring earlier
+% catcodes.
+%    \begin{macrocode}
+\begingroup\let\c\catcode\fam32\c\fam10\advance\fam5\c\fam14\c45 12 %
+\c54 12\c55 12\c56 12\c57 12\c58 11\c95 11\c104 11\c106 11\c107 11 %
+\c113 11\c115 11\c119 11\c120 11\c121 11\c122 11\endlinechar-1 %
+\expandafter\ifx\csname unravel\endcsname\relax
+\else\endinput\expandafter\endgroup\fi
+%    \end{macrocode}
+%
+% Set |T| and |X| to be letters for an error message.  Set up braces
+% and |#| for definitions, |=| for nicer character code assignments,
+% |>| for integer comparison, |+| for integer expressions.
+%    \begin{macrocode}
+\c84 11\c88 11\c35 6\c123 1\c125 2\c62 12\c61 12\c43 12 %
+%    \end{macrocode}
+%
+% If \eTeX{}'s \tn{numexpr} or \tn{protected} are not available, bail
+% out with an error.
+%    \begin{macrocode}
+\expandafter\ifx\csname numexpr\endcsname\relax
+\errmessage{unravel requires \numexpr from eTeX}
+\endinput\expandafter\endgroup\fi
+\expandafter\ifx\csname protected\endcsname\relax
+\errmessage{unravel requires \protected from eTeX}
+\endinput\expandafter\endgroup\fi
+%    \end{macrocode}
+%
+% If \pkg{unravel} is loaded within a group, bail out because
+% \pkg{expl3} would not be loaded properly.
+%    \begin{macrocode}
+\expandafter\ifx\csname currentgrouplevel\endcsname\relax\else
+\ifnum\currentgrouplevel>1 \errmessage{unravel loaded in a group}
+\endinput\expandafter\expandafter\expandafter\endgroup\fi\fi
+%    \end{macrocode}
+%
+% Make spaces ignored and make |~| a space, to prettify code.
+%    \begin{macrocode}
+\catcode 32 = 9 \relax
+\catcode 126 = 10 \relax
+%    \end{macrocode}
+%
+% \begin{variable}{\l_@@_setup_restore_tl}
+%   This token list variable will contain code to restore category
+%   codes to their value when the package was loaded.
+%    \begin{macrocode}
+\gdef \l_@@_setup_restore_tl { }
+%    \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}[int]{\@@_setup_restore:}
+%   Use the token list to restore catcodes to their former values, then
+%   empty the list since there is no catcode to restore anymore.  This
+%   mechanism cannot be nested.
+%    \begin{macrocode}
+\protected \gdef \@@_setup_restore:
+  {
+    \l_@@_setup_restore_tl
+    \def \l_@@_setup_restore_tl { }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{\@@_setup_save:}
+% \begin{macro}[aux, EXP]{\@@_setup_save_aux:n}
+%   This saves into \cs{l_@@_setup_restore_tl} the current catcodes
+%   (from $0$ to $255$ only), \tn{endlinechar}, \tn{escapechar},
+%   \tn{newlinechar}.
+%    \begin{macrocode}
+\protected \gdef \@@_setup_save:
+  {
+    \edef \l_@@_setup_restore_tl
+      {
+        \@@_setup_save_aux:w 0 =
+        \endlinechar = \the \endlinechar
+        \escapechar = \the \escapechar
+        \newlinechar = \the \newlinechar
+        \relax
+      }
+  }
+\long \gdef \@@_setup_save_aux:w #1 =
+  {
+    \catcode #1 = \the \catcode #1 ~
+    \ifnum 255 > #1 ~
+      \expandafter \@@_setup_save_aux:w
+      \the \numexpr #1 + 1 \expandafter =
+    \fi
+  }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[int]{\@@_setup_catcodes:nnn}
+%   This sets all characters from |#1| to |#2| (inclusive) to have
+%   catcode |#3|.
+%    \begin{macrocode}
+\protected \long \gdef \@@_setup_catcodes:nnn #1 #2 #3
+  {
+    \ifnum #1 > #2 ~ \else
+      \catcode #1 = #3 ~
+      \expandafter \@@_setup_catcodes:nnn \expandafter
+        { \the \numexpr #1 + 1 } {#2} {#3}
+    \fi
+  }
 %    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{\@@_setup_latexe:}
+%   This saves the catcodes and related parameters, then sets them to
+%   the value they normally have in a \LaTeXe{} package (in particular,
+%   |@| is a letter).
+%    \begin{macrocode}
+\protected \gdef \@@_setup_latexe:
+  {
+    \@@_setup_save:
+    \@@_setup_catcodes:nnn {0} {8} {15}
+    \catcode 9 = 10 ~
+    \catcode 10 = 12 ~
+    \catcode 11 = 15 ~
+    \catcode 12 = 13 ~
+    \catcode 13 = 5 ~
+    \@@_setup_catcodes:nnn {14} {31} {15}
+    \catcode 32 = 10 ~
+    \catcode 33 = 12 ~
+    \catcode 34 = 12 ~
+    \catcode 35 = 6 ~
+    \catcode 36 = 3 ~
+    \catcode 37 = 14 ~
+    \catcode 38 = 4 ~
+    \@@_setup_catcodes:nnn {39} {63} {12}
+    \@@_setup_catcodes:nnn {64} {90} {11}
+    \catcode 91 = 12 ~
+    \catcode 92 = 0 ~
+    \catcode 93 = 12 ~
+    \catcode 94 = 7 ~
+    \catcode 95 = 8 ~
+    \catcode 96 = 12 ~
+    \@@_setup_catcodes:nnn {97} {122} {11}
+    \catcode 123 = 1 ~
+    \catcode 124 = 12 ~
+    \catcode 125 = 2 ~
+    \catcode 126 = 13 ~
+    \catcode 127 = 15 ~
+    \@@_setup_catcodes:nnn {128} {255} {12}
+    \endlinechar = 13 ~
+    \escapechar = 92 ~
+    \newlinechar = 10 ~
+  }
+%    \end{macrocode}
+% \end{macro}
 %
+% \begin{macro}[int]{\@@_setup_unravel:}
+%   Catcodes for \pkg{unravel} (in particular, |@| is other, |:| and
+%   |_| are letters, spaces are ignored, |~| is a space).
+%    \begin{macrocode}
+\protected \gdef \@@_setup_unravel:
+  {
+    \@@_setup_save:
+    \@@_setup_catcodes:nnn {0} {8} {15}
+    \catcode 9 = 9 ~
+    \catcode 10 = 12 ~
+    \catcode 11 = 15 ~
+    \catcode 12 = 13 ~
+    \catcode 13 = 5 ~
+    \@@_setup_catcodes:nnn {14} {31} {15}
+    \catcode 32 = 9 ~
+    \catcode 33 = 12 ~
+    \catcode 34 = 12 ~
+    \catcode 35 = 6 ~
+    \catcode 36 = 3 ~
+    \catcode 37 = 14 ~
+    \catcode 38 = 4 ~
+    \@@_setup_catcodes:nnn {39} {57} {12}
+    \catcode 58 = 11 ~
+    \@@_setup_catcodes:nnn {59} {64} {12}
+    \@@_setup_catcodes:nnn {65} {90} {11}
+    \catcode 91 = 12 ~
+    \catcode 92 = 0 ~
+    \catcode 93 = 12 ~
+    \catcode 94 = 7 ~
+    \catcode 95 = 11 ~
+    \catcode 96 = 12 ~
+    \@@_setup_catcodes:nnn {97} {122} {11}
+    \catcode 123 = 1 ~
+    \catcode 124 = 12 ~
+    \catcode 125 = 2 ~
+    \catcode 126 = 10 ~
+    \catcode 127 = 15 ~
+    \@@_setup_catcodes:nnn {128} {255} {12}
+    \escapechar  = 92 ~
+    \endlinechar  = 32 ~
+    \newlinechar  = 10 ~
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% End the group where all catcodes where changed, but expand
+% \cs{@@_setup_latexe:} to sanitize catcodes again outside the
+% group.  The catcodes are saved.
+%    \begin{macrocode}
+\expandafter \endgroup \@@_setup_latexe:
+%    \end{macrocode}
+%
+% Load a few dependencies: \pkg{expl3}, \pkg{xparse}, \pkg{gtl}.
 % Load \pkg{l3str} if \pkg{expl3} is too old and does not define
 % \cs{str_range:nnn}.  Otherwise loading \pkg{l3str} would give
 % an error.
 %    \begin{macrocode}
-\cs_if_exist:NF \str_range:nnn { \RequirePackage{l3str} }
+\RequirePackage{expl3,xparse}[2015/09/11]
+\RequirePackage{gtl}[2015/09/21]
+\csname cs_if_exist:cF\endcsname{str_range:nnn}{\RequirePackage{l3str}}
 %    \end{macrocode}
 %
+% Before loading \pkg{unravel}, restore catcodes, so that the implicit
+% \cs{ExplSyntaxOn} in \cs{ProvidesExplPackage} picks up the correct
+% catcodes to restore when \cs{ExplSyntaxOff} is run at the end of the
+% package.  The place where catcodes are restored are beyond
+% \pkg{unravel}'s reach, which is why we cannot bypass \pkg{expl3} and
+% simply restore the catcodes once everything is done.  To avoid issues
+% with crazy catcodes, make \TeX{} read the arguments of
+% \cs{ProvidesExplPackage} before restoring catcodes.  Then immediately
+% go to the catcodes we want.
 %    \begin{macrocode}
-%<@@=unravel>
+\csname use:n\endcsname
+  {%
+    \csname @@_setup_restore:\endcsname
+    \ProvidesExplPackage
+      {unravel} {2015/09/30} {0.2} {Watching TeX digest tokens}%
+    \csname @@_setup_unravel:\endcsname
+  }%
 %    \end{macrocode}
 %
 % \subsection{Primitives, variants, and helpers}
@@ -281,6 +533,7 @@
 \cs_new_eq:NN \@@_everyeof:w             \etex_everyeof:D
 \cs_new_eq:NN \@@_everypar:w             \tex_everypar:D
 \cs_new_eq:NN \@@_hbox:w                 \tex_hbox:D
+\cs_new_eq:NN \@@_mag:                   \tex_mag:D
 \cs_new_eq:NN \@@_nullfont:              \tex_nullfont:D
 \cs_new_eq:NN \@@_the:w                  \tex_the:D
 %    \end{macrocode}
@@ -299,24 +552,15 @@
 %
 % Variants that we need.
 %    \begin{macrocode}
-\cs_if_exist:NF \exp_last_unbraced:NNn
-  { \cs_new_eq:NN \exp_last_unbraced:NNn \use:nnn }
-\cs_generate_variant:Nn \exp_last_unbraced:NNn { NNv }
 \cs_generate_variant:Nn \str_head:n { f }
 \cs_generate_variant:Nn \tl_to_str:n { o }
 \cs_generate_variant:Nn \tl_if_head_eq_meaning:nNT { V }
-\cs_generate_variant:Nn \tl_if_head_is_space:nTF { o }
-\cs_generate_variant:Nn \tl_if_head_is_space:nT { V }
-\cs_generate_variant:Nn \tl_if_head_is_N_type:nTF { o }
 \cs_generate_variant:Nn \tl_if_in:nnF { nV }
 \cs_generate_variant:Nn \tl_if_in:nnTF { nV }
-\cs_generate_variant:Nn \tl_if_eq:nnT { V }
 \cs_generate_variant:Nn \tl_if_in:NnTF { No , NV }
 \cs_generate_variant:Nn \tl_if_single_token:nT { V }
 \cs_generate_variant:Nn \tl_gset_rescan:Nnn { Nnx }
-\cs_generate_variant:Nn \gtl_gput_left:Nn { Nx , NV , No }
-\cs_generate_variant:Nn \gtl_gput_right:Nn { Nx , NV }
-\cs_generate_variant:Nn \gtl_put_right:Nn { NV }
+\cs_generate_variant:Nn \gtl_gput_right:Nn { NV }
 \cs_generate_variant:Nn \ior_get_str:NN { Nc }
 \cs_generate_variant:Nn \gtl_if_empty:NTF { c }
 \cs_generate_variant:Nn \gtl_to_str:N { c }
@@ -337,31 +581,97 @@
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}[aux]{\@@_tl_gset_input:Nnn, \@@_tl_gset_input:Nno}
-% \begin{macro}[aux]{\@@_tl_gset_input_aux:wN}
+% \begin{macro}[aux]{\@@_file_get:nN}
+% \begin{macro}[aux]{\@@_file_get_aux:wN}
 %    \begin{macrocode}
-\cs_new_protected:Npn \@@_tl_gset_input:Nnn #1#2#3
+\cs_set_protected:Npn \@@_tmp:w #1
   {
-    \group_begin:
-      \@@_everyeof:w \exp_after:wN { \token_to_str:N @ @ #1 }
-      #2
-      \tl_gclear:N #1
-      \exp_after:wN \@@_tl_gset_input_aux:wN
-      \exp_after:wN \prg_do_nothing:
-        \tex_input:D \tl_to_str:n {#3} \scan_stop:
-    \group_end:
-    \tl_gput_right:NV #1 \@@_everyeof:w
+    \cs_new_protected:Npn \@@_file_get:nN ##1##2
+      {
+        \group_begin:
+          \@@_everyeof:w { #1 ##2 }
+          \exp_after:wN \@@_file_get_aux:wN
+          \exp_after:wN \prg_do_nothing:
+            \tex_input:D ##1 \scan_stop:
+      }
+    \cs_new_protected:Npn \@@_file_get_aux:wN ##1 #1 ##2
+      {
+        \group_end:
+        \tl_set:Nx ##2
+          { \exp_not:o {##1} \exp_not:V \@@_everyeof:w }
+      }
   }
-\cs_generate_variant:Nn \@@_tl_gset_input:Nnn { Nno }
-\use:x
+\exp_args:No \@@_tmp:w { \token_to_str:N : : }
+%    \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}[int, EXP]{\@@_tl_first_int:N}
+% \begin{macro}[aux, EXP]{\@@_tl_first_int_aux:Nn}
+%   Function that finds an explicit number in a token list.  This is
+%   used for instance when implementing \tn{read}, to find the stream
+%   \meta{number} within the whole \tn{read} \meta{number} |to|
+%   \meta{cs} construction.  The auxiliary initially has itself as a
+%   first argument, and once a first digit is found it has
+%   \cs{use_none_delimit_by_q_stop:w}.  That first argument is used
+%   whenever what follows is not a digit, hence initially we loop,
+%   while after the first digit is found any non-digit stops the
+%   recursion.  If no integer is found, $0$ is left in the token list.
+%   The surrounding \cs{int_eval:n} lets us dump digits in the input
+%   stream while keeping the function fully expandable.
+%    \begin{macrocode}
+\cs_new:Npn \@@_tl_first_int:N #1
   {
-    \cs_new_protected:Npn \exp_not:N \@@_tl_gset_input_aux:wN
-      ##1 \token_to_str:N @ @ ##2
-  }   { \tl_gset:No #2 {#1} }
+    \int_eval:n
+      {
+        \exp_after:wN \@@_tl_first_int_aux:Nn
+        \exp_after:wN \@@_tl_first_int_aux:Nn
+        #1 ? 0 ? \q_stop
+      }
+  }
+\cs_new:Npn \@@_tl_first_int_aux:Nn #1#2
+  {
+    \tl_if_single:nT {#2}
+      {
+        \token_if_eq_catcode:NNT + #2
+          {
+            \if_int_compare:w 1 < 1 #2 \exp_stop_f:
+              #2
+              \exp_after:wN \use_i_ii:nnn
+              \exp_after:wN \@@_tl_first_int_aux:Nn
+              \exp_after:wN \use_none_delimit_by_q_stop:w
+            \fi:
+          }
+      }
+    #1
+  }
 %    \end{macrocode}
 % \end{macro}
 % \end{macro}
 %
+% \begin{macro}[int]{\@@_prepare_mag:}
+%   Used whenever \TeX{} needs the value of \tn{mag}.  ^^A todo: move
+%    \begin{macrocode}
+\cs_new_protected_nopar:Npn \@@_prepare_mag:
+  {
+    \int_compare:nNnT { \g_@@_mag_set_int } > { 0 }
+      {
+        \int_compare:nNnF { \@@_mag: } = { \g_@@_mag_set_int }
+          {
+            \@@_tex_error:nn { incompatible-mag } { }
+            \int_gset_eq:NN \@@_mag: \g_@@_mag_set_int
+          }
+      }
+    \int_compare:nF { 1 <= \@@_mag: <= 32768 }
+      {
+        \@@_tex_error:nV { illegal-mag } \l_@@_head_tl
+        \int_gset:Nn \@@_mag: { 1000 }
+      }
+    \int_gset_eq:NN \g_@@_mag_set_int \@@_mag:
+  }
+%    \end{macrocode}
+% \end{macro}
+%
 % \subsubsection{String helpers}
 %
 % \begin{macro}[int]{\@@_strip_escape:w}
@@ -951,6 +1261,14 @@
 %    \end{macrocode}
 % \end{variable}
 %
+% \begin{variable}{\l_@@_after_group_gtl}
+%   Tokens to insert after the current group ends.  This variable must
+%   be emptied at the beginning of every group.
+%    \begin{macrocode}
+\gtl_new:N \l_@@_after_group_gtl
+%    \end{macrocode}
+% \end{variable}
+%
 % \begin{variable}{\c_@@_parameters_tl}
 %   Used to determine if a macro has simple parameters or not.
 %    \begin{macrocode}
@@ -1052,6 +1370,19 @@
 %    \end{macrocode}
 % \end{variable}
 %
+% \subsubsection{\TeX{} parameters}
+%
+% \begin{variable}{\g_@@_mag_set_int}
+%   The first time \TeX{} uses the value of \tn{mag}, it stores it in a
+%   global parameter |mag_set| (initially $0$ to denote not being set).
+%   Any time \TeX{} needs the value of \tn{mag}, it checks that the
+%   value matches |mag_set|.  This is done in \pkg{unravel} by
+%   \cs{@@_prepare_mag:}, storing |mag_set| in \cs{g_@@_mag_set_int}.
+%    \begin{macrocode}
+\int_new:N \g_@@_mag_set_int
+%    \end{macrocode}
+% \end{variable}
+%
 % \subsection{Numeric codes}
 % \label{sec:numeric-codes}
 %
@@ -2516,7 +2847,7 @@
     \bool_if:NT \g_@@_internal_debug_bool
       {
         \@@_set_cmd:
-        \iow_term:x { Exp:~\int_use:N \l_@@_head_cmd_int }
+        \iow_term:x { Exp:~\int_to_arabic:n { \l_@@_head_cmd_int } }
       }
     \token_if_macro:NTF \l_@@_head_token
       { \@@_macro_call: }
@@ -2769,6 +3100,21 @@
 % \end{macro}
 % \end{macro}
 %
+% \begin{macro}[aux]{\@@_scan_to:}
+%   Used when |to| is mandatory: after \tn{read} or \tn{readline} and
+%   after \tn{vsplit}.
+%    \begin{macrocode}
+\cs_new_protected_nopar:Npn \@@_scan_to:
+  {
+    \@@_scan_keyword:nF { tToO }
+      {
+        \msg_error:nn { unravel } { missing-to }
+        \@@_prev_input:n { to }
+      }
+  }
+%    \end{macrocode}
+% \end{macro}
+%
 % \begin{macro}[aux]{\@@_scan_font_ident:}
 %   Find a font identifier.
 %    \begin{macrocode}
@@ -2794,8 +3140,8 @@
       }
       {
         \msg_error:nn { unravel } { missing-font-id }
+        \@@_back_input:
         \@@_prev_input:n { \@@_nullfont: }
-        \@@_back_error:
       }
   }
 %    \end{macrocode}
@@ -2859,7 +3205,7 @@
 %   performed after placing the current token in a new level of
 %   |prev_input| and telling the user about it.  Once done with this
 %   step, \cs{l_@@_head_tl} contains the tokens found.  Convert them to
-%   the wanted level with \cs{@@_thing_use:nN} (at this stage, \TeX{}
+%   the wanted level with \cs{@@_thing_use_get:nnNN} (at this stage, \TeX{}
 %   may complain about a missing number or incompatible glue units), and
 %   place the result in |prev_input|.  Finally, tell the user the tokens
 %   that have been found and their value (and, if there was a single
@@ -2880,7 +3226,11 @@
   {
     \int_compare:nNnTF
       { ( #1 + \c_two ) / \c_four } > { ( #3 + \c_two ) / \c_four }
-      { \@@_back_input: }
+      {
+        \@@_back_input:
+        \msg_error:nn { unravel } { missing-something }
+        \tl_clear:N \l_@@_tmpa_tl
+      }
       {
         \tl_if_empty:nF {#2}
           {
@@ -2889,9 +3239,8 @@
             #2
             \seq_gpop_right:NN \g_@@_prev_input_seq \l_@@_head_tl
           }
+        \@@_thing_use_get:nnNN {#1} {#3} \l_@@_head_tl \l_@@_tmpa_tl
       }
-    \tl_set:Nx \l_@@_tmpa_tl
-      { \@@_thing_use:nnN {#1} {#3} \l_@@_head_tl }
     \@@_prev_input_silent:V \l_@@_tmpa_tl
     \@@_set_action_text:
     \@@_set_action_text:x
@@ -2937,9 +3286,9 @@
         { 83 } { 1 \@@_scan_int:           } % set_box_dimen
         { 84 } { 0 \@@_scan_int:           } % set_shape
         { 85 } { 0 \@@_scan_int:           } % def_code
-        { 86 } { 4 \@@_scan_font_ident:    } % def_family
-        { 87 } { 4 \@@_scan_font_ident:    } % set_font
-        { 88 } { 4 \@@_scan_font_ident:    } % def_font
+        { 86 } { 4 \@@_scan_int:           } % def_family
+        { 87 } { 4                         } % set_font
+        { 88 } { 4                         } % def_font
         { 89 } { \@@_thing_register:       } % register
       }
       { 8 }
@@ -3009,21 +3358,45 @@
 %    \end{macrocode}
 % \end{macro}
 %
-% \begin{macro}[aux,EXP]{\@@_thing_use:nnN}
-%   Given a level~|#1|, call a function to convert the token list~|#2|
-%   to the correct level.  This step may trigger \TeX{} errors, which
-%   should precisely match the expected ones.
+% \begin{macro}[aux]{\@@_thing_use_get:nnNN}
+%   Given a level found~|#1| and a target level~|#2| (with |#1| in
+%   $[0,3]$ or |#1| and |#2| in $[4,5]$), turn the token list |#3| into
+%   the desired level, and store the result in |#4|.  This step may
+%   trigger \TeX{} errors, which should precisely match the expected
+%   ones.
 %    \begin{macrocode}
-\cs_new:Npn \@@_thing_use:nnN #1#2
+\cs_new_protected:Npn \@@_thing_use_get:nnNN #1#2#3#4
   {
-    \int_case:nnF { \int_min:nn {#1} {#2} }
+    \int_compare:nNnTF {#2} < { 3 }
+      {
+        \int_compare:nNnT {#1} = { 3 }
+          { \@@_tex_error:nV { incompatible-units } #3 }
+        \tl_set:Nx #4
+          {
+            \int_case:nn { \int_min:nn {#1} {#2} }
+              {
+                { 0 } \int_eval:n
+                { 1 } \dim_eval:n
+                { 2 } \skip_eval:n
+              }
+            { \int_compare:nNnT {#1} = { 3 } \etex_mutoglue:D #3 }
+          }
+      }
       {
-        { 0 } \int_eval:n
-        { 1 } \dim_eval:n
-        { 2 } \skip_eval:n
-        { 3 } \muskip_eval:n
+        \int_compare:nNnTF {#2} = { 3 }
+          {
+            \int_case:nnF {#1}
+              {
+                { 0 } { \tl_set:Nx #4 { \int_eval:n {#3} } }
+                { 3 } { \tl_set:Nx #4 { \muskip_eval:n {#3} } }
+              }
+              {
+                \@@_tex_error:nV { incompatible-units } #3
+                \tl_set:Nx #4 { \muskip_eval:n { \etex_gluetomu:D #3 } }
+              }
+          }
+          { \tl_set:Nx #4 { \@@_the:w #3 } }
       }
-      { \@@_the:w }
   }
 %    \end{macrocode}
 % \end{macro}
@@ -3222,28 +3595,10 @@
     \@@_cmd_if_internal:TF
       {
         \int_compare:nNnTF {#1} = { 3 }
-          {
-            \@@_scan_something_internal:n { 3 }
-            \int_case:nnF \g_@@_val_level_int
-              {
-                { 0 } { \@@_scan_dim_unit:nN {#1} #2 }
-                { 3 } { }
-              }
-              {
-                \msg_error:nn { unravel } { incompatible-units }
-                % ^^A todo: error recovery
-              }
-          }
-          {
-            \@@_scan_something_internal:n { 1 }
-            \int_case:nnF \g_@@_val_level_int
-              {
-                { 0 } { \@@_scan_dim_unit:nN {#1} #2 }
-                { 3 } % ^^A todo: error recovery
-                { \msg_error:nn { unravel } { incompatible-units } }
-              }
-              { }
-          }
+          { \@@_scan_something_internal:n { 3 } }
+          { \@@_scan_something_internal:n { 1 } }
+        \int_compare:nNnT \g_@@_val_level_int = { 0 }
+          { \@@_scan_dim_unit:nN {#1} #2 }
       }
       { \@@_scan_dimen_char:nN {#1} #2 }
     \seq_gpop_right:NN \g_@@_prev_input_seq \l_@@_head_tl
@@ -3283,7 +3638,7 @@
   {
     \bool_if:NT #2
       {
-        \@@_scan_keyword:nT { fF iI lL }
+        \@@_scan_keyword:nT { fFiIlL }
           {
             \@@_scan_inf_unit_loop:
             \@@_break:w
@@ -3302,25 +3657,26 @@
       { \@@_back_input: }
     \int_compare:nNnT {#1} = { 3 }
       {
-        \@@_scan_keyword:nT { mM uU } { \@@_break:w }
+        \@@_scan_keyword:nT { mMuU } { \@@_break:w }
         \msg_error:nn { unravel } { missing-mudim }
         \@@_break:w
       }
-    \@@_scan_keyword:nT { eE mM } { \@@_break:w }
-    \@@_scan_keyword:nT { eE xX } { \@@_break:w }
-    \@@_scan_keyword:nT { pP xX } { \@@_break:w }
-    \@@_scan_keyword:n { tT rR uU eE }
-    \@@_scan_keyword:nT { pP tT } { \@@_break:w }
-    \@@_scan_keyword:nT { iI nN } { \@@_break:w }
-    \@@_scan_keyword:nT { pP cC } { \@@_break:w }
-    \@@_scan_keyword:nT { cC mM } { \@@_break:w }
-    \@@_scan_keyword:nT { mM mM } { \@@_break:w }
-    \@@_scan_keyword:nT { bB pP } { \@@_break:w }
-    \@@_scan_keyword:nT { dD dD } { \@@_break:w }
-    \@@_scan_keyword:nT { cC cC } { \@@_break:w }
-    \@@_scan_keyword:nT { nN dD } { \@@_break:w }
-    \@@_scan_keyword:nT { nN cC } { \@@_break:w }
-    \@@_scan_keyword:nT { sS pP } { \@@_break:w }
+    \@@_scan_keyword:nT { eEmM } { \@@_break:w }
+    \@@_scan_keyword:nT { eExX } { \@@_break:w }
+    \@@_scan_keyword:nT { pPxX } { \@@_break:w }
+    \@@_scan_keyword:nT { tTrRuUeE }
+      { \@@_prepare_mag: }
+    \@@_scan_keyword:nT { pPtT } { \@@_break:w }
+    \@@_scan_keyword:nT { iInN } { \@@_break:w }
+    \@@_scan_keyword:nT { pPcC } { \@@_break:w }
+    \@@_scan_keyword:nT { cCmM } { \@@_break:w }
+    \@@_scan_keyword:nT { mMmM } { \@@_break:w }
+    \@@_scan_keyword:nT { bBpP } { \@@_break:w }
+    \@@_scan_keyword:nT { dDdD } { \@@_break:w }
+    \@@_scan_keyword:nT { cCcC } { \@@_break:w }
+    \@@_scan_keyword:nT { nNdD } { \@@_break:w }
+    \@@_scan_keyword:nT { nNcC } { \@@_break:w }
+    \@@_scan_keyword:nT { sSpP } { \@@_break:w }
     \@@_break_point:
   }
 \cs_new_protected_nopar:Npn \@@_scan_inf_unit_loop:
@@ -3367,20 +3723,17 @@
         \int_case:nnF \g_@@_val_level_int
           {
             { 0 } { \@@_scan_dimen:nN {#1} \c_false_bool }
-            {#1} { \@@_break:w }
-          }
-          {
-            \int_compare:nNnF { \g_@@_val_level_int + #1 } = 3
-              { \msg_error:nn { unravel } { incompatible-units } }
+            { 1 } { }
           }
+          { \@@_break:w }
       }
       { \@@_back_input: \@@_scan_dimen:nN {#1} \c_false_bool }
     \@@_prev_input_join_get:nN {#1} \l_@@_tmpa_tl
     \seq_gput_right:Nn \g_@@_prev_input_seq { }
     \seq_gput_right:NV \g_@@_prev_input_seq \l_@@_tmpa_tl
-    \@@_scan_keyword:nT { pP lL uU sS }
+    \@@_scan_keyword:nT { pPlLuUsS }
       { \@@_scan_dimen:nN {#1} \c_true_bool }
-    \@@_scan_keyword:nT { mM iI nN uU sS }
+    \@@_scan_keyword:nT { mMiInNuUsS }
       { \@@_scan_dimen:nN {#1} \c_true_bool }
     \@@_break_point:
     \@@_prev_input_join_get:nN {#1} \l_@@_tmpa_tl
@@ -3654,9 +4007,9 @@
 %    \begin{macrocode}
 \cs_new_protected_nopar:Npn \@@_scan_spec:
   {
-    \@@_scan_keyword:nTF { tT oO } { \@@_scan_normal_dimen: }
+    \@@_scan_keyword:nTF { tToO } { \@@_scan_normal_dimen: }
       {
-        \@@_scan_keyword:nT { sS pP rR eE aA dD }
+        \@@_scan_keyword:nT { sSpPrReEaAdD }
           { \@@_scan_normal_dimen: }
       }
     \@@_scan_left_brace:
@@ -3742,11 +4095,7 @@
         { 3 } % vsplit
           {
             \@@_scan_int:
-            \@@_scan_keyword:nF { tT oO }
-              {
-                \msg_error:nn { unravel } { missing-to }
-                \@@_prev_input:n { to }
-              }
+            \@@_scan_to:
             \@@_scan_normal_dimen:
           }
       }
@@ -3760,14 +4109,18 @@
 %   For leaders, we need to fetch a glue.  In all cases, retrieve the
 %   box construction (such as |\raise3pt\vsplit7to5em|).  Finally, let
 %   \TeX{} run the code and print what we have done.
+%   In the case of \tn{shipout}, check that \tn{mag} has a value
+%   between $1$ and $32768$.
 %    \begin{macrocode}
 \cs_new_protected:Npn \@@_do_simple_box:N #1
   {
     \bool_if:NTF #1 { \@@_do_leaders_fetch_skip: }
       {
         \seq_gpop_right:NN \g_@@_prev_input_seq \l_@@_head_tl
+        \tl_if_head_eq_meaning:VNT \l_@@_head_tl \tex_shipout:D
+          { \@@_prepare_mag: }
         \tl_use:N \l_@@_head_tl \scan_stop:
-        \gtl_put_right:NV \g_@@_output_gtl \l_@@_head_tl
+        \gtl_gput_right:NV \g_@@_output_gtl \l_@@_head_tl
         \@@_print_action:x { \tl_to_str:N \l_@@_head_tl }
       }
   }
@@ -3812,8 +4165,8 @@
 %   is such that all that material is collected and put into the input
 %   that we will study.
 %   We must remember to find a glue for leaders, and for this
-%   we use a stack of booleans: the top is true if the innermost box is
-%   part of leaders.
+%   we use a stack of letters |v|, |h| for vertical/horizontal
+%   leaders, and |Z| for normal boxes.
 %    \begin{macrocode}
 \cs_new_protected:Npn \@@_do_box_explicit:N #1
   {
@@ -3838,6 +4191,10 @@
 %
 % \begin{macro}[aux]{\@@_box_hook:N}
 % \begin{macro}[aux]{\@@_box_hook:w, \@@_box_hook_end:}
+%   Used to capture the contents of an \tn{everyhbox} or similar,
+%   without altering \tn{everyhbox} too much (just add one token at the
+%   start).  The various \texttt{o}-expansions remove
+%   \cs{prg_do_nothing:}, used to avoid losing braces.
 %    \begin{macrocode}
 \cs_new_protected:Npn \@@_box_hook:N #1
   {
@@ -3855,6 +4212,7 @@
       {
         \exp_args:No #1 {##1}
         \cs_gset_eq:NN \@@_box_hook:w \prg_do_nothing:
+        \gtl_clear:N \l_@@_after_group_gtl
         \@@_print_action:
         \@@_back_input:o {##1}
         \@@_set_action_text:x
@@ -4009,30 +4367,35 @@
 % ^^A todo: implement \@@_end_disc_group:
 % ^^A todo: implement \@@_end_math_choice_group:
 %
-% \begin{macro}[aux]{\@@_handle_left_brace:}
+% \begin{macro}[aux]{\@@_handle_right_brace:}
 %   When an end-group character is sensed, the result depends on the
 %   current group type.
 %    \begin{macrocode}
-\cs_new_protected_nopar:Npn \@@_handle_left_brace:
+\cs_new_protected_nopar:Npn \@@_handle_right_brace:
   {
-    \int_case:nnF \@@_currentgrouptype:
+    \int_compare:nTF { 1 <= \@@_currentgrouptype: <= 13 }
       {
-        { 1 } { \@@_end_simple_group: } % simple
-        { 2 } { \@@_end_box_group: } % hbox
-        { 3 } { \@@_end_box_group: } % adjusted_hbox
-        { 4 } { \@@_end_graf: \@@_end_box_group: } % vbox
-        { 5 } { \@@_end_graf: \@@_end_box_group: } % vtop
-        { 6 } { \@@_end_align_group: } % align
-        { 7 } { \@@_end_no_align_group: } % no_align
-        { 8 } { \@@_end_output_group: } % output
-        { 9 } { \@@_end_simple_group: } % math
-        { 10 } { \@@_end_disc_group: } % disc
-        { 11 } { \@@_end_graf: \@@_end_simple_group: } % insert
-        { 12 } { \@@_end_graf: \@@_end_simple_group: } % vcenter
-        { 13 } { \@@_end_math_choice_group: } % math_choice
+        \gtl_gconcat:NNN \g_@@_output_gtl
+          \g_@@_output_gtl \c_group_end_gtl
+        \@@_back_input_gtl:N \l_@@_after_group_gtl
+        \int_case:nn \@@_currentgrouptype:
+          {
+            { 1 } { \@@_end_simple_group: } % simple
+            { 2 } { \@@_end_box_group: } % hbox
+            { 3 } { \@@_end_box_group: } % adjusted_hbox
+            { 4 } { \@@_end_graf: \@@_end_box_group: } % vbox
+            { 5 } { \@@_end_graf: \@@_end_box_group: } % vtop
+            { 6 } { \@@_end_align_group: } % align
+            { 7 } { \@@_end_no_align_group: } % no_align
+            { 8 } { \@@_end_output_group: } % output
+            { 9 } { \@@_end_simple_group: } % math
+            { 10 } { \@@_end_disc_group: } % disc
+            { 11 } { \@@_end_graf: \@@_end_simple_group: } % insert
+            { 12 } { \@@_end_graf: \@@_end_simple_group: } % vcenter
+            { 13 } { \@@_end_math_choice_group: } % math_choice
+          }
       }
       { % bottom_level, semi_simple, math_shift, math_left
-        \@@_back_input:
         \l_@@_head_token
         \@@_print_action:
       }
@@ -4047,8 +4410,6 @@
 \cs_new_protected_nopar:Npn \@@_end_simple_group:
   {
     \l_@@_head_token
-    \gtl_gconcat:NNN \g_@@_output_gtl
-      \g_@@_output_gtl \c_group_end_gtl
     \@@_print_action:
   }
 %    \end{macrocode}
@@ -4062,13 +4423,17 @@
 \cs_new_protected_nopar:Npn \@@_end_box_group:
   {
     \seq_pop:NN \l_@@_leaders_box_seq \l_@@_tmpa_tl
-    \str_if_eq_x:nnTF \l_@@_tmpa_tl { Z }
+    \exp_args:No \@@_end_box_group_aux:n { \l_@@_tmpa_tl }
+  }
+\cs_new_protected:Npn \@@_end_box_group_aux:n #1
+  {
+    \str_if_eq_x:nnTF {#1} { Z }
       { \@@_end_simple_group: }
       {
         \@@_get_x_non_relax:
         \@@_set_cmd:
         \int_compare:nNnTF \l_@@_head_cmd_int
-          = { \@@_tex_use:n { \l_@@_tmpa_tl skip } }
+          = { \@@_tex_use:n { #1 skip } }
           {
             \tl_put_left:Nn \l_@@_head_tl { \c_group_end_token }
             \@@_do_append_glue:
@@ -4191,6 +4556,7 @@
     \mode_if_inner:TF
       {
         \gtl_gput_right:NV \g_@@_output_gtl \l_@@_head_tl
+        \@@_back_input_gtl:N \l_@@_after_group_gtl
         $ % $
       }
       {
@@ -4204,6 +4570,7 @@
             \msg_error:nn { unravel } { missing-dollar }
           }
         \gtl_gput_right:NV \g_@@_output_gtl \l_@@_head_tl
+        \@@_back_input_gtl:N \l_@@_after_group_gtl
         $ $
       }
     \@@_print_action:
@@ -4221,7 +4588,7 @@
   {
     \@@_set_action_text:
     \bool_if:NT \g_@@_internal_debug_bool
-      { \iow_term:x { Cmd:~\int_use:N \l_@@_head_cmd_int } }
+      { \iow_term:x { Cmd:~\int_to_arabic:n { \l_@@_head_cmd_int } } }
     \cs_if_exist_use:cF
       { @@_cmd_ \int_use:N \l_@@_head_cmd_int : }
       { \msg_error:nnx { unravel } { internal } { unknown-command } }
@@ -4267,7 +4634,7 @@
 %    \end{macrocode}
 %
 % Begin-group characters are sent to the output, as their grouping
-% behaviour may affect the scope of font changes, for instance.  The are
+% behaviour may affect the scope of font changes, for instance.  They are
 % also performed.
 %    \begin{macrocode}
 \@@_new_tex_cmd:nn { begin-group_char }                     % 1
@@ -4276,12 +4643,13 @@
       \g_@@_output_gtl \c_group_begin_gtl
     \@@_print_action:
     \l_@@_head_token
+    \gtl_clear:N \l_@@_after_group_gtl
   }
 %    \end{macrocode}
 %
 %    \begin{macrocode}
 \@@_new_tex_cmd:nn { end-group_char }                       % 2
-  { \@@_handle_left_brace: }
+  { \@@_handle_right_brace: }
 %    \end{macrocode}
 %
 % Math shift characters quit vertical mode, and start math mode.
@@ -4436,6 +4804,7 @@
   }
 %    \end{macrocode}
 %
+% ^^A todo: \dump only at bottom-level (\currentgrouptype=0)
 %    \begin{macrocode}
 \@@_new_tex_cmd:nn { stop }                                 % 14
   {
@@ -4865,9 +5234,33 @@
   }
 %    \end{macrocode}
 %
+% Save the next token at the end of \cs{l_@@_after_group_gtl}, unless
+% we are at the bottom group level, in which case, the token is ignored
+% completely.
 %    \begin{macrocode}
 \@@_new_tex_cmd:nn { after_group }                          % 41
-  { \msg_error:nnx { unravel } { not-implemented } { aftergroup } }
+  {
+    \tl_set_eq:NN \l_@@_tmpa_tl \l_@@_head_tl
+    \@@_get_next:
+    \int_compare:nNnTF \@@_currentgrouptype: = \c_zero
+      {
+        \@@_print_action:x
+          {
+            Aftergroup~(level~0~=>~dropped):~
+            \tl_to_str:N \l_@@_tmpa_tl
+            \gtl_to_str:N \l_@@_head_gtl
+          }
+      }
+      {
+        \gtl_concat:NNN \l_@@_after_group_gtl
+          \l_@@_after_group_gtl \l_@@_head_gtl
+        \@@_print_action:x
+          {
+            Aftergroup:~\tl_to_str:N \l_@@_tmpa_tl
+            \gtl_to_str:N \l_@@_head_gtl
+          }
+      }
+  }
 %    \end{macrocode}
 %
 % See \cs{@@_do_append_glue:}.
@@ -5124,19 +5517,20 @@
   }
 %    \end{macrocode}
 %
-% ^^A todo: take \aftergroup into account
 %    \begin{macrocode}
 \@@_new_tex_cmd:nn { begin_group }                          % 61
   {
     \gtl_gput_right:NV \g_@@_output_gtl \l_@@_head_tl
-    \@@_print_action:
     \l_@@_head_token
+    \gtl_clear:N \l_@@_after_group_gtl
+    \@@_print_action:
   }
 \@@_new_tex_cmd:nn { end_group }                            % 62
   {
     \gtl_gput_right:NV \g_@@_output_gtl \l_@@_head_tl
-    \@@_print_action:
+    \@@_back_input_gtl:N \l_@@_after_group_gtl
     \l_@@_head_token
+    \@@_print_action:
   }
 %    \end{macrocode}
 %
@@ -5245,24 +5639,24 @@
           }
         { 6 } % pdfliteral
           {
-            \@@_scan_keyword:nF { dD iI rR eE cC tT }
-              { \@@_scan_keyword:n { pP aA gG eE } }
+            \@@_scan_keyword:nF { dDiIrReEcCtT }
+              { \@@_scan_keyword:n { pPaAgGeE } }
             \@@_scan_pdf_ext_toks:
           }
         { 7 } % pdfobj
           {
             \@@_scan_keyword:nTF
-              { rR eE sS eE rR vV eE oO bB jJ nN uU mM }
+              { rReEsSeErRvVeEoObBjJnNuUmM }
               { \@@_skip_optional_space: }
               {
-                \@@_scan_keyword:nF { uU sS eE oO bB jJ nN uU mM }
+                \@@_scan_keyword:nF { uUsSeEoObBjJnNuUmM }
                   { \@@_scan_int: }
-                \@@_scan_keyword:nT { sS tT rR eE aA mM }
+                \@@_scan_keyword:nT { sStTrReEaAmM }
                   {
-                    \@@_scan_keyword:nT { aA tT tT rR }
+                    \@@_scan_keyword:nT { aAtTtTrR }
                       { \@@_scan_pdf_ext_toks: }
                   }
-                \@@_scan_keyword:n { fF iI lL eE }
+                \@@_scan_keyword:n { fFiIlLeE }
                 \@@_scan_pdf_ext_toks:
               }
           }
@@ -5270,9 +5664,9 @@
           { \@@_scan_int: }
         { 9 } % pdfxform
           {
-            \@@_scan_keyword:nT { aA tT tT rR }
+            \@@_scan_keyword:nT { aAtTtTrR }
               { \@@_scan_pdf_ext_toks: }
-            \@@_scan_keyword:nTF { rR eE sS oO uU rR cC eE sS }
+            \@@_scan_keyword:nTF { rReEsSoOuUrRcCeEsS }
               { \@@_scan_pdf_ext_toks: }
             \@@_scan_int:
           }
@@ -5285,10 +5679,10 @@
         { 13 } % pdfannot
           {
             \@@_scan_keyword:nTF
-              { rR eE sS eE rR vV eE oO bB jJ nN uU mM }
+              { rReEsSeErRvVeEoObBjJnNuUmM }
               { \@@_scan_optional_space: }
               {
-                \@@_scan_keyword:nT { uU sS eE oO bB jJ nN uU mM }
+                \@@_scan_keyword:nT { uUsSeEoObBjJnNuUmM }
                   { \@@_scan_int: }
                 \@@_scan_alt_rule:
                 \@@_scan_pdf_ext_toks:
@@ -5310,10 +5704,10 @@
           }
         { 16 } % pdfoutline
           {
-            \@@_scan_keyword:nT { aA tT tT rR }
+            \@@_scan_keyword:nT { aAtTtTrR }
               { \@@_scan_pdf_ext_toks: }
             \@@_scan_action:
-            \@@_scan_keyword:nT { cC oO uU nN tT }
+            \@@_scan_keyword:nT { cCoOuUnNtT }
               { \@@_scan_int: }
             \@@_scan_pdf_ext_toks:
           }
@@ -5332,7 +5726,7 @@
         { 23 } % pdfcatalog
           {
             \@@_scan_pdf_ext_toks:
-            \@@_scan_keyword:n { oO pP eE nN aA cC tT iI oO nN }
+            \@@_scan_keyword:n { oOpPeEnNaAcCtTiIoOnN }
               { \@@_scan_action: }
           }
         { 24 } % pdfnames
@@ -5422,7 +5816,7 @@
 \cs_new_protected_nopar:Npn \@@_scan_rule_attr:
   {
     \@@_scan_alt_rule:
-    \@@_scan_keyword:nT { aA tT tT rR }
+    \@@_scan_keyword:nT { aAtTtTrR }
       { \@@_scan_pdf_ext_toks: }
   }
 %    \end{macrocode}
@@ -5839,7 +6233,7 @@
     \@@_scan_keyword:nTF { aAtT }
       { \@@_scan_normal_dimen: }
       {
-        \@@_scan_keyword:nT { sS cC aA lL eE dD }
+        \@@_scan_keyword:nT { sScCaAlLeEdD }
           { \@@_scan_int: }
       }
     \bool_gset_false:N \g_@@_name_in_progress_bool
@@ -5910,21 +6304,55 @@
   }
 %    \end{macrocode}
 %
+% \begin{macro}[aux, EXP]{\@@_read_to_cs_safe:nTF, \@@_read_to_cs_safe:fTF}
+%   After \tn{read} or \tn{readline}, find an int, the mandatory
+%   keyword |to|, and an assignable token.  The \tn{read} and
+%   \tn{readline} primitives throw a fatal error in \tn{nonstopmode}
+%   and in \tn{batchmode} when trying to read from a stream that is
+%   outside $[0,15]$ or that is not open (according to \tn{ifeof}).  We
+%   detect this situation using \cs{@@_read_to_cs_safe:nTF} after
+%   grabbing all arguments of the primitives.  If reading is unsafe,
+%   let the user know that \TeX{} would have thrown a fatal error.
 %    \begin{macrocode}
 \@@_prefixed_new:nn { read_to_cs }                          % 96
   {
-    \@@_prev_input:V \l_@@_head_tl
+    \@@_prev_input_silent:V \l_@@_head_tl
     \@@_print_action:x { \tl_to_str:N \l_@@_head_tl }
     \@@_scan_int:
-    \@@_scan_keyword:nF { tToO }
+    \@@_scan_to:
+    \@@_scan_r_token:
+    \seq_get_right:NN \g_@@_prev_input_seq \l_@@_tmpa_tl
+    \@@_read_to_cs_safe:fTF
+      { \@@_tl_first_int:N \l_@@_tmpa_tl }
+      { \@@_assign_token:n { } }
       {
-        \msg_error:nn { unravel } { missing-to }
-        \@@_prev_input:n { to }
+        \seq_gpop_right:NN \g_@@_prev_input_seq \l_@@_head_tl
+        \@@_tex_fatal_error:nV { cannot-read } \l_@@_head_tl
+      }
+  }
+\prg_new_conditional:Npnn \@@_read_to_cs_safe:n #1 { TF }
+  {
+    \int_compare:nNnTF { \etex_interactionmode:D } > { 1 }
+      { \prg_return_true: }
+      {
+        \int_compare:nNnTF {#1} < { 0 }
+          { \prg_return_false: }
+          {
+            \int_compare:nNnTF {#1} > { 15 }
+              { \prg_return_false: }
+              {
+                \tex_ifeof:D #1 \exp_stop_f:
+                  \prg_return_false:
+                \else:
+                  \prg_return_true:
+                \fi:
+              }
+          }
       }
-    \@@_scan_r_token:
-    \@@_assign_token:n { }
   }
+\cs_generate_variant:Nn \@@_read_to_cs_safe:nTF { f }
 %    \end{macrocode}
+% \end{macro}
 %
 %    \begin{macrocode}
 \@@_prefixed_new:nn { def }                                 % 97
@@ -6312,8 +6740,8 @@
     \@@_scan_file_name:
     \seq_gpop_right:NN \g_@@_prev_input_seq \l_@@_head_tl
     \tl_set:Nx \l_@@_tmpa_tl { \tl_tail:N \l_@@_head_tl }
-    \@@_tl_gset_input:Nno \g_@@_tmpc_tl { } \l_@@_tmpa_tl
-    \@@_back_input:V \g_@@_tmpc_tl
+    \@@_file_get:nN \l_@@_tmpa_tl \l_@@_tmpa_tl
+    \@@_back_input:V \l_@@_tmpa_tl
     \@@_print_action:x { \tl_to_str:N \l_@@_head_tl }
   }
 %    \end{macrocode}
@@ -6380,7 +6808,7 @@
         { 23 } \@@_scan_pdf_ext_toks:
         { 24 }
           {
-            \@@_scan_keyword:n { fF iI lL eE }
+            \@@_scan_keyword:n { fFiIlLeE }
             \@@_scan_pdf_ext_toks:
           }
         { 25 } \@@_scan_pdffiledump:
@@ -6402,23 +6830,23 @@
   { \@@_scan_int: \@@_scan_int: }
 \cs_new_protected_nopar:Npn \@@_scan_pdfcolorstackinit:
   {
-    \@@_scan_keyword:nTF { pP aA gG eE }
+    \@@_scan_keyword:nTF { pPaAgGeE }
       { \bool_set_true:N \l_@@_tmpa_bool }
       { \bool_set_false:N \l_@@_tmpb_bool }
-    \@@_scan_keyword:nF { dD iI rR eE cC tT }
-      { \@@_scan_keyword:n { pP aA gG eE } }
+    \@@_scan_keyword:nF { dDiIrReEcCtT }
+      { \@@_scan_keyword:n { pPaAgGeE } }
     \@@_scan_toks_to_str:
   }
 \cs_new_protected_nopar:Npn \@@_scan_pdffiledump:
   {
-    \@@_scan_keyword:nT { oO fF fF sS eE tT } \@@_scan_int:
-    \@@_scan_keyword:nT { lL eE nN gG tT hH } \@@_scan_int:
+    \@@_scan_keyword:nT { oOfFfFsSeEtT } \@@_scan_int:
+    \@@_scan_keyword:nT { lLeEnNgGtThH } \@@_scan_int:
     \@@_scan_pdf_ext_toks:
   }
 \cs_new_protected_nopar:Npn \@@_scan_pdfmatch:
   {
-    \@@_scan_keyword:n { iI cC aA sS eE }
-    \@@_scan_keyword:nT { sS uU bB cC oO uU nN tT }
+    \@@_scan_keyword:n { iIcCaAsSeE }
+    \@@_scan_keyword:nT { sSuUbBcCoOuUnNtT }
       { \@@_scan_int: }
     \@@_scan_pdf_ext_toks:
     \@@_scan_pdf_ext_toks:
@@ -7040,7 +7468,7 @@
       {
         [=====
         \bool_if:NT \g_@@_number_steps_bool
-          { ~ Step ~ \int_use:N \g_@@_step_int \ }
+          { ~ Step ~ \int_to_arabic:n { \g_@@_step_int } ~ }
         =====]~
         \int_compare:nNnTF
           { \str_count:N \g_@@_action_text_str }
@@ -7091,12 +7519,15 @@
 \cs_new_protected_nopar:Npn \@@_print_assigned_register:
   {
     \@@_after_assignment: % ^^A todo: simplify
-    \@@_print_action:x
+    \exp_args:Nx \@@_print_action:x
       {
-        Set~ \exp_after:wN \token_to_str:N \l_@@_defined_tl
-        \tl_if_single:NT \l_@@_defined_tl
-          { ( \exp_after:wN \token_to_meaning:N \l_@@_defined_tl ) }
-        = \exp_after:wN \@@_the:w \l_@@_defined_tl
+        \exp_not:n
+          {
+            Set~ \exp_after:wN \token_to_str:N \l_@@_defined_tl
+            \tl_if_single:NT \l_@@_defined_tl
+              { ( \exp_after:wN \token_to_meaning:N \l_@@_defined_tl ) }
+          }
+        = \exp_not:N \tl_to_str:n { \@@_the:w \l_@@_defined_tl }
       }
     \@@_omit_after_assignment:w
   }
@@ -7287,6 +7718,50 @@
 %
 % ^^A todo: I suspect a bug if given 'a' twice in a row
 %
+% \subsubsection{Errors}
+%
+% \begin{macro}[int]{\@@_tex_msg_new:nnn}
+%   This stores a \TeX{} error message.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_tex_msg_new:nnn #1#2#3
+  {
+    \cs_new_nopar:cpn { @@_tex_msg_error_#1: } {#2}
+    \cs_new_nopar:cpn { @@_tex_msg_help_#1: } {#3}
+  }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{\@@_tex_error:nn, \@@_tex_error:nV}
+%   Throw the |tex-error| message, with arguments: |#2| which triggered
+%   the error, \TeX{}'s error message, and \TeX{}'s help text.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_tex_error:nn #1#2
+  {
+    \msg_error:nnxxx { unravel } { tex-error }
+      { \tl_to_str:n {#2} }
+      { \use:c { @@_tex_msg_error_#1: } }
+      { \use:c { @@_tex_msg_help_#1: } }
+  }
+\cs_generate_variant:Nn \@@_tex_error:nn { nV }
+%    \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[int]{\@@_tex_fatal_error:nn, \@@_tex_fatal_error:nV}
+%   Throw the |tex-fatal| error message, with arguments: |#2| which
+%   triggered the fatal error, \TeX{}'s error message, and \TeX{}'s
+%   help text.
+%    \begin{macrocode}
+\cs_new_protected:Npn \@@_tex_fatal_error:nn #1#2
+  {
+    \msg_error:nnxxx { unravel } { tex-fatal }
+      { \tl_to_str:n {#2} }
+      { \use:c { @@_tex_msg_error_#1: } }
+      { \use:c { @@_tex_msg_help_#1: } }
+  }
+\cs_generate_variant:Nn \@@_tex_fatal_error:nn { nV }
+%    \end{macrocode}
+% \end{macro}
+%
 % \subsection{Keys}
 %
 % Each key needs to be defined twice: for its default setting and for
@@ -7426,6 +7901,7 @@
     \gtl_gclear:N \g_@@_after_assignment_gtl
     \bool_gset_true:N  \g_@@_set_box_allowed_bool
     \bool_gset_false:N \g_@@_name_in_progress_bool
+    \gtl_clear:N \l_@@_after_group_gtl
   }
 %    \end{macrocode}
 % \end{macro}
@@ -7486,8 +7962,6 @@
   { Missing~font~identifier.~\iow_char:N\\nullfont~inserted. }
 \msg_new:nnn { unravel } { missing-rparen }
   { Missing~right~parenthesis~inserted~for~expression. }
-\msg_new:nnn { unravel } { incompatible-units }
-  { Mu~glue/dimen~used~as~a~normal~glue/dimen~or~vice-versa. }
 \msg_new:nnn { unravel } { missing-mudim }
   { Missing~mu~unit. }
 \msg_new:nnn { unravel } { missing-cs }
@@ -7544,6 +8018,63 @@
   { The~following~feature~is~not~implemented:~'#1'. }
 \msg_new:nnn { unravel } { endinput-ignored }
   { The~primitive~\iow_char:N\\endinput~was~ignored. }
+\msg_new:nnn { unravel } { missing-something }
+  { Something~is~missing,~sorry! }
+\msg_new:nnnn { unravel } { tex-error }
+  { TeX~sees~"#1"~and~throws~an~error:\\\\ \iow_indent:n {#2} }
+  {
+    \tl_if_empty:nTF {#3}
+      { TeX~provides~no~further~help~for~this~error. }
+      { TeX's~advice~is:\\\\ \iow_indent:n {#3} }
+  }
+\msg_new:nnnn { unravel } { tex-fatal }
+  { TeX~sees~"#1"~and~throws~a~fatal~error:\\\\ \iow_indent:n {#2} }
+  {
+    \tl_if_empty:nTF {#3}
+      { TeX~provides~no~further~help~for~this~error. }
+      { TeX's~advice~is:\\\\ \iow_indent:n {#3} }
+  }
+%    \end{macrocode}
+%
+% Some error messages from \TeX{} itself.
+%    \begin{macrocode}
+\@@_tex_msg_new:nnn { incompatible-mag }
+  {
+    Incompatible~magnification~
+    ( \int_to_arabic:n { \@@_mag: } ); \\
+    \ the~previous~value~will~be~retained
+  }
+  {
+    I~can~handle~only~one~magnification~ratio~per~job.~So~I've\\
+    reverted~to~the~magnification~you~used~earlier~on~this~run.
+  }
+\@@_tex_msg_new:nnn { illegal-mag }
+  {
+    Illegal~magnification~has~been~changed~to~1000~
+    ( \int_to_arabic:n { \@@_mag: } )
+  }
+  { The~magnification~ratio~must~be~between~1~and~32768. }
+\@@_tex_msg_new:nnn { incompatible-units }
+  { Incompatible~glue~units }
+  { I'm~going~to~assume~that~1mu=1pt~when~they're~mixed. }
+%    \end{macrocode}
+%
+% Fatal \TeX{} error messages.
+%    \begin{macrocode}
+\@@_tex_msg_new:nnn { cannot-read }
+  { ***~(cannot~\iow_char:N\\read~from~terminal~in~nonstop~modes) }
+  { }
+\@@_tex_msg_new:nnn { file-error }
+  { ***~(job~aborted,~file~error~in~nonstop~mode) }
+  { }
+\@@_tex_msg_new:nnn { interwoven-preambles }
+  { (interwoven~alignment~preambles~are~not~allowed) }
+  { }
+%    \end{macrocode}
+%
+% Restore catcodes to their original values.
+%    \begin{macrocode}
+\@@_setup_restore:
 %    \end{macrocode}
 %
 %    \begin{macrocode}
-- 
cgit v1.2.3

