% \iffalse meta-comment
%
%% File: primargs.dtx Copyright (C) 2012-2024 Bruno Le Floch
%%
%% It may be distributed and/or modified under the conditions of the
%% LaTeX Project Public License (LPPL), either version 1.3c of this
%% license or (at your option) any later version.  The latest version
%% of this license is in the file
%%
%%    http://www.latex-project.org/lppl.txt
%%
%% This work has the LPPL maintenance status 'maintained'
%% and the current maintainer is Bruno Le Floch.
%% -----------------------------------------------------------------------
%
%<*driver>
\documentclass[full]{l3doc}
\newcommand{\proc}[1]{\texttt{#1}}
\begin{document}
  \DocInput{\jobname.dtx}
\end{document}
%</driver>
% \fi
%
% \title{The \textsf{primargs} package: \\
%   Parsing arguments of primitives}
% \author{Bruno Le Floch}
% \date{2024/02/02}
%
% \maketitle
% \tableofcontents
%
% \begin{documentation}
%
% \section{\pkg{primargs} documentation}
%
% This \TeX{} and \LaTeX{} package is currently used by \pkg{morewrites}
% when redefining primitives: it allows to read arguments of primitives
% in place of \TeX{}, which is useful to add hooks to primitives.  Of
% course, this is much slower than letting \TeX{} do things directly.
%
% All assignments done by this package are global.  While a negative
% value of the \tn{globaldefs} (primitive) parameter normally makes all
% assignments local, this package makes sure \tn{globaldefs} is
% non-negative before assignments.
%
% \subsection{Reading one token without removing it}
%
% \begin{variable}[tested = primargs001]{\g_primargs_token}
%   The token read by \cs{primargs_read_token:N} or
%   \cs{primargs_read_x_token:N}.  Its value is always set globally.
%   It can be an \tn{outer} macro.
% \end{variable}
%
% \begin{function}[tested = primargs001]{\primargs_read_token:N}
%   \begin{syntax}
%     \cs{primargs_read_token:N} \meta{function}
%   \end{syntax}
%   Sets \cs{g_primargs_token} equal to the token following the
%   \meta{function}, then calls the \meta{function}.  The token
%   following the \meta{function} is not removed.
%   \begin{texnote}
%     This is essentially \tn{global} \tn{futurelet}
%     \cs{g_primargs_token} \meta{function}, with the added guarantee
%     that the assignment is global even when $\tn{globaldefs}$ is negative.
%   \end{texnote}
% \end{function}
%
% \begin{function}[tested = primargs001]{\primargs_read_x_token:N}
%   \begin{syntax}
%     \cs{primargs_read_x_token:N} \meta{function}
%   \end{syntax}
%   Expands tokens recursively with \cs{exp_after:wN} until encountering
%   a non-expandable token and afterwards calls the \meta{function}.
%   The non-expandable token following the \meta{function} is not
%   removed and \cs{g_primargs_token} is also set (globally) equal to
%   that token.
% \end{function}
%
% \subsection{Removing tokens}
%
% \begin{function}[tested = primargs005]{\primargs_remove_token:N}
%   \begin{syntax}
%     \cs{primargs_remove_token:N} \meta{function}
%   \end{syntax}
%   Removes the \meta{token} which follows the \meta{function}, then
%   calls the \meta{function}.  This also sets \cs{g_primargs_token}
%   (globally) equal to the removed token.
% \end{function}
%
% \begin{function}[tested = primargs005]{\primargs_remove_one_optional_space:N}
%   \begin{syntax}
%     \cs{primargs_remove_one_optional_space:N} \meta{function}
%   \end{syntax}
%   Expands tokens following the \meta{function} until a non-expandable
%   token is found, and sets \cs{g_primargs_token} (globally) equal to this token,
%   then removes the token if it has catcode~$10$ (space).  Finally,
%   call the \meta{function}.
% \end{function}
%
% \begin{function}[tested = primargs005]{\primargs_remove_optional_spaces:N}
%   \begin{syntax}
%     \cs{primargs_remove_optional_spaces:N} \meta{function}
%   \end{syntax}
%   Expands tokens following the \meta{function}, removing any token
%   with catcode~$10$ (space), then sets \cs{g_primargs_token} (globally) equal to
%   the first non-space token and calls the \meta{function}.
% \end{function}
%
% \begin{function}[tested = primargs005]{\primargs_remove_equals:N}
%   \begin{syntax}
%     \cs{primargs_remove_equals:N} \meta{function}
%   \end{syntax}
%   Expands tokens following the \meta{function}, removing any token
%   with catcode~$10$ (space), then sets \cs{g_primargs_token} (globally) equal to
%   the first non-space token.  If this token is an explicit~|=|
%   character token with catcode~$12$ (other), then it is removed as
%   well.  Finally, calls the \meta{function}.
% \end{function}
%
% \begin{function}[added = 2014-08-06, tested = primargs005]{\primargs_remove_filler:N}
%   \begin{syntax}
%     \cs{primargs_remove_filler:N} \meta{function}
%   \end{syntax}
%   Expands tokens following the \meta{function}, removing any token
%   with catcode~$10$ (space) or equal to \tn{relax}, then sets
%   \cs{g_primargs_token} (globally) equal to the next
%   token.  Finally, calls the \meta{function}.
% \end{function}
%
% \subsection{Grabbing arguments}
%
% \begin{function}[tested = primargs002]
%   {
%     \primargs_get_number:N,
%     \primargs_get_dimen:N,
%     \primargs_get_glue:N,
%     \primargs_get_mudimen:N,
%     \primargs_get_muglue:N,
%   }
%   \begin{syntax}
%     \cs{primargs_get_number:N} \meta{function}
%   \end{syntax}
%   Reads a number/dimension/glue/math dimension/math glue following the
%   \meta{function}, then calls the \meta{function} with a braced
%   argument containing the value found.  For instance,
%   \begin{verbatim}
%     \primargs_get_glue:N \test 3sp plus \numexpr 2-3 fill X
%   \end{verbatim}
%   yields
%   \begin{verbatim}
%     \test {3sp plus -1fill}X
%   \end{verbatim}
%   A word of warning: the \cs{primargs_get_mudimen:N} function
%   currently parses a \meta{muskip} instead of a \meta{mudimen}.
% \end{function}
%
% \begin{function}[updated = 2014-08-06, tested = primargs003]
%   {\primargs_get_general_text:N}
%   \begin{syntax}
%     \cs{primargs_get_general_text:N} \meta{function}
%   \end{syntax}
%   Finds what \TeX{}'s grammar calls a \meta{general text} (that is, a
%   \meta{filler}, a catcode~$1$ token, a \meta{balanced text}, and an
%   explicit catcode~$2$ token) following the \meta{function}, and calls
%   the \meta{function} with the \meta{balanced text} as a braced
%   argument.
% \end{function}
%
% \begin{function}[updated = 2024-01-05, tested = primargs004]{\primargs_get_file_name:N, \primargs_get_input_file_name:N}
%   \begin{syntax}
%     \cs{primargs_get_file_name:N} \meta{function}
%     \cs{primargs_get_input_file_name:N} \meta{function}
%   \end{syntax}
%   Reads a \meta{file name} following the \meta{function} and calls the
%   \meta{function} with this \meta{file name} as a braced argument.
%   The first function only allows for the historical unbraced file
%   names that plain \TeX{} supports.  The second one also allows braced
%   file names.  Historically this was first supported in \LuaTeX{} for
%   \tn{input} and related primitives, hence the name.  Now all main
%   engines (in \TeX{}Live at least) support both syntaxes for all
%   primitives that take file names.
%   \begin{texnote}
%     When braced file names are disallowed, the file name is obtained
%     by discarding \meta{optional spaces} then repeatedly doing the
%     following.  Fully expand what follows in the input stream.  If
%     the next token is an explicit or implicit character token
%     (regardless of its catcode) then add that character to the
%     file name and remove it from the input stream, and go back to
%     expanding tokens, except in one case: if the character code
%     is~$32$ (space) and the number of quote characters (code~$34$)
%     already in the file name is even, then the space is removed from
%     the input stream, not included in the file name, and parsing
%     ends.  Finally, if the next token is a non-expandable command
%     (be it a control sequence or an active character) then the file
%     name ends and the command is left in the input stream.
%
%     When braced file names are allowed, the following steps are added
%     prior to the procedure above.  First remove a \meta{filler}.  If
%     the next token is of catcode~$1$ then fully expand tokens one by
%     one and add their string representation (with \cs{tl_to_str:n},
%     not \cs{token_to_str:N}) to the file name.
%   \end{texnote}
% \end{function}
%
% \subsection{Comments}
%
% This package is not idiomatic \pkg{expl3} and should not be used as an
% example of good coding practices.  It uses \cs[no-index]{\ldots{}:D}
% primitives directly:
% \begin{itemize}
% \item to cope with \tn{outer} tokens, since this package is meant to
%   be used quite broadly;
% \item for primitives with (rightfully) no \pkg{expl3} interface (or a
%   slightly incomplete interface), namely \tn{afterassignment},
%   \tn{globaldefs}, \tn{aftergroup}, \tn{the}, \tn{deadcycles},
%   \tn{hoffset}, \tn{topskip}, \tn{thinmuskip}, \tn{unexpanded};
% \item to test that a token's meaning is a given primitive when the
%   \pkg{expl3} interface is not (or not obviously) a copy of the primitive.
% \end{itemize}
% As a result, \emph{do not take this package as an example of how to
% code with \pkg{expl3}; go and see Joseph Wright's \pkg{siunitx} for
% instance.}
%
% Despite large efforts expended to make this package robust against
% changes to the \tn{globaldefs} parameter, setting it to a non-zero
% value may make some parts of this package crash.
%
% Tokens inserted using \tn{afterassignment} may be lost when using this
% package, since it uses \tn{afterassignment} internally.
%
% Todo list.
% \begin{itemize}
% \item Test all functions within alignments and understand their
%   interaction with the master counter.
% \item Correct the parsing of \meta{mudimen}.
% \item Perhaps parse \meta{muglue} and \meta{glue} by hand to avoid bad
%   interactions with \tn{globaldefs}.  Otherwise put up a warning about
%   \tn{globaldefs} when relevant.  Better partial fix: declare a skip and a muskip.
% \item Write tests of engine behaviour, especially \LuaTeX{}'s \tn{input},
%   \tn{openin}, \tn{openout} including behaviour of |#| and spaces and
%   character-code-zero, to detect unexpected changes.  In
%   \tn{input}|{|\ldots{}\tn{input}\ldots{}|}|, \LuaTeX{} expands the
%   inner \tn{input} but uses the inner file name as the outer file name.
% \end{itemize}
%
% \end{documentation}
%
% \begin{implementation}
%
% \section{\pkg{primargs} implementation}
%
%<*package>
%    \begin{macrocode}
\ProvidesExplPackage
  {primargs} {2024/02/02} {} {Parsing arguments of primitives}
%    \end{macrocode}
%
%    \begin{macrocode}
%<@@=primargs>
%    \end{macrocode}
%
% \begin{function}{\@@_get_rhs:NnN, \@@_get_rhs:NoN}
%   \begin{syntax}
%     \cs{@@_get_rhs:NnN} \meta{register} \Arg{register rhs} \meta{function}
%   \end{syntax}
%   Use the \meta{register} to find a right-hand side of a valid
%   assignment for this type of variable, and feed the value found to
%   the \meta{function}.  The value of the \meta{register} is then
%   restored using \meta{register} |=| \meta{register rhs}, where the
%   \meta{register rhs} should be the initial value of the
%   \meta{register}.  All those assignments are performed within a
%   group, but some are automatically global, and \tn{globaldefs} may
%   cause trouble with others.
% \end{function}
%
% \subsection{Variables and helpers}
%
% \begin{macro}{\g_@@_code_tl}
%   Used to contain temporary code.
%    \begin{macrocode}
\tl_new:N \g_@@_code_tl
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\g_@@_file_name_tl, \g_@@_file_name_level_tl}
%   Token list used to build a file name, one character at a time.
%   Token list holding the level of nesting in quotes or braces.
%    \begin{macrocode}
\tl_new:N \g_@@_file_name_tl
\tl_new:N \g_@@_file_name_level_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_safe:}
%   This function, which must be called in a group, cancels any
%   \tn{afterassignment} token and makes the \tn{globaldefs} parameter
%   non-negative.  This ensures that assignments prefixed with
%   \tn{global} are indeed global.  When \tn{globaldefs} is positive,
%   every assignment is global, and it is not possible to safely
%   (locally) set it to zero.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_safe:
  {
    \tex_afterassignment:D \tex_relax:D
    \if_int_compare:w 0 > \tex_globaldefs:D
      \int_zero:N \tex_globaldefs:D
    \fi:
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Read token with or without expansion}
%
% \TeX{} often calls the \proc{get_x_token} procedure when parsing
% various parts of its grammar.  This expands tokens recursively until
% reaching a non-expandable token.  We emulate this by reading the next
% token with \tn{futurelet}, checking whether it is expandable or not by
% comparing its meaning to its meaning when acted upon by \tn{noexpand},
% and expanding it with \tn{expandafter} if it is expandable.
%
% One thing to be careful about is that
% \begin{quote}
%   \tn{expandafter} \tn{show} \tn{noexpand} \tn{space}
% \end{quote}
% shows the \tn{meaning} of the |\notexpanded: \space|,
% namely \tn{relax} (frozen, in fact, hence a bit different from the
% normal \tn{relax}), while expanding twice with
% \begin{quote}
%   \tn{expandafter} \tn{expandafter} \tn{expandafter} \tn{show}
%   \tn{noexpand} \tn{space}
% \end{quote}
% expands the \tn{space} to the underlying space character token.
% What this means is that we must first check if the token is expandable
% or not, and only then expand, and that the token should not be queried
% again using \tn{futurelet}.  On this latter point, run
% \begin{verbatim}
%   \def \test { \show \next \futurelet \next \test }
%   \expandafter \test \noexpand \space
% \end{verbatim}
% to see how \tn{next} changes from \tn{relax} to becoming a macro.
%
% \begin{macro}{\primargs_read_x_token:N}
% \begin{macro}
%   {
%     \@@_read_x_token:N, \@@_read_x_token_aux:N,
%     \@@_read_x_token_std:N, \@@_read_x_token_file:N
%   }
%   This is a bit messy, because we need to support the fact that
%   \TeX{} does not consider \tn{input} as expandable when it is
%   looking for a file name.  This variation is encapsulated by
%   letting \cs{@@_read_x_token_aux:N} equal to either a standard
%   (\texttt{std}) version or a version specific to file names
%   (\texttt{file}).
%
%   First query the following token.  Then test whether it is
%   expandable, using a variant of the \cs{token_if_expandable:NTF}
%   test.\footnote{This \LaTeX3 test returns \texttt{false} for
%     undefined tokens (by design), but \TeX{}'s \proc{get_x_token}
%     expands those undefined tokens, causing errors, so we should as
%     well.} If the token is expandable, \cs{exp_not:N} will change its
%   \tn{meaning} to \tn{relax}, the test is \texttt{false}, we expand,
%   and call the loop.  Otherwise, we stop.  In the \texttt{file}
%   version there is an extra test for \cs{tex_input:D}.  By default
%   use the standard version.
%    \begin{macrocode}
\cs_new_protected:Npn \primargs_read_x_token:N
  {
    \group_begin:
      \@@_safe:
      \@@_read_x_token:N
  }
\cs_new_protected:Npn \@@_read_x_token:N
  {
    \tex_afterassignment:D \@@_read_x_token_aux:N
    \tex_global:D \tex_futurelet:D \g_primargs_token
  }
\cs_new_protected:Npn \@@_read_x_token_std:N
  {
    \exp_after:wN
    \if_meaning:w \exp_not:N \g_primargs_token \g_primargs_token
      \group_end: \use_i:nnnn
    \fi:
    \exp_after:wN \@@_read_x_token:N \exp_after:wN
  }
\cs_new_eq:NN \@@_read_x_token_aux:N
              \@@_read_x_token_std:N
\cs_new_protected:Npn \@@_read_x_token_file:N
  {
    \if_meaning:w \tex_input:D \g_primargs_token
      \use_i_ii:nnn \group_end:
    \fi:
    \@@_read_x_token_std:N
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\primargs_read_token:N}
%   The same without expansion, useful for instance when we already know
%   that what follows is expanded.  Interestingly, we don't ever need to
%   take the user's function as an argument.
%    \begin{macrocode}
\cs_new_protected:Npn \primargs_read_token:N
  {
    \group_begin:
      \@@_safe:
      \tex_afterassignment:D \group_end:
      \tex_global:D \tex_futurelet:D \g_primargs_token
  }
%    \end{macrocode}
% \end{macro}
%
% \subsection{Removing tokens}
%
% \begin{macro}{\primargs_remove_token:N}
%   Remove token using \tn{let} (note the presence of |=| and a space,
%   to correctly remove explicit space characters), then insert the
%   \meta{function} after closing the group.
%    \begin{macrocode}
\cs_new_protected:Npn \primargs_remove_token:N #1
  {
    \group_begin:
      \@@_safe:
      \tex_aftergroup:D #1
      \tex_afterassignment:D \group_end:
      \tex_global:D \tex_let:D \g_primargs_token = ~
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\primargs_remove_one_optional_space:N}
% \begin{macro}{\@@_remove_one_optional_space:}
%   Start a group: we will insert the \meta{function} at its end.
%    \begin{macrocode}
\cs_new_protected:Npn \primargs_remove_one_optional_space:N #1
  {
    \group_begin:
      \@@_safe:
      \tex_aftergroup:D #1
      \primargs_read_x_token:N \@@_remove_one_optional_space:
  }
\cs_new_protected:Npn \@@_remove_one_optional_space:
  {
    \if_catcode:w \c_space_token \exp_not:N \g_primargs_token
      \exp_after:wN \primargs_remove_token:N
    \fi:
    \group_end:
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\primargs_remove_optional_spaces:N}
% \begin{macro}
%   {\@@_remove_optional_spaces:, \@@_remove_optional_spaces_aux:}
%   Start a group, make assignments safe, then recursively expand tokens
%   and remove any token with catcode~$10$ (space).  Once another token
%   is found, close the group hence insert the \meta{function}~|#1|.
%    \begin{macrocode}
\cs_new_protected:Npn \primargs_remove_optional_spaces:N #1
  {
    \group_begin:
      \@@_safe:
      \tex_aftergroup:D #1
      \@@_remove_optional_spaces:
  }
\cs_new_protected:Npn \@@_remove_optional_spaces:
  { \primargs_read_x_token:N \@@_remove_optional_spaces_aux: }
\cs_new_protected:Npn \@@_remove_optional_spaces_aux:
  {
    \if_catcode:w \c_space_token \exp_not:N \g_primargs_token
      \exp_after:wN \primargs_remove_token:N
      \exp_after:wN \@@_remove_optional_spaces:
    \else:
      \exp_after:wN \group_end:
    \fi:
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\primargs_remove_equals:N}
% \begin{macro}{\@@_remove_equals:, \@@_remove_equals_aux:NN}
%   Remove \meta{optional spaces}, then test for an explicit~|=|, both
%   in \tn{meaning} and as a token list: once we know its \tn{meaning},
%   we can grab it safely.
%    \begin{macrocode}
\cs_new_protected:Npn \primargs_remove_equals:N #1
  {
    \group_begin:
      \tex_aftergroup:D #1
      \primargs_remove_optional_spaces:N \@@_remove_equals:
  }
\cs_new_protected:Npn \@@_remove_equals:
  {
      \if_meaning:w = \g_primargs_token
        \exp_after:wN \@@_remove_equals_aux:NN
      \fi:
    \group_end:
  }
\cs_new_protected:Npn \@@_remove_equals_aux:NN #1#2
  { \tl_if_eq:nnTF { #2 } { = } { #1 } { #1 #2 } }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\primargs_remove_filler:N}
% \begin{macro}
%   {
%     \@@_remove_filler:,
%     \@@_remove_filler_aux:,
%     \@@_remove_filler_end:NNNNN
%   }
%   Within a group remove a \meta{filler}, and insert the user's |#1|
%   after closing the group.  A \meta{filler} consists of tokens with
%   catcode~$10$ (space) or equal to \tn{relax} or to the
%   \enquote{frozen \tn{relax}} command.
%    \begin{macrocode}
\cs_new_protected:Npn \primargs_remove_filler:N #1
  {
    \group_begin:
      \@@_safe:
      \tex_aftergroup:D #1
      \@@_remove_filler:
  }
\cs_new_protected:Npn \@@_remove_filler:
  { \primargs_read_x_token:N \@@_remove_filler_aux: }
\cs_new_protected:Npn \@@_remove_filler_aux:
  {
    \if_catcode:w \c_space_token \exp_not:N \g_primargs_token
    \else:
      \if_meaning:w \tex_relax:D \g_primargs_token
      \else:
        \exp_after:wN
        \if_meaning:w \exp_not:N \prg_do_nothing: \g_primargs_token
        \else:
          \@@_remove_filler_end:NNNNN
        \fi:
      \fi:
    \fi:
    \primargs_remove_token:N \@@_remove_filler:
  }
\cs_new_protected:Npn \@@_remove_filler_end:NNNNN #1#2#3#4#5
  { #1 #2 #3 \group_end: }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \subsection{Right-hand sides of assignments}
%
% The naive approach to reading an integer, or a general text, is to let
% \TeX{} perform an assignment to a \tn{count}, or a \tn{toks}, register
% and regain control using \tn{afterassignment}.  The question is then
% to know which \tn{count} or \tn{toks} register to use.  One might
% think that any can be used as long as the assignment happens in a
% group.
%
% However, there comes the question of the \tn{globaldefs} parameter.
% If this parameter is positive, every assignment is global, including
% assignments to the parameter itself, preventing us from setting it to
% zero locally; hence, we are stuck with global assignments (if
% \tn{globaldefs} is negative, we can change it, locally, to whatever
% value pleases us, as done by \cs{@@_safe:}).  We may thus not
% use scratch registers to parse integers, general texts, and other
% pieces of \TeX{}'s grammar.
%
% For integers, we will use \tn{deadcycles}, a parameter which is
% automatically assigned globally, and we revert it to its previous
% value afterwards.
%
% \begin{macro}{\@@_get_rhs:NnN, \@@_get_rhs:NoN}
%   The last two lines of this function are the key: assign to |#1|,
%   then take control using \tn{afterassignment}.  After the assignment,
%   we expand the value found, |\tex_the:D #1|, within a brace group,
%   then restore |#1| using its initial value |#2|, and end the group.
%   The earlier use of \cs{tex_aftergroup:D} inserts the \meta{function}
%   |#3| before the brace group containing the value found.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_get_rhs:NnN #1#2#3
  {
    \group_begin:
      \@@_safe:
      \tex_aftergroup:D #3
      \tl_gset:Nn \g_@@_code_tl
        {
          \use:x
            {
              \exp_not:n { #1 = #2 \group_end: }
              { \tex_the:D #1 }
            }
        }
      \tex_afterassignment:D \g_@@_code_tl
      #1 =
  }
\cs_generate_variant:Nn \@@_get_rhs:NnN { No }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\primargs_get_number:N}
%   We use the general \cs{@@_get_rhs:NoN}, using the internal register
%   \tn{deadcycles}, for which all assignments are global: thus,
%   restoring its value will not interact badly with groups.
%    \begin{macrocode}
\cs_new_protected:Npn \primargs_get_number:N
  {
    \@@_get_rhs:NoN \tex_deadcycles:D
      { \tex_the:D \tex_deadcycles:D }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\primargs_get_dimen:N}
%   Use \tn{hoffset} as a register since it is not too likely to be
%   changed locally (anyways, which register we use is not that
%   important since normally, \tn{globaldefs} is zero, and everything is
%   done within a group).
%    \begin{macrocode}
\cs_new_protected:Npn \primargs_get_dimen:N
  {
    \@@_get_rhs:NoN \tex_hoffset:D
      { \tex_the:D \tex_hoffset:D }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\primargs_get_glue:N}
%   Use \tn{topskip}.
%    \begin{macrocode}
\cs_new_protected:Npn \primargs_get_glue:N
  {
    \@@_get_rhs:NoN \tex_topskip:D
      { \tex_the:D \tex_topskip:D }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\primargs_get_mudimen:N}
%   There is no such thing as a \meta{mudimen variable}, so we're on our
%   own to parse a \meta{mudimen}.  Warn about that problem, and parse a
%   \meta{muglue} instead.
%    \begin{macrocode}
\cs_new_protected:Npn \primargs_get_mudimen:N
  {
    \msg_warning:nn { primargs } { get-mudimen }
    \primargs_get_muglue:N
  }
\msg_new:nnn { primargs } { get-mudimen }
  { The~\iow_char:N\\primargs_get_mudimen:N~function~is~buggy. }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\primargs_get_muglue:N}
%   Use \tn{thinmuskip}.
%    \begin{macrocode}
\cs_new_protected:Npn \primargs_get_muglue:N
  {
    \@@_get_rhs:NoN \tex_thinmuskip:D
      { \tex_the:D \tex_thinmuskip:D }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\primargs_get_general_text:N}
% \begin{macro}
%   {\@@_get_general_text:, \@@_get_general_text_error:n}
%   Getting a \meta{general text} is more tricky, as an assignment to
%   \tn{errhelp} (for instance) would also allow constructions such as
%   |\toks0|.  Instead, we remove a \meta{filler} then test whether the
%   next token (already expanded) is a catcode~$1$ token, in which case
%   we replace it by an explicit left brace before calling the function.
%   When the next token is not of catcode~$1$, we produce an error,
%   attempting to imitate as closely as possible the \TeX{} error.
%    \begin{macrocode}
\cs_new_protected:Npn \primargs_get_general_text:N #1
  {
    \group_begin:
      \@@_safe:
      \tex_aftergroup:D #1
      \tex_aftergroup:D { \if_false: } \fi:
      \primargs_remove_filler:N \@@_get_general_text:
  }
\cs_new_protected:Npn \@@_get_general_text:
  {
    \if_catcode:w \c_group_begin_token \g_primargs_token
      \exp_after:wN \primargs_remove_token:N
    \else:
      \group_begin:
        \tex_aftergroup:D \@@_get_general_text_error:n
        \if_catcode:w \c_group_end_token \g_primargs_token
          \tex_aftergroup:D {
          \tex_aftergroup:D }
        \fi:
    \fi:
    \group_end:
  }
\cs_new_protected:Npn \@@_get_general_text_error:n #1
  {
    \exp_after:wN \group_end:
    \tex_unexpanded:D \if_int_compare:w `{ = \c_zero_int \fi: #1 }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \subsection{Get file name}
%
% \begin{macro}{\primargs_get_file_name:N}
%   Empty the file name (globally), and build it one character at a time.
%   The \meta{function} is added at the end of a group, started here.
%   As described in the \TeX{}book, a \meta{file name} should start with
%   \meta{optional spaces} (\LuaTeX{} changes that to \meta{filler}),
%   which we remove, then character tokens,
%   ending with a non-expandable character or control sequence.  After
%   space removal, \cs{g_primargs_token} contains the next token, so no
%   need for \cs{primargs_read_token:N}.
%   When \TeX{} reads a file name, the \tn{input} primitive is
%   temporarily not expandable, so we temporarily change
%   \cs{primargs_read_x_token:N} to not expand this primitive.  This is
%   reverted by \cs{@@_get_file_name_end:}.
%    \begin{macrocode}
\cs_new_protected:Npn \primargs_get_file_name:N #1
  {
    \group_begin:
      \@@_safe:
      \cs_gset_eq:NN \@@_read_x_token_aux:N
                     \@@_read_x_token_file:N
      \tex_aftergroup:D #1
      \tl_gclear:N \g_@@_file_name_tl
      \tl_gset:Nn \g_@@_file_name_level_tl { 0 }
      \primargs_remove_optional_spaces:N \@@_get_file_name_test:
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_get_file_name_test:}
%   The token read is in \cs{g_primargs_token}, and is non-expandable.
%   If it is a control sequence, end the \meta{file name}.  Spaces are
%   special (quotes too, but that is treated elsewhere).  Otherwise,
%   we extract the character from the \tn{meaning} of the \meta{token},
%   which we remove anyways: in that case, we'll recurse.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_get_file_name_test:
  {
    \token_if_cs:NTF \g_primargs_token
      { \@@_get_file_name_end: }
      {
        \token_if_eq_charcode:NNTF \c_space_token \g_primargs_token
          { \primargs_remove_token:N \@@_get_file_name_space: }
          { \primargs_remove_token:N \@@_get_file_name_char: }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_get_file_name_end:}
%   When the end of the file name is reached, reinstate the original
%   definition of |read_x_token| so as to make \tn{input} expandable
%   again, then end the group, after
%   expanding the contents of \cs{g_@@_file_name_tl}.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_get_file_name_end:
  {
    \cs_gset_eq:NN \@@_read_x_token_aux:N
                   \@@_read_x_token_std:N
    \exp_args:No \group_end: \g_@@_file_name_tl
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_get_file_name_space:}
%   We have already removed the space from the input stream.  If
%   there is an odd number of quotes so far, add a space to the
%   file name and continue.  Otherwise the file name ends.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_get_file_name_space:
  {
    \int_if_odd:nTF { \g_@@_file_name_level_tl }
      {
        \tl_gput_right:Nn \g_@@_file_name_tl { ~ }
        \primargs_read_x_token:N \@@_get_file_name_test:
      }
      { \@@_get_file_name_end: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_get_file_name_char:}
% \begin{macro}[EXP]
%   {\@@_get_file_name_char_ii:w, \@@_get_file_name_char_iii:w}
%   Check for a quote, which switches \cs{g_@@_file_name_level_tl}
%   from $0$ to $1$ or back.
%   With an explicit character, applying \tn{string} would give the
%   character code.  Here, implicit characters have to be converted too,
%   so we must work with the \tn{meaning}, which is two or three words
%   separated by spaces, then the character.  The \texttt{ii} auxiliary
%   removes the first two words, and duplicates the remainder (either
%   one character, or a word and a character), and the second auxiliary
%   leaves the second piece in the definition (in both cases, the
%   character).  Then loop with expansion.  This technique would fail if
%   the character could be a space (character code~$32$).
%    \begin{macrocode}
\cs_new_protected:Npn \@@_get_file_name_char:
  {
    \token_if_eq_charcode:NNT " \g_primargs_token % "
      {
        \tl_gset:Nx \g_@@_file_name_level_tl
          { \int_eval:n { 1 - \g_@@_file_name_level_tl } }
      }
    \tl_gput_right:Nx \g_@@_file_name_tl
      {
        \exp_after:wN \@@_get_file_name_char_ii:w
        \token_to_meaning:N \g_primargs_token
        \q_stop
      }
    \primargs_read_x_token:N \@@_get_file_name_test:
  }
\cs_new:Npn \@@_get_file_name_char_ii:w #1 ~ #2 ~ #3 \q_stop
  { \@@_get_file_name_char_iii:w #3 ~ #3 ~ \q_stop }
\cs_new:Npn \@@_get_file_name_char_iii:w #1 ~ #2 ~ #3 \q_stop {#2}
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\primargs_get_input_file_name:N}
% \begin{macro}
%   {
%     \@@_get_input_file_name_first:,
%     \@@_get_input_file_name_loop:, \@@_get_input_file_name_test:,
%     \@@_get_input_file_name_brace:, \@@_get_input_file_name_aux:N
%   }
%   In addition to file names detected by \cs{primargs_get_file_name:N}
%   this allows for braced file names.  The weird indentation is because
%   historically we had to distinguish \LuaTeX{}, allowing braced file
%   names, from other engines.
%   We test for a catcode $1$ token (after a filler) then
%   expand and collect tokens (turned to strings) one by one, counting
%   begin-group and end-group tokens in \cs{g_@@_file_name_level_tl}.
%   The control sequence \tn{par} is ignored.  After removing a filler
%   or after expansion, \cs{g_primargs_token} cannot be \tn{outer} hence
%   the tests are safe.  We use primitives to cope with outer macro
%   hidden by \tn{noexpand} upon first expansion.
%    \begin{macrocode}
    \cs_new_protected:Npn \primargs_get_input_file_name:N #1
      {
        \group_begin:
          \@@_safe:
          \tex_aftergroup:D #1
          \tl_gclear:N \g_@@_file_name_tl
          \tl_gset:Nn \g_@@_file_name_level_tl { 1 }
          \primargs_remove_filler:N \@@_get_input_file_name_first:
      }
    \cs_new_protected:Npn \@@_get_input_file_name_first:
      {
        \token_if_eq_catcode:NNTF \g_primargs_token \c_group_begin_token
          { \primargs_remove_token:N \@@_get_input_file_name_loop: }
          { \primargs_get_file_name:N \group_end: }
      }
    \cs_new_protected:Npn \@@_get_input_file_name_loop:
      { \primargs_read_x_token:N \@@_get_input_file_name_test: }
    \cs_new_protected:Npn \@@_get_input_file_name_test:
      {
        \token_if_eq_catcode:NNTF \g_primargs_token \c_group_begin_token
          {
            \tl_gset:Nx \g_@@_file_name_level_tl
              { \int_eval:n { \g_@@_file_name_level_tl + 1 } }
            \primargs_remove_token:N \@@_get_input_file_name_brace:
          }
          {
            \token_if_eq_catcode:NNTF \g_primargs_token \c_group_end_token
              {
                \tl_gset:Nx \g_@@_file_name_level_tl
                  { \int_eval:n { \g_@@_file_name_level_tl - 1 } }
                \int_compare:nNnTF { \g_@@_file_name_level_tl } > 0
                  { \primargs_remove_token:N \@@_get_input_file_name_brace: }
                  { \primargs_remove_token:N \@@_get_file_name_end: }
              }
              {
                \token_if_eq_meaning:NNTF \g_primargs_token \c_space_token
                  {
                    \tl_gput_right:Nn \g_@@_file_name_tl { ~ }
                    \primargs_remove_token:N \@@_get_input_file_name_loop:
                  }
                  { \exp_after:wN \@@_get_input_file_name_aux:N \exp_not:N }
              }
          }
      }
    \cs_new_protected:Npn \@@_get_input_file_name_brace:
      {
        \tl_gput_right:Nx \g_@@_file_name_tl
          {
            \exp_after:wN \@@_get_file_name_char_ii:w
            \token_to_meaning:N \g_primargs_token
            \q_stop
          }
        \@@_get_input_file_name_loop:
      }
    \cs_new_protected:Npn \@@_get_input_file_name_aux:N #1
      {
        \exp_after:wN \str_if_eq:eeT
        \exp_after:wN { \token_to_str:N #1 } { \token_to_str:N \par }
          { \use_none:nnn }
        \tex_xdef:D \g_@@_file_name_tl
          {
            \g_@@_file_name_tl
            \exp_after:wN \tl_to_str:n \exp_after:wN { \exp_not:N #1 }
          }
        \@@_get_input_file_name_loop:
      }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
%</package> 
%
% \end{implementation}
%
% \PrintIndex
