% \iffalse meta-comment
%
%% File: l3file.dtx
%
% Copyright (C) 1990-2020 The LaTeX3 Project
%
% 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
%
%    https://www.latex-project.org/lppl.txt
%
% This file is part of the "l3kernel bundle" (The Work in LPPL)
% and all files in that bundle must be distributed together.
%
% -----------------------------------------------------------------------
%
% The development version of the bundle can be found at
%
%    https://github.com/latex3/latex3
%
% for those people who are interested.
%
%<*driver>
\documentclass[full,kernel]{l3doc}
\begin{document}
\DocInput{\jobname.dtx}
\PrintIndex
\end{document}
%</driver>
% \fi
%
% \title{^^A
%   The \pkg{l3file} package\\ File and I/O operations^^A
% }
%
% \author{^^A
%  The \LaTeX3 Project\thanks
%    {^^A
%      E-mail:
%        \href{mailto:latex-team@latex-project.org}
%          {latex-team@latex-project.org}^^A
%    }^^A
% }
%
% \date{Released 2020-04-06}
%
% \maketitle
%
% \begin{documentation}
%
% This module provides functions for working with external files. Some of these
% functions apply to an entire file, and have prefix \cs[no-index]{file_\ldots}, while
% others are used to work with files on a line by line basis and have prefix
% \cs[no-index]{ior_\ldots} (reading) or \cs[no-index]{iow_\ldots} (writing).
%
% It is important to remember that when reading external files \TeX{}
% attempts to locate them using both the operating system path and entries in the
% \TeX{} file database (most \TeX{} systems use such a database). Thus the
% \enquote{current path} for \TeX{} is somewhat broader than that for other
% programs.
%
% For functions which expect a \meta{file name} argument, this argument
% may contain both literal items and expandable content, which should on
% full expansion be the desired file name.  Active characters (as
% declared in \cs{l_char_active_seq}) are \emph{not} expanded,
% allowing the direct use of these in file names. Quote tokens (|"|) are
% not permitted in file names as they are reserved for internal use by some
% \TeX{} primitives.
%
% Spaces are trimmed at the beginning and end of the file name:
% this reflects the fact that some file systems do not allow or interact
% unpredictably with spaces in these positions. When no extension is given,
% this will trim spaces from the start of the name only.
%
% \section{Input--output stream management}
%
% As \TeX{} engines have a limited number of input and output streams, direct
% use of the streams by the programmer is not supported in \LaTeX3. Instead, an
% internal pool of streams is maintained, and these are allocated and
% deallocated as needed by other modules. As a result, the programmer should
% close streams when they are no longer needed, to release them for other
% processes.
%
% Note that I/O operations are global: streams should all be declared
% with global names and treated accordingly.
%
% \begin{function}[added = 2011-09-26, updated = 2011-12-27]
%   {\ior_new:N, \ior_new:c, \iow_new:N, \iow_new:c}
%   \begin{syntax}
%     \cs{ior_new:N} \meta{stream}
%     \cs{iow_new:N} \meta{stream}
%   \end{syntax}
%   Globally reserves the name of the \meta{stream}, either for reading
%   or for writing as appropriate. The \meta{stream} is not opened until
%   the appropriate \cs[no-index]{\ldots_open:Nn} function is used. Attempting to
%   use a \meta{stream} which has not been opened is an error, and the
%   \meta{stream} will behave as the corresponding \cs[no-index]{c_term_\ldots}.
% \end{function}
%
% \begin{function}[updated = 2012-02-10]{\ior_open:Nn, \ior_open:cn}
%   \begin{syntax}
%     \cs{ior_open:Nn} \meta{stream} \Arg{file name}
%   \end{syntax}
%   Opens \meta{file name} for reading using \meta{stream} as the
%   control sequence for file access. If the \meta{stream} was already
%   open it is closed before the new operation begins. The
%   \meta{stream} is available for access immediately and will remain
%   allocated to \meta{file name} until a \cs{ior_close:N} instruction
%   is given or the \TeX{} run ends.
%   If the file is not found, an error is raised.
% \end{function}
%
% \begin{function}[added = 2013-01-12, TF]{\ior_open:Nn, \ior_open:cn}
%   \begin{syntax}
%     \cs{ior_open:NnTF} \meta{stream} \Arg{file name} \Arg{true code} \Arg{false code}
%   \end{syntax}
%   Opens \meta{file name} for reading using \meta{stream} as the
%   control sequence for file access. If the \meta{stream} was already
%   open it is closed before the new operation begins. The
%   \meta{stream} is available for access immediately and will remain
%   allocated to \meta{file name} until a \cs{ior_close:N} instruction
%   is given or the \TeX{} run ends. The \meta{true code} is then inserted
%   into the input stream. If the file is not found, no error is raised and
%   the \meta{false code} is inserted into the input stream.
% \end{function}
%
% \begin{function}[updated = 2012-02-09]{\iow_open:Nn, \iow_open:cn}
%   \begin{syntax}
%     \cs{iow_open:Nn} \meta{stream} \Arg{file name}
%   \end{syntax}
%   Opens \meta{file name} for writing using \meta{stream} as the
%   control sequence for file access. If the \meta{stream} was already
%   open it is closed before the new operation begins. The
%   \meta{stream} is available for access immediately and will remain
%   allocated to \meta{file name} until a \cs{iow_close:N} instruction
%   is given or the \TeX{} run ends. Opening a file for writing clears
%   any existing content in the file (\emph{i.e.}~writing is \emph{not}
%   additive).
% \end{function}
%
% \begin{function}[updated = 2012-07-31]
%   {\ior_close:N, \ior_close:c, \iow_close:N, \iow_close:c}
%   \begin{syntax}
%     \cs{ior_close:N} \meta{stream}
%     \cs{iow_close:N} \meta{stream}
%   \end{syntax}
%   Closes the \meta{stream}. Streams should always be closed when
%   they are finished with as this ensures that they remain available
%   to other programmers.
% \end{function}
%
% \begin{function}[added = 2017-06-27]
%   {
%     \ior_show_list:, \ior_log_list:,
%     \iow_show_list:, \iow_log_list:
%   }
%   \begin{syntax}
%     \cs{ior_show_list:}
%     \cs{ior_log_list:}
%     \cs{iow_show_list:}
%     \cs{iow_log_list:}
%   \end{syntax}
%   Display (to the terminal or log file) a list of the file names
%   associated with each open (read or write) stream.  This is intended
%   for tracking down problems.
% \end{function}
%
% \subsection{Reading from files}
%
% Reading from files and reading from the terminal are separate processes in
% \pkg{expl3}. The functions \cs{ior_get:NN} and \cs{ior_str_get:NN}, and their
% branching equivalents, are designed to work with \emph{files}.
%
% \begin{function}[noTF, added = 2012-06-24, updated = 2019-03-23]{\ior_get:NN}
%   \begin{syntax}
%     \cs{ior_get:NN} \meta{stream} \meta{token list variable}
%     \cs{ior_get:NNTF} \meta{stream} \meta{token list variable} \meta{true code} \meta{false code}
%   \end{syntax}
%   Function that reads one or more lines (until an equal number of left
%   and right braces are found) from the file input \meta{stream} and stores
%   the result locally in the \meta{token list} variable.
%   The material read from the \meta{stream} is tokenized by \TeX{}
%   according to the category codes and \tn{endlinechar} in force when
%   the function is used.  Assuming normal settings, any lines which do
%   not end in a comment character~|%| have the line ending
%   converted to a space, so for example input
%   \begin{verbatim}
%      a b  c
%   \end{verbatim}
%   results in a token list \verb*|a b c |.  Any blank line is
%   converted to the token \cs{par}. Therefore, blank lines can be
%   skipped by using a test such as
%   \begin{verbatim}
%      \ior_get:NN \l_my_stream \l_tmpa_tl
%      \tl_set:Nn \l_tmpb_tl { \par }
%      \tl_if_eq:NNF \l_tmpa_tl \l_tmpb_tl
%      ...
%   \end{verbatim}
%   Also notice that if multiple lines are read to match braces
%   then the resulting token list can contain \cs{par} tokens.
%   In the non-branching version, where the \meta{stream} is not open
%   the \meta{tl var} is set to \cs{q_no_value}.
%   \begin{texnote}
%     This protected macro is a wrapper around the \TeX{} primitive
%     \tn{read}.  Regardless of settings, \TeX{} replaces trailing space
%     and tab characters (character codes 32 and~9) in each line by an
%     end-of-line character (character code \tn{endlinechar}, omitted if
%     \tn{endlinechar} is negative or too large) before turning
%     characters into tokens according to current category codes.  With
%     default settings, spaces appearing at the beginning of lines are
%     also ignored.
%   \end{texnote}
% \end{function}
%
% \begin{function}[noTF, added = 2016-12-04, updated = 2019-03-23]
%   {\ior_str_get:NN}
%   \begin{syntax}
%     \cs{ior_str_get:NN} \meta{stream} \meta{token list variable}
%     \cs{ior_str_get:NNTF} \meta{stream} \meta{token list variable} \meta{true code} \meta{false code}
%   \end{syntax}
%   Function that reads one line from the file input \meta{stream} and stores
%   the result locally in the \meta{token list} variable.
%   The material is read from the \meta{stream} as a series of tokens with
%   category code $12$ (other), with the exception of space
%   characters which are given category code $10$ (space).
%   Multiple whitespace characters are retained by this process.  It
%   always only reads one line and any blank lines in the input
%   result in the \meta{token list variable} being empty. Unlike
%   \cs{ior_get:NN}, line ends do not receive any special treatment. Thus
%   input
%   \begin{verbatim}
%      a b  c
%   \end{verbatim}
%   results in a token list |a b  c| with the letters |a|, |b|, and |c|
%   having category code~12.
%   In the non-branching version, where the\meta{stream} is not open
%   the \meta{tl var} is set to \cs{q_no_value}.
%   \begin{texnote}
%     This protected macro is a wrapper around the \eTeX{} primitive
%     \tn{readline}.  Regardless of settings, \TeX{} removes trailing
%     space and tab characters (character codes 32 and~9).  However, the
%     end-line character normally added by this primitive is not
%     included in the result of \cs{ior_str_get:NN}.
%   \end{texnote}
% \end{function}
%
% All mappings are done at the current group level, \emph{i.e.}~any
% local assignments made by the \meta{function} or \meta{code} discussed
% below remain in effect after the loop.
%
% \begin{function}[added = 2012-02-11]{\ior_map_inline:Nn}
%   \begin{syntax}
%     \cs{ior_map_inline:Nn} \meta{stream} \Arg{inline function}
%   \end{syntax}
%   Applies the \meta{inline function} to each set of \meta{lines}
%   obtained by calling \cs{ior_get:NN} until reaching the end of the
%   file.  \TeX{} ignores any trailing new-line marker from the file it
%   reads.  The \meta{inline function} should consist of code which
%   receives the \meta{line} as |#1|.
% \end{function}
%
% \begin{function}[added = 2012-02-11]{\ior_str_map_inline:Nn}
%   \begin{syntax}
%     \cs{ior_str_map_inline:Nn} \meta{stream} \Arg{inline function}
%   \end{syntax}
%   Applies the \meta{inline function} to every \meta{line}
%   in the \meta{stream}. The material is read from the \meta{stream}
%   as a series of tokens with category code $12$ (other), with the
%   exception of space characters which are given category code $10$
%   (space). The \meta{inline function} should consist of code which
%   receives the \meta{line} as |#1|.
%   Note that \TeX{} removes trailing space and tab characters
%   (character codes 32 and 9) from every line upon input.  \TeX{} also
%   ignores any trailing new-line marker from the file it reads.
% \end{function}
%
% \begin{function}[added = 2019-01-13]{\ior_map_variable:NNn}
%   \begin{syntax}
%     \cs{ior_map_variable:NNn} \meta{stream} \meta{tl~var} \Arg{code}
%   \end{syntax}
%   For each set of \meta{lines} obtained by calling \cs{ior_get:NN}
%   until reaching the end of the file, stores the \meta{lines} in the
%   \meta{tl~var} then applies the \meta{code}.  The \meta{code} will
%   usually make use of the \meta{variable}, but this is not enforced.
%   The assignments to the \meta{variable} are local.
%   Its value after the loop is the last set of \meta{lines}, or its
%   original value if the \meta{stream} is empty.  \TeX{} ignores
%   any trailing new-line marker from the file it reads.
%   This function is typically faster than \cs{ior_map_inline:Nn}.
% \end{function}
%
% \begin{function}[added = 2019-01-13]{\ior_str_map_variable:NNn}
%   \begin{syntax}
%     \cs{ior_str_map_variable:NNn} \meta{stream} \meta{variable} \Arg{code}
%   \end{syntax}
%   For each \meta{line} in the \meta{stream}, stores the \meta{line} in
%   the \meta{variable} then applies the \meta{code}.  The material is
%   read from the \meta{stream} as a series of tokens with category code
%   $12$ (other), with the exception of space characters which are given
%   category code $10$ (space).  The \meta{code} will usually make use
%   of the \meta{variable}, but this is not enforced.  The assignments
%   to the \meta{variable} are local.  Its value after the loop is the
%   last \meta{line}, or its original value if the \meta{stream} is
%   empty.  Note that \TeX{} removes trailing
%   space and tab characters (character codes 32 and 9) from every line
%   upon input.  \TeX{} also ignores any trailing new-line marker from
%   the file it reads.
%   This function is typically faster than \cs{ior_str_map_inline:Nn}.
% \end{function}
%
% \begin{function}[added = 2012-06-29]{\ior_map_break:}
%   \begin{syntax}
%     \cs{ior_map_break:}
%   \end{syntax}
%   Used to terminate a \cs[no-index]{ior_map_\ldots} function before all
%   lines from the \meta{stream} have been processed. This
%   normally takes place within a conditional statement, for example
%   \begin{verbatim}
%     \ior_map_inline:Nn \l_my_ior
%       {
%         \str_if_eq:nnTF { #1 } { bingo }
%           { \ior_map_break: }
%           {
%             % Do something useful
%           }
%       }
%   \end{verbatim}
%   Use outside of a \cs[no-index]{ior_map_\ldots} scenario leads to low
%   level \TeX{} errors.
%   \begin{texnote}
%     When the mapping is broken, additional tokens may be inserted
%     before further items are taken
%     from the input stream. This depends on the design of the mapping
%     function.
%   \end{texnote}
% \end{function}
%
% \begin{function}[added = 2012-06-29]{\ior_map_break:n}
%   \begin{syntax}
%     \cs{ior_map_break:n} \Arg{code}
%   \end{syntax}
%   Used to terminate a \cs[no-index]{ior_map_\ldots} function before all
%   lines in the \meta{stream} have been processed, inserting
%   the \meta{code} after the mapping has ended. This
%   normally takes place within a conditional statement, for example
%   \begin{verbatim}
%     \ior_map_inline:Nn \l_my_ior
%       {
%         \str_if_eq:nnTF { #1 } { bingo }
%           { \ior_map_break:n { <code> } }
%           {
%             % Do something useful
%           }
%       }
%   \end{verbatim}
%   Use outside of a \cs[no-index]{ior_map_\ldots} scenario leads to low
%   level \TeX{} errors.
%   \begin{texnote}
%     When the mapping is broken, additional tokens may be inserted
%     before the \meta{code} is
%     inserted into the input stream.
%     This depends on the design of the mapping function.
%   \end{texnote}
% \end{function}
%
% \begin{function}[updated = 2012-02-10, EXP, pTF]{\ior_if_eof:N}
%   \begin{syntax}
%     \cs{ior_if_eof_p:N} \meta{stream} \\
%     \cs{ior_if_eof:NTF} \meta{stream} \Arg{true code} \Arg{false code}
%   \end{syntax}
%   Tests if the end of a file \meta{stream} has been reached during a reading
%   operation. The test also returns a \texttt{true} value if
%   the \meta{stream} is not open.
% \end{function}
%
% \subsection{Writing to files}
%
% \begin{function}[updated = 2012-06-05]{\iow_now:Nn, \iow_now:Nx, \iow_now:cn, \iow_now:cx}
%   \begin{syntax}
%     \cs{iow_now:Nn} \meta{stream} \Arg{tokens}
%   \end{syntax}
%   This functions writes \meta{tokens} to the specified
%   \meta{stream} immediately (\emph{i.e.}~the write operation is called
%   on expansion of \cs{iow_now:Nn}).
% \end{function}
%
% \begin{function}{\iow_log:n, \iow_log:x}
%   \begin{syntax}
%     \cs{iow_log:n} \Arg{tokens}
%   \end{syntax}
%   This function writes the given \meta{tokens} to the log (transcript)
%   file immediately: it is a dedicated version of \cs{iow_now:Nn}.
% \end{function}
%
% \begin{function}{\iow_term:n, \iow_term:x}
%   \begin{syntax}
%     \cs{iow_term:n} \Arg{tokens}
%   \end{syntax}
%   This function writes the given \meta{tokens} to the terminal
%   file immediately: it is a dedicated version of \cs{iow_now:Nn}.
% \end{function}
%
% \begin{function}
%   {
%     \iow_shipout:Nn, \iow_shipout:Nx,
%     \iow_shipout:cn, \iow_shipout:cx
%   }
%   \begin{syntax}
%     \cs{iow_shipout:Nn} \meta{stream} \Arg{tokens}
%   \end{syntax}
%   This functions writes \meta{tokens} to the specified
%   \meta{stream} when the current page is finalised (\emph{i.e.}~at
%   shipout). The \texttt{x}-type variants expand the \meta{tokens}
%   at the point where the function is used but \emph{not} when the
%   resulting tokens are written to the \meta{stream}
%   (\emph{cf.}~\cs{iow_shipout_x:Nn}).
%   \begin{texnote}
%     When using \pkg{expl3} with a format other than \LaTeX{}, new line
%     characters inserted using \cs{iow_newline:} or using the
%     line-wrapping code \cs{iow_wrap:nnnN} are not recognized in
%     the argument of \cs{iow_shipout:Nn}.  This may lead to the
%     insertion of additional unwanted line-breaks.
%   \end{texnote}
% \end{function}
%
% \begin{function}[updated = 2012-09-08]
%   {
%     \iow_shipout_x:Nn, \iow_shipout_x:Nx,
%     \iow_shipout_x:cn, \iow_shipout_x:cx
%   }
%   \begin{syntax}
%     \cs{iow_shipout_x:Nn} \meta{stream} \Arg{tokens}
%   \end{syntax}
%   This functions writes \meta{tokens} to the specified
%   \meta{stream} when the current page is finalised (\emph{i.e.}~at
%   shipout). The \meta{tokens} are expanded at the time of writing
%   in addition to any expansion when the function is used. This makes
%   these functions suitable for including material finalised during
%   the page building process (such as the page number integer).
%   \begin{texnote}
%     This is a wrapper around the \TeX{} primitive \tn{write}.
%     When using \pkg{expl3} with a format other than \LaTeX{}, new line
%     characters inserted using \cs{iow_newline:} or using the
%     line-wrapping code \cs{iow_wrap:nnnN} are not recognized in
%     the argument of \cs{iow_shipout:Nn}.  This may lead to the
%     insertion of additional unwanted line-breaks.
%   \end{texnote}
% \end{function}
%
% \begin{function}[EXP]{\iow_char:N}
%   \begin{syntax}
%     \cs{iow_char:N} |\|\meta{char}
%   \end{syntax}
%   Inserts \meta{char} into the output stream. Useful when trying to
%   write difficult characters such as |%|, |{|, |}|,
%   \emph{etc.}~in messages, for example:
%   \begin{verbatim}
%     \iow_now:Nx \g_my_iow { \iow_char:N \{ text \iow_char:N \} }
%   \end{verbatim}
%   The function has no effect if writing is taking place without
%   expansion (\emph{e.g.}~in the second argument of \cs{iow_now:Nn}).
% \end{function}
%
% \begin{function}[EXP]{\iow_newline:}
%   \begin{syntax}
%     \cs{iow_newline:}
%   \end{syntax}
%   Function to add a new line within the \meta{tokens} written to a
%   file. The function has no effect if writing is taking place without
%   expansion (\emph{e.g.}~in the second argument of \cs{iow_now:Nn}).
%   \begin{texnote}
%     When using \pkg{expl3} with a format other than \LaTeX{}, the
%     character inserted by \cs{iow_newline:} is not recognized by
%     \TeX{}, which may lead to the insertion of additional unwanted
%     line-breaks.  This issue only affects \cs{iow_shipout:Nn},
%     \cs{iow_shipout_x:Nn} and direct uses of primitive operations.
%   \end{texnote}
% \end{function}
%
% \subsection{Wrapping lines in output}
%
% \begin{function}[added = 2012-06-28, updated = 2017-12-04]
%   {\iow_wrap:nnnN, \iow_wrap:nxnN}
%   \begin{syntax}
%     \cs{iow_wrap:nnnN} \Arg{text} \Arg{run-on text} \Arg{set up} \meta{function}
%   \end{syntax}
%   This function wraps the \meta{text} to a fixed number of
%   characters per line. At the start of each line which is wrapped,
%   the \meta{run-on text} is inserted.  The line character count
%   targeted is the value of \cs{l_iow_line_count_int} minus the
%   number of characters in the \meta{run-on text} for all lines except
%   the first, for which the target number of characters is simply
%   \cs{l_iow_line_count_int} since there is no run-on text.  The
%   \meta{text} and \meta{run-on text} are exhaustively expanded by the
%   function, with the following substitutions:
%   \begin{itemize}
%     \item |\\| or \cs{iow_newline:} may be used to force a new line,
%     \item \verb*|\ | may be used to represent a forced space
%       (for example after a control sequence),
%     \item |\#|, |\%|, |\{|, |\}|, |\~| may be used to represent
%       the corresponding character,
%     \item \cs{iow_allow_break:} may be used to allow a line-break
%       without inserting a space (this is experimental),
%     \item \cs{iow_indent:n} may be used to indent a part of the
%       \meta{text} (not the \meta{run-on text}).
%   \end{itemize}
%   Additional functions may be added to the wrapping by using the
%   \meta{set up}, which is executed before the wrapping takes place: this
%   may include overriding the substitutions listed.
%
%   Any expandable material in the \meta{text} which is not to be expanded
%   on wrapping should be converted to a string using \cs{token_to_str:N},
%   \cs{tl_to_str:n}, \cs{tl_to_str:N}, \emph{etc.}
%
%   The result of the wrapping operation is passed as a braced argument to the
%   \meta{function}, which is typically a wrapper around a write
%   operation. The output of \cs{iow_wrap:nnnN} (\emph{i.e.}~the argument
%   passed to the \meta{function}) consists of characters of category
%   \enquote{other} (category code~12), with the exception of spaces which
%   have category \enquote{space} (category code~10). This means that the
%   output does \emph{not} expand further when written to a file.
%
%   \begin{texnote}
%     Internally, \cs{iow_wrap:nnnN} carries out an \texttt{x}-type expansion
%     on the \meta{text} to expand it. This is done in such a way that
%     \cs{exp_not:N} or \cs{exp_not:n} \emph{could} be used to prevent
%     expansion of material. However, this is less conceptually clear than
%     conversion to a string, which is therefore the supported method for
%     handling expandable material in the \meta{text}.
%   \end{texnote}
% \end{function}
%
% \begin{function}[added = 2011-09-21]{\iow_indent:n}
%   \begin{syntax}
%     \cs{iow_indent:n} \Arg{text}
%   \end{syntax}
%   In the first argument of \cs{iow_wrap:nnnN} (for instance in messages),
%   indents \meta{text} by four spaces. This function does not cause
%   a line break, and only affects lines which start within the scope
%   of the \meta{text}. In case the indented \meta{text} should appear
%   on separate lines from the surrounding text, use |\\| to force
%   line breaks.
% \end{function}
%
% \begin{variable}[added = 2012-06-24]{\l_iow_line_count_int}
%   The maximum number of characters in a line to be written
%   by the \cs{iow_wrap:nnnN}
%   function. This value depends on the \TeX{} system in use: the standard
%   value is $78$, which is typically correct for unmodified \TeX{}live
%   and \hologo{MiKTeX} systems.
% \end{variable}
%
% \subsection{Constant input--output streams, and variables}
%
% \begin{variable}[added = 2017-12-11]{\g_tmpa_ior, \g_tmpb_ior}
%   Scratch input stream for global use. These are never used by
%   the kernel code, and so are safe for use with any \LaTeX3-defined
%   function. However, they may be overwritten by other non-kernel
%   code and so should only be used for short-term storage.
% \end{variable}
%
% \begin{variable}{\c_log_iow, \c_term_iow}
%   Constant output streams for writing to the log and to the terminal
%   (plus the log), respectively.
% \end{variable}
%
% \begin{variable}[added = 2017-12-11]{\g_tmpa_iow, \g_tmpb_iow}
%   Scratch output stream for global use. These are never used by
%   the kernel code, and so are safe for use with any \LaTeX3-defined
%   function. However, they may be overwritten by other non-kernel
%   code and so should only be used for short-term storage.
% \end{variable}
%
% \subsection{Primitive conditionals}
%
% \begin{function}[EXP]{\if_eof:w}
%   \begin{syntax}
%     \cs{if_eof:w} \meta{stream}
%     ~~\meta{true code}
%     \cs{else:}
%     ~~\meta{false code}
%     \cs{fi:}
%   \end{syntax}
%   Tests if the \meta{stream} returns \enquote{end of file}, which is true
%   for non-existent files. The \cs{else:} branch is optional.
%   \begin{texnote}
%     This is the \TeX{} primitive \tn{ifeof}.
%   \end{texnote}
% \end{function}
%
% \section{File operation functions}
%
% \begin{variable}[added = 2017-06-21]
%   {
%     \g_file_curr_dir_str,
%     \g_file_curr_name_str,
%     \g_file_curr_ext_str
%   }
%   Contain the directory, name and extension of the current file. The
%   directory is empty if the file was loaded without an explicit
%   path (\emph{i.e.}~if it is in the \TeX{} search path), and does
%   \emph{not} end in |/| other than the case that it is exactly equal
%   to the root directory. The \meta{name} and \meta{ext} parts together
%   make up the file name, thus the \meta{name} part may be thought of
%   as the \enquote{job name} for the current file. Note that \TeX{} does
%   not provide information on the \meta{ext} part for the main (top
%   level) file and that this file always has an empty \meta{dir} component.
%   Also, the \meta{name} here will be equal to \cs{c_sys_jobname_str},
%   which may be different from the real file name (if set using
%   |--jobname|, for example).
% \end{variable}
%
% \begin{variable}[added = 2017-06-18]{\l_file_search_path_seq}
%   Each entry is the path to a directory which should be searched when
%   seeking a file. Each path can be relative or absolute, and should
%   not include the trailing slash. The entries are not expanded when
%   used so may contain active characters but should not feature any
%   variable content. Spaces need not be quoted.
%
%   \begin{texnote}
%     When working as a package in \LaTeXe{}, \pkg{expl3} will
%     automatically append the current \tn{input@path} to the
%     set of values from \cs{l_file_search_path_seq}.
%   \end{texnote}
% \end{variable}
%
% \begin{function}[TF, updated = 2012-02-10]{\file_if_exist:n}
%   \begin{syntax}
%     \cs{file_if_exist:nTF} \Arg{file name} \Arg{true code} \Arg{false code}
%   \end{syntax}
%   Searches for \meta{file name} using the current \TeX{} search
%   path and the additional paths controlled by
%   \cs{l_file_search_path_seq}.
% \end{function}
%
% \begin{function}[noTF, added = 2019-01-16, updated = 2019-02-16]{\file_get:nnN}
%   \begin{syntax}
%     \cs{file_get:nnN} \Arg{filename} \Arg{setup} \meta{tl}
%     \cs{file_get:nnNTF} \Arg{filename} \Arg{setup} \meta{tl} \Arg{true code} \Arg{false code}
%   \end{syntax}
%   Defines \meta{tl} to the contents of \meta{filename}.
%   Category codes may need to be set appropriately via the \meta{setup}
%   argument.
%   The non-branching version sets the \meta{tl} to \cs{q_no_value} if the file is
%   not found. The branching version runs the \meta{true code} after the
%   assignment to \meta{tl} if the file is found, and \meta{false code}
%   otherwise.
% \end{function}
%
% \begin{function}[noTF, updated = 2019-02-16]
%   {\file_get_full_name:nN, \file_get_full_name:VN}
%   \begin{syntax}
%     \cs{file_get_full_name:nN} \Arg{file name} \meta{tl}
%     \cs{file_get_full_name:nNTF} \Arg{file name} \meta{tl} \Arg{true code} \Arg{false code}
%   \end{syntax}
%   Searches for \meta{file name} in the path as detailed for
%   \cs{file_if_exist:nTF}, and if found sets the \meta{tl var} the
%   fully-qualified name of the file, \emph{i.e.}~the path and file name.
%   This includes an extension |.tex| when the given \meta{file name}
%   has no extension but the file found has that extension.
%   In the non-branching version, the \meta{tl var} will be set to
%   \cs{q_no_value} in the case that the file does not exist.
% \end{function}
%
% \begin{function}[added = 2019-09-03, rEXP]{\file_full_name:n, \file_full_name:V}
%   \begin{syntax}
%     \cs{file_full_name:n} \Arg{file name}
%   \end{syntax}
%   Searches for \meta{file name} in the path as detailed for
%   \cs{file_if_exist:nTF}, and if found leaves the
%   fully-qualified name of the file, \emph{i.e.}~the path and file name,
%   in the input stream.
%   This includes an extension |.tex| when the given \meta{file name}
%   has no extension but the file found has that extension.
%   If the file is not found on the path, the expansion is empty.
% \end{function}
%
% \begin{function}[added = 2017-06-23, updated = 2017-06-26]
%   {\file_parse_full_name:nNNN, \file_parse_full_name:VNNN}
%   \begin{syntax}
%     \cs{file_parse_full_name:nNNN} \Arg{full name} \meta{dir} \meta{name} \meta{ext}
%   \end{syntax}
%   Parses the \meta{full name} and splits it into three parts, each of
%   which is returned by setting the appropriate local string variable:
%   \begin{itemize}
%     \item The \meta{dir}: everything up to the last |/| (path separator)
%       in the \meta{file path}. As with system \texttt{PATH} variables
%       and related functions, the \meta{dir} does \emph{not} include the
%       trailing |/| unless it points to the root directory. If there is no path (only
%       a file name), \meta{dir} is empty.
%     \item The \meta{name}: everything after the last |/| up to the last |.|,
%       where both of those characters are optional. The \meta{name} may
%       contain multiple |.| characters. It is empty if \meta{full name}
%       consists only of a directory name.
%     \item The \meta{ext}: everything after the last |.| (including the dot).
%       The \meta{ext} is empty if there is no |.| after the last |/|.
%   \end{itemize}
%   This function does not expand the \meta{full name} before turning it
%   to a string.  It assume that the \meta{full name} either contains no
%   quote (|"|) characters or is surrounded by a pair of quotes.
% \end{function}
%
% \begin{function}[rEXP, added = 2019-11-19]
%   {\file_hex_dump:n, \file_hex_dump:nnn}
%   \begin{syntax}
%     \cs{file_hex_dump:n} \Arg{file name}
%     \cs{file_hex_dump:nnn} \Arg{file name} \Arg{start index} \Arg{end index}
%   \end{syntax}
%   Searches for \meta{file name} using the current \TeX{} search
%   path and the additional paths controlled by \cs{l_file_search_path_seq}.
%   It then expands to leave the hexadecimal dump of the file content in the
%   input stream. The file is read as bytes, which means that in
%   contrast to most \TeX{} behaviour there will be a difference in result
%   depending on the line endings used in text files. The same file will
%   produce the same result between different engines: the algorithm used
%   is the same in all cases. When the file is not found, the result of
%   expansion is empty. The \Arg{start index} and \Arg{end index} values
%   work as described for \cs{str_range:nnn}.
% \end{function}
%
% \begin{function}[noTF, added = 2019-11-19]
%   {\file_get_hex_dump:nN, \file_get_hex_dump:nnnN}
%   \begin{syntax}
%     \cs{file_get_hex_dump:nN} \Arg{file name} \meta{tl var}
%     \cs{file_get_hex_dump:nnnN} \Arg{file name} \Arg{start index} \Arg{end index} \meta{tl var}
%   \end{syntax}
%   Sets the \meta{tl var} to the result of applying
%   \cs{file_hex_dump:n}/\cs{file_hex_dump:nnn} to the \meta{file}.
%   If the file is not found, the \meta{tl var} will be set to \cs{q_no_value}.
% \end{function}
%
% \begin{function}[rEXP, added = 2019-09-03]{\file_mdfive_hash:n}
%   \begin{syntax}
%     \cs{file_mdfive_hash:n} \Arg{file name}
%   \end{syntax}
%   Searches for \meta{file name} using the current \TeX{} search
%   path and the additional paths controlled by \cs{l_file_search_path_seq}.
%   It then expands to leave the MD5 sum generated from the contents of
%   the file in the input stream. The file is read as bytes, which means that in
%   contrast to most \TeX{} behaviour there will be a difference in result
%   depending on the line endings used in text files. The same file will
%   produce the same result between different engines: the algorithm used
%   is the same in all cases. When the file is not found, the result of
%   expansion is empty.
% \end{function}
%
% \begin{function}[noTF, added = 2017-07-11, updated = 2019-02-16]
%   {\file_get_mdfive_hash:nN}
%   \begin{syntax}
%     \cs{file_get_mdfive_hash:nN} \Arg{file name} \meta{tl var}
%   \end{syntax}
%   Sets the \meta{tl var} to the result of applying
%   \cs{file_mdfive_hash:n} to the \meta{file}. If the file is not found, 
%   the \meta{tl var} will be set to \cs{q_no_value}.
% \end{function}
%
% \begin{function}[rEXP, added = 2019-09-03]{\file_size:n}
%   \begin{syntax}
%     \cs{file_size:n} \Arg{file name}
%   \end{syntax}
%   Searches for \meta{file name} using the current \TeX{} search
%   path and the additional paths controlled by \cs{l_file_search_path_seq}.
%   It then expands to leave the size of the file in bytes in the input stream.
%   When the file is not found, the result of expansion is empty.
% \end{function}
%
% \begin{function}[noTF, added = 2017-07-09, updated = 2019-02-16]
%   {\file_get_size:nN}
%   \begin{syntax}
%     \cs{file_get_size:nN} \Arg{file name} \meta{tl var}
%   \end{syntax}
%   Sets the \meta{tl var} to the result of applying
%   \cs{file_size:n} to the \meta{file}. If the file is not found, 
%   the \meta{tl var} will be set to \cs{q_no_value}.
%   This is not available in older versions of \XeTeX{}.
% \end{function}
%
% \begin{function}[rEXP, added = 2019-09-03]{\file_timestamp:n}
%   \begin{syntax}
%     \cs{file_timestamp:n} \Arg{file name}
%   \end{syntax}
%   Searches for \meta{file name} using the current \TeX{} search
%   path and the additional paths controlled by \cs{l_file_search_path_seq}.
%   It then expands to leave the modification timestamp of
%   the file in the input stream. The timestamp is of the form
%   |D:|\meta{year}\meta{month}\meta{day}\meta{hour}^^A
%   \meta{minute}\meta{second}\meta{offset}, where the latter may be |Z|
%   (UTC) or \meta{plus-minus}\meta{hours}|'|\meta{minutes}|'|.
%   When the file is not found, the result of expansion is empty.
%   This is not available in older versions of \XeTeX{}.
% \end{function}
%
% \begin{function}[noTF, added = 2017-07-09, updated = 2019-02-16]
%   {\file_get_timestamp:nN}
%   \begin{syntax}
%     \cs{file_get_timestamp:nN} \Arg{file name} \meta{tl var}
%   \end{syntax}
%   Sets the \meta{tl var} to the result of applying
%   \cs{file_timestamp:n} to the \meta{file}. If the file is not found, 
%   the \meta{tl var} will be set to \cs{q_no_value}.
%   This is not available in older versions of \XeTeX{}.
% \end{function}
%
% \begin{function}[added = 2019-05-13, updated = 2019-09-20, pTF, EXP]
%   {\file_compare_timestamp:nNn}
%   \begin{syntax}
%     \cs{file_compare_timestamp:nNn} \Arg{file-1} \meta{comparator} \Arg{file-2} \Arg{true code} \Arg{false code}
%   \end{syntax}
%   Compares the file stamps on the two \meta{files} as indicated by
%   the \meta{comparator}, and inserts either the \meta{true code}
%   or \meta{false case} as required. A file which is not found
%   is treated as older than any file which is found. This allows for
%   example the construct
%   \begin{verbatim}
%     \file_compare_timestamp:nNnT { source-file } > { derived-file }
%       {
%         % Code to regenerate derived file
%       }
%   \end{verbatim}
%   to work when the derived file is entirely absent. The timestamp
%   of two absent files is regarded as different.
%   This is not available in older versions of \XeTeX{}.
% \end{function}
%
%
% \begin{function}[updated = 2017-06-26]{\file_input:n}
%   \begin{syntax}
%     \cs{file_input:n} \Arg{file name}
%   \end{syntax}
%   Searches for \meta{file name} in the path as detailed for
%   \cs{file_if_exist:nTF}, and if found reads in the file as
%   additional \LaTeX{} source. All files read are recorded
%   for information and the file name stack is updated by this
%   function. An error is raised if the file is not found.
% \end{function}
%
% \begin{function}[added = 2014-07-02]{\file_if_exist_input:n, \file_if_exist_input:nF}
%   \begin{syntax}
%     \cs{file_if_exist_input:n} \Arg{file name}
%     \cs{file_if_exist_input:nF} \Arg{file name} \Arg{false code}
%   \end{syntax}
%   Searches for \meta{file name} using the current \TeX{} search
%   path and the additional paths controlled by
%   \cs{file_path_include:n}. If found then
%   reads in the file as additional \LaTeX{} source as described for
%   \cs{file_input:n}, otherwise inserts the \meta{false code}.
%   Note that these functions do not raise
%   an error if the file is not found, in contrast to \cs{file_input:n}.
% \end{function}
%
% \begin{function}[added = 2017-07-07]{\file_input_stop:}
%   \begin{syntax}
%     \cs{file_input_stop:}
%   \end{syntax}
%   Ends the reading of a file started by \cs{file_input:n} or similar before
%   the end of the file is reached. Where the file reading is being terminated
%   due to an error, \cs[index = msg_critical:nn]{msg_critical:nn(nn)}
%   should be preferred.
%   \begin{texnote}
%     This function must be used on a line on its own: \TeX{} reads files
%     line-by-line and so any additional tokens in the \enquote{current} line
%     will still be read.
%
%     This is also true if the function is hidden inside another function
%     (which will be the normal case), i.e., all tokens on the same line
%     in the source file are still processed. Putting it on a line by itself
%     in the definition doesn't help as it is the line where it is used that
%     counts!
%   \end{texnote}
% \end{function}
%
% \begin{function}{\file_show_list:, \file_log_list:}
%   \begin{syntax}
%     \cs{file_show_list:}
%     \cs{file_log_list:}
%   \end{syntax}
%   These functions list all files loaded by \LaTeXe{} commands that
%   populate \tn{@filelist} or by \cs{file_input:n}.  While
%   \cs{file_show_list:} displays the list in the terminal,
%   \cs{file_log_list:} outputs it to the log file only.
% \end{function}
%
% \end{documentation}
%
% \begin{implementation}
%
% \section{\pkg{l3file} implementation}
%
% \TestFiles{m3file001}
%
%    \begin{macrocode}
%<*initex|package>
%    \end{macrocode}
%
% \subsection{Input operations}
%
%    \begin{macrocode}
%<@@=ior>
%    \end{macrocode}
%
% \subsubsection{Variables and constants}
%
% \begin{variable}{\l_@@_internal_tl}
%   Used as a short-term scratch variable.
%    \begin{macrocode}
\tl_new:N  \l_@@_internal_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\c_@@_term_ior}
%   Reading from the terminal (with a prompt) is done using a positive
%   but non-existent stream number. Unlike writing, there is no concept
%   of reading from the log.
%    \begin{macrocode}
\int_const:Nn \c_@@_term_ior { 16 }
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\g_@@_streams_seq}
%   A list of the currently-available input streams to be used as a
%   stack.  In format mode, all streams (from $0$ to~$15$) are
%   available, while the package requests streams to \LaTeXe{} as they
%   are needed (initially none are needed), so the starting point
%   varies!
%    \begin{macrocode}
\seq_new:N \g_@@_streams_seq
%<*initex>
\seq_gset_split:Nnn \g_@@_streams_seq { , }
  { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 }
%</initex>
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_stream_tl}
%   Used to recover the raw stream number from the stack.
%    \begin{macrocode}
\tl_new:N \l_@@_stream_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\g_@@_streams_prop}
%   The name of the file attached to each stream is tracked in a property
%   list. To get the correct number of reserved streams in package mode the
%   underlying mechanism needs to be queried. For \LaTeXe{} and plain \TeX{}
%   this data is stored in |\count16|: with the \pkg{etex} package loaded
%   we need to subtract $1$ as the register holds the number of the next
%   stream to use. In \ConTeXt{}, we need to look at |\count38| but there
%   is no subtraction: like the original plain \TeX{}/\LaTeXe{} mechanism
%   it holds the value of the \emph{last} stream allocated.
%    \begin{macrocode}
\prop_new:N \g_@@_streams_prop
%<*package>
\int_step_inline:nnn
  { 0 }
  {
    \cs_if_exist:NTF \normalend
      { \tex_count:D 38 ~ }
      {
        \tex_count:D 16 ~ %
        \cs_if_exist:NT \loccount { - 1 }
      }
  }
  {
    \prop_gput:Nnn \g_@@_streams_prop {#1} { Reserved~by~format }
  }
%</package>
%    \end{macrocode}
% \end{variable}
%
% \subsubsection{Stream management}
%
% \begin{macro}{\ior_new:N, \ior_new:c}
%   Reserving a new stream is done by defining the name as equal to using the
%   terminal.
%    \begin{macrocode}
\cs_new_protected:Npn \ior_new:N #1 { \cs_new_eq:NN #1 \c_@@_term_ior }
\cs_generate_variant:Nn \ior_new:N { c }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\g_tmpa_ior, \g_tmpb_ior}
%   The usual scratch space.
%    \begin{macrocode}
\ior_new:N \g_tmpa_ior
\ior_new:N \g_tmpb_ior
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\ior_open:Nn, \ior_open:cn}
%   Use the conditional version, with an error if the file is not found.
%    \begin{macrocode}
\cs_new_protected:Npn \ior_open:Nn #1#2
  { \ior_open:NnF #1 {#2} { \__kernel_file_missing:n {#2} } }
\cs_generate_variant:Nn \ior_open:Nn { c }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\l_@@_file_name_tl}
%   Data storage.
%    \begin{macrocode}
\tl_new:N \l_@@_file_name_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}[TF]{\ior_open:Nn, \ior_open:cn}
%   An auxiliary searches for the file in the \TeX{}, \LaTeXe{} and
%   \LaTeX3 paths.  Then pass the file found to the lower-level function
%   which deals with streams.  The |full_name| is empty when the file is
%   not found.
%    \begin{macrocode}
\prg_new_protected_conditional:Npnn \ior_open:Nn #1#2 { T , F , TF }
  {
    \file_get_full_name:nNTF {#2} \l_@@_file_name_tl
      {
        \__kernel_ior_open:No #1 \l_@@_file_name_tl
        \prg_return_true:
      }
      { \prg_return_false: }
  }
\prg_generate_conditional_variant:Nnn \ior_open:Nn { c } { T , F , TF }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_new:N}
%   In package mode, streams are reserved using \tn{newread} before they
%   can be managed by \pkg{ior}.  To prevent \pkg{ior} from being
%   affected by redefinitions of \tn{newread} (such as done by the
%   third-party package \pkg{morewrites}), this macro is saved here
%   under a private name.  The complicated code ensures that
%   \cs{@@_new:N} is not \tn{outer} despite plain \TeX{}'s \tn{newread}
%   being \tn{outer}. For \ConTeXt{}, we have to deal with the fact
%   that \tn{newread} works like our own: it actually checks before
%   altering definition.
%    \begin{macrocode}
%<*package>
\exp_args:NNf \cs_new_protected:Npn \@@_new:N
  { \exp_args:NNc \exp_after:wN \exp_stop_f: { newread } }
\cs_if_exist:NT \normalend
  {
    \cs_new_eq:NN \@@_new_aux:N \@@_new:N
    \cs_set_protected:Npn \@@_new:N #1
      {
        \cs_undefine:N #1
        \@@_new_aux:N #1
      }
  }
%</package>
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\__kernel_ior_open:Nn, \__kernel_ior_open:No}
% \begin{macro}{\@@_open_stream:Nn}
%   The stream allocation itself uses the fact that there is a list of all of
%   those available, so allocation is simply a question of using the number at
%   the top of the list. In package mode, life gets more complex as it's
%   important to keep things in sync. That is done using a two-part approach:
%   any streams that have already been taken up by \pkg{ior} but are now free
%   are tracked, so we first try those. If that fails, ask plain \TeX{} or \LaTeXe{}
%   for a new stream and use that number (after a bit of conversion).
%    \begin{macrocode}
\cs_new_protected:Npn \__kernel_ior_open:Nn #1#2
  {
    \ior_close:N #1
    \seq_gpop:NNTF \g_@@_streams_seq \l_@@_stream_tl
      { \@@_open_stream:Nn #1 {#2} }
%<*initex>
      { \__kernel_msg_fatal:nn { kernel } { input-streams-exhausted } }
%</initex>
%<*package>
      {
        \@@_new:N #1
        \tl_set:Nx \l_@@_stream_tl { \int_eval:n {#1} }
        \@@_open_stream:Nn #1 {#2}
      }
%</package>
  }
\cs_generate_variant:Nn \__kernel_ior_open:Nn { No }
%    \end{macrocode}
%   Here, we act defensively in case \LuaTeX{} is in use with an
%   extensionless file name.
%    \begin{macrocode}
\cs_new_protected:Npx \@@_open_stream:Nn #1#2
  {
    \tex_global:D \tex_chardef:D #1 = \exp_not:N \l_@@_stream_tl \scan_stop:
    \prop_gput:NVn \exp_not:N \g_@@_streams_prop #1 {#2}
    \tex_openin:D #1
      \sys_if_engine_luatex:TF
        { {#2} }
        {  \exp_not:N \__kernel_file_name_quote:n {#2} \scan_stop: }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\ior_close:N, \ior_close:c}
%   Closing a stream means getting rid of it at the \TeX{} level and
%   removing from the various data structures.  Unless the name passed
%   is an invalid stream number (outside the range $[0,15]$), it can be
%   closed.  On the other hand, it only gets added to the stack if it
%   was not already there, to avoid duplicates building up.
%    \begin{macrocode}
\cs_new_protected:Npn \ior_close:N #1
  {
    \int_compare:nT { -1 < #1 < \c_@@_term_ior }
      {
        \tex_closein:D #1
        \prop_gremove:NV \g_@@_streams_prop #1
        \seq_if_in:NVF \g_@@_streams_seq #1
          { \seq_gpush:NV \g_@@_streams_seq #1 }
        \cs_gset_eq:NN #1 \c_@@_term_ior
      }
  }
\cs_generate_variant:Nn \ior_close:N { c }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\ior_show_list:, \ior_log_list:}
% \begin{macro}{\@@_list:N}
%   Show the property lists, but with some \enquote{pretty printing}.
%   See the \pkg{l3msg} module.  The first argument of the message is
%   |ior| (as opposed to |iow|) and the second is empty if no read
%   stream is open and non-empty (the list of streams formatted using
%   \cs{msg_show_item_unbraced:nn}) otherwise.  The code of the message
%   \texttt{show-streams} takes care of translating |ior|/|iow| to
%   English.
%    \begin{macrocode}
\cs_new_protected:Npn \ior_show_list: { \@@_list:N \msg_show:nnxxxx }
\cs_new_protected:Npn \ior_log_list: { \@@_list:N \msg_log:nnxxxx }
\cs_new_protected:Npn \@@_list:N #1
  {
    #1 { LaTeX / kernel } { show-streams }
      { ior }
      {
        \prop_map_function:NN \g_@@_streams_prop
          \msg_show_item_unbraced:nn
      }
      { } { }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \subsubsection{Reading input}
%
% \begin{macro}{\if_eof:w}
%   The primitive conditional
%    \begin{macrocode}
\cs_new_eq:NN \if_eof:w \tex_ifeof:D
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[pTF]{\ior_if_eof:N}
%   To test if some particular input stream is exhausted the following
%   conditional is provided.  The primitive test can only deal with
%   numbers in the range $[0,15]$ so we catch outliers (they are
%   exhausted).
%    \begin{macrocode}
\prg_new_conditional:Npnn \ior_if_eof:N #1 { p , T , F , TF }
  {
    \cs_if_exist:NTF #1
      {
        \int_compare:nTF { -1 < #1 < \c_@@_term_ior }
          {
            \if_eof:w #1
              \prg_return_true:
            \else:
              \prg_return_false:
            \fi:
          }
          { \prg_return_true: }
      }
      { \prg_return_true: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\ior_get:NN, \@@_get:NN}
% \begin{macro}[TF]{\ior_get:NN}
%   And here we read from files.
%    \begin{macrocode}
\cs_new_protected:Npn \ior_get:NN #1#2
  { \ior_get:NNF #1 #2 { \tl_set:Nn #2 { \q_no_value } } }
\cs_new_protected:Npn \@@_get:NN #1#2
  { \tex_read:D #1 to #2 }
\prg_new_protected_conditional:Npnn \ior_get:NN #1#2 { T , F , TF }
  {
    \ior_if_eof:NTF #1
      { \prg_return_false: }
      {
        \@@_get:NN #1 #2
        \prg_return_true:
      }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\ior_str_get:NN, \@@_str_get:NN}
% \begin{macro}[TF]{\ior_str_get:NN}
%   Reading as strings is a more complicated wrapper, as we wish to
%   remove the endline character and restore it afterwards.
%    \begin{macrocode}
\cs_new_protected:Npn \ior_str_get:NN #1#2
  { \ior_str_get:NNF #1 #2 { \tl_set:Nn #2 { \q_no_value } } }
\cs_new_protected:Npn \@@_str_get:NN #1#2
  {
    \exp_args:Nno \use:n
      {
        \int_set:Nn \tex_endlinechar:D { -1 }
        \tex_readline:D #1 to #2
        \int_set:Nn \tex_endlinechar:D
      }   { \int_use:N \tex_endlinechar:D }
  }
\prg_new_protected_conditional:Npnn \ior_str_get:NN #1#2 { T , F , TF }
  {
    \ior_if_eof:NTF #1
      { \prg_return_false: }
      {
        \@@_str_get:NN #1 #2
        \prg_return_true:
      }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{variable}{\c_@@_term_noprompt_ior}
%   For reading without a prompt.
%    \begin{macrocode}
\int_const:Nn \c_@@_term_noprompt_ior { -1 }
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\ior_get_term:nN, \ior_str_get_term:nN}
% \begin{macro}{\@@_get_term:NnN}
%   Getting from the terminal is better with pretty-printing.
%    \begin{macrocode}
\cs_new_protected:Npn \ior_get_term:nN #1#2
  { \@@_get_term:NnN \@@_get:NN {#1} #2 }
\cs_new_protected:Npn \ior_str_get_term:nN #1#2
  { \@@_get_term:NnN \@@_str_get:NN {#1} #2 }
\cs_new_protected:Npn \@@_get_term:NnN #1#2#3
  {
    \group_begin:
      \tex_escapechar:D = -1 \scan_stop:
      \tl_if_blank:nTF {#2}
        { \exp_args:NNc #1 \c_@@_term_noprompt_ior }
        { \exp_args:NNc #1 \c_@@_term_ior }
          {#2}
    \exp_args:NNNv \group_end:
    \tl_set:Nn #3 {#2}
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}[EXP]{\ior_map_break:, \ior_map_break:n}
%   Usual map breaking functions.
%    \begin{macrocode}
\cs_new:Npn \ior_map_break:
  { \prg_map_break:Nn \ior_map_break: { } }
\cs_new:Npn \ior_map_break:n
  { \prg_map_break:Nn \ior_map_break: }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\ior_map_inline:Nn, \ior_str_map_inline:Nn}
% \begin{macro}{\@@_map_inline:NNn}
% \begin{macro}{\@@_map_inline:NNNn}
% \begin{macro}{\@@_map_inline_loop:NNN}
%   Mapping to an input stream can be done on either a token or a string
%   basis, hence the set up. Within that, there is a check to avoid reading
%   past the end of a file, hence the two applications of \cs{ior_if_eof:N}
%   and its lower-level analogue \cs{if_eof:w}.
%   This mapping cannot be nested with twice the same stream, as the
%   stream has only one \enquote{current line}.
%    \begin{macrocode}
\cs_new_protected:Npn \ior_map_inline:Nn
  { \@@_map_inline:NNn \@@_get:NN }
\cs_new_protected:Npn \ior_str_map_inline:Nn
  { \@@_map_inline:NNn \@@_str_get:NN }
\cs_new_protected:Npn \@@_map_inline:NNn
  {
    \int_gincr:N \g__kernel_prg_map_int
    \exp_args:Nc \@@_map_inline:NNNn
      { @@_map_ \int_use:N \g__kernel_prg_map_int :n }
  }
\cs_new_protected:Npn \@@_map_inline:NNNn #1#2#3#4
  {
    \cs_gset_protected:Npn #1 ##1 {#4}
    \ior_if_eof:NF #3 { \@@_map_inline_loop:NNN #1#2#3 }
    \prg_break_point:Nn \ior_map_break:
      { \int_gdecr:N \g__kernel_prg_map_int }
  }
\cs_new_protected:Npn \@@_map_inline_loop:NNN #1#2#3
  {
    #2 #3 \l_@@_internal_tl
    \if_eof:w #3
      \exp_after:wN \ior_map_break:
    \fi:
    \exp_args:No #1 \l_@@_internal_tl
    \@@_map_inline_loop:NNN #1#2#3
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\ior_map_variable:NNn, \ior_str_map_variable:NNn}
% \begin{macro}{\@@_map_variable:NNNn}
% \begin{macro}{\@@_map_variable_loop:NNNn}
%   Since the \TeX{} primitive (\tn{read} or \tn{readline}) assigns the
%   tokens read in the same way as a token list assignment, we simply
%   call the appropriate primitive.  The end-of-loop is checked using
%   the primitive conditional for speed.
%    \begin{macrocode}
\cs_new_protected:Npn \ior_map_variable:NNn
  { \@@_map_variable:NNNn \ior_get:NN }
\cs_new_protected:Npn \ior_str_map_variable:NNn
  { \@@_map_variable:NNNn \ior_str_get:NN }
\cs_new_protected:Npn \@@_map_variable:NNNn #1#2#3#4
  {
    \ior_if_eof:NF #2 { \@@_map_variable_loop:NNNn #1#2#3 {#4} }
    \prg_break_point:Nn \ior_map_break: { }
  }
\cs_new_protected:Npn \@@_map_variable_loop:NNNn #1#2#3#4
  {
    #1 #2 #3
    \if_eof:w #2
      \exp_after:wN \ior_map_break:
    \fi:
    #4
    \@@_map_variable_loop:NNNn #1#2#3 {#4}
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \subsection{Output operations}
%
%    \begin{macrocode}
%<@@=iow>
%    \end{macrocode}
%
% There is a lot of similarity here to the input operations, at least for
% many of the basics. Thus quite a bit is copied from the earlier material
% with minor alterations.
%
% \subsubsection{Variables and constants}
%
% \begin{variable}{\c_log_iow, \c_term_iow}
%   Here we allocate two output streams for writing to the transcript
%   file only (\cs{c_log_iow}) and to both the terminal and transcript
%   file (\cs{c_term_iow}).  Recent \LuaTeX{} provide $128$ write
%   streams; we also use \cs{c_term_iow} as the first non-allowed write
%   stream so its value depends on the engine.
%    \begin{macrocode}
\int_const:Nn \c_log_iow  { -1 }
\int_const:Nn \c_term_iow
  {
    \bool_lazy_and:nnTF
      { \sys_if_engine_luatex_p: }
      { \int_compare_p:nNn \tex_luatexversion:D > { 80 } }
      { 128 }
      { 16 }
  }
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\g_@@_streams_seq}
%   A list of the currently-available output streams to be used as a
%   stack.  The stream $18$ is special, as \tn{write}18 is used to
%   denote commands to be sent to the OS\@.
%    \begin{macrocode}
\seq_new:N \g_@@_streams_seq
%<*initex>
\exp_args:Nnx \use:n
  { \seq_gset_split:Nnn \g_@@_streams_seq { } }
  {
    \int_step_function:nnN { 0 } { \c_term_iow }
      \prg_do_nothing:
  }
\int_compare:nNnF \c_term_iow < { 18 }
  { \seq_gremove_all:Nn \g_@@_streams_seq { 18 } }
%</initex>
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_stream_tl}
%   Used to recover the raw stream number from the stack.
%    \begin{macrocode}
\tl_new:N \l_@@_stream_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\g_@@_streams_prop}
%   As for reads with the appropriate adjustment of the register numbers to
%   check on.
%    \begin{macrocode}
\prop_new:N \g_@@_streams_prop
%<*package>
\int_step_inline:nnn
  { 0 }
  {
    \cs_if_exist:NTF \normalend
      { \tex_count:D 39 ~ }
      {
        \tex_count:D 17 ~
        \cs_if_exist:NT \loccount { - 1 }
      }
  }
  {
    \prop_gput:Nnn \g_@@_streams_prop {#1} { Reserved~by~format }
  }
%</package>
%    \end{macrocode}
% \end{variable}
%
% \subsection{Stream management}
%
% \begin{macro}{\iow_new:N, \iow_new:c}
%   Reserving a new stream is done by defining the name as equal to writing
%   to the terminal: odd but at least consistent.
%    \begin{macrocode}
\cs_new_protected:Npn \iow_new:N #1 { \cs_new_eq:NN #1 \c_term_iow }
\cs_generate_variant:Nn \iow_new:N { c }
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\g_tmpa_iow, \g_tmpb_iow}
%   The usual scratch space.
%    \begin{macrocode}
\iow_new:N \g_tmpa_iow
\iow_new:N \g_tmpb_iow
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_new:N}
%   As for read streams, copy \tn{newwrite} in package mode, making sure
%   that it is not \tn{outer}.
%    \begin{macrocode}
%<*package>
\exp_args:NNf \cs_new_protected:Npn \@@_new:N
  { \exp_args:NNc \exp_after:wN \exp_stop_f: { newwrite } }
%</package>
%    \end{macrocode}
% \end{macro}
%
% \begin{variable}{\l_@@_file_name_tl}
%  Data storage.
%    \begin{macrocode}
\tl_new:N \l_@@_file_name_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\iow_open:Nn, \iow_open:cn}
% \begin{macro}{\@@_open_stream:Nn, \@@_open_stream:NV}
%   The same idea as for reading, but without the path and without the need
%   to allow for a conditional version.
%    \begin{macrocode}
\cs_new_protected:Npn \iow_open:Nn #1#2
  {
    \tl_set:Nx \l_@@_file_name_tl
      { \__kernel_file_name_sanitize:n {#2} }
    \iow_close:N #1
    \seq_gpop:NNTF \g_@@_streams_seq \l_@@_stream_tl
      { \@@_open_stream:NV #1 \l_@@_file_name_tl }
%<*initex>
      { \__kernel_msg_fatal:nn { kernel } { output-streams-exhausted } }
%</initex>
%<*package>
      {
        \@@_new:N #1
        \tl_set:Nx \l_@@_stream_tl { \int_eval:n {#1} }
        \@@_open_stream:NV #1 \l_@@_file_name_tl
      }
%</package>
  }
\cs_generate_variant:Nn \iow_open:Nn { c }
\cs_new_protected:Npn \@@_open_stream:Nn #1#2
  {
    \tex_global:D \tex_chardef:D #1 = \l_@@_stream_tl \scan_stop:
    \prop_gput:NVn \g_@@_streams_prop #1 {#2}
    \tex_immediate:D \tex_openout:D
        #1 \__kernel_file_name_quote:n {#2} \scan_stop:
  }
\cs_generate_variant:Nn \@@_open_stream:Nn { NV }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\iow_close:N, \iow_close:c}
%   Closing a stream is not quite the reverse of opening one. First,
%   the close operation is easier than the open one, and second as the
%   stream is actually a number we can use it directly to show that the
%   slot has been freed up.
%    \begin{macrocode}
\cs_new_protected:Npn \iow_close:N #1
  {
    \int_compare:nT { - \c_log_iow < #1 < \c_term_iow }
      {
        \tex_immediate:D \tex_closeout:D #1
        \prop_gremove:NV \g_@@_streams_prop #1
        \seq_if_in:NVF \g_@@_streams_seq #1
          { \seq_gpush:NV \g_@@_streams_seq #1 }
        \cs_gset_eq:NN #1 \c_term_iow
      }
  }
\cs_generate_variant:Nn \iow_close:N { c }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\iow_show_list:, \iow_log_list:}
% \begin{macro}{\@@_list:N}
%   Done as for input, but with a copy of the auxiliary so the name is correct.
%    \begin{macrocode}
\cs_new_protected:Npn \iow_show_list: { \@@_list:N \msg_show:nnxxxx }
\cs_new_protected:Npn \iow_log_list: { \@@_list:N \msg_log:nnxxxx }
\cs_new_protected:Npn \@@_list:N #1
  {
    #1 { LaTeX / kernel } { show-streams }
      { iow }
      {
        \prop_map_function:NN \g_@@_streams_prop
          \msg_show_item_unbraced:nn
      }
      { } { }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \subsubsection{Deferred writing}
%
% \begin{macro}{\iow_shipout_x:Nn, \iow_shipout_x:Nx, \iow_shipout_x:cn, \iow_shipout_x:cx}
%   First the easy part, this is the primitive, which expects its
%   argument to be braced.
%    \begin{macrocode}
\cs_new_protected:Npn \iow_shipout_x:Nn #1#2
  { \tex_write:D #1 {#2} }
\cs_generate_variant:Nn \iow_shipout_x:Nn { c, Nx, cx }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\iow_shipout:Nn, \iow_shipout:Nx, \iow_shipout:cn, \iow_shipout:cx}
%   With \eTeX{} available deferred writing without expansion is easy.
%    \begin{macrocode}
\cs_new_protected:Npn \iow_shipout:Nn #1#2
  { \tex_write:D #1 { \exp_not:n {#2} } }
\cs_generate_variant:Nn \iow_shipout:Nn { c, Nx, cx }
%    \end{macrocode}
% \end{macro}
%
% \subsubsection{Immediate writing}
%
% \begin{macro}{\__kernel_iow_with:Nnn}
% \begin{macro}{\@@_with:nNnn}
%   If the integer~|#1| is equal to~|#2|, just leave~|#3| in the input
%   stream.  Otherwise, pass the old value to an auxiliary, which sets
%   the integer to the new value, runs the code, and restores the
%   integer.
%    \begin{macrocode}
\cs_new_protected:Npn \__kernel_iow_with:Nnn #1#2
  {
    \int_compare:nNnTF {#1} = {#2}
      { \use:n }
      { \exp_args:No \@@_with:nNnn { \int_use:N #1 } #1 {#2} }
  }
\cs_new_protected:Npn \@@_with:nNnn #1#2#3#4
  {
    \int_set:Nn #2 {#3}
    #4
    \int_set:Nn #2 {#1}
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\iow_now:Nn, \iow_now:Nx, \iow_now:cn, \iow_now:cx}
%   This routine writes the second argument onto the output stream without
%   expansion. If this stream isn't open, the output goes to the terminal
%   instead. If the first argument is no output stream at all, we get an
%   internal error.  We don't use the expansion done by \tn{write} to
%   get the |Nx| variant, because it differs in subtle ways from
%   \texttt{x}-expansion, namely, macro parameter characters would not
%   need to be doubled.  We set the \tn{newlinechar} to~$10$ using
%   \cs{__kernel_iow_with:Nnn} to support formats such as plain \TeX{}: otherwise,
%   \cs{iow_newline:} would not work.  We do not do this for
%   \cs{iow_shipout:Nn} or \cs{iow_shipout_x:Nn}, as \TeX{} looks at the
%   value of the \tn{newlinechar} at shipout time in those cases.
%    \begin{macrocode}
\cs_new_protected:Npn \iow_now:Nn #1#2
  {
    \__kernel_iow_with:Nnn \tex_newlinechar:D { `\^^J }
      { \tex_immediate:D \tex_write:D #1 { \exp_not:n {#2} } }
  }
\cs_generate_variant:Nn \iow_now:Nn { c, Nx, cx }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\iow_log:n, \iow_log:x}
% \begin{macro}{\iow_term:n, \iow_term:x}
%   Writing to the log and the terminal directly are relatively easy.
%    \begin{macrocode}
\cs_set_protected:Npn \iow_log:x  { \iow_now:Nx \c_log_iow  }
\cs_new_protected:Npn \iow_log:n  { \iow_now:Nn \c_log_iow  }
\cs_set_protected:Npn \iow_term:x { \iow_now:Nx \c_term_iow }
\cs_new_protected:Npn \iow_term:n { \iow_now:Nn \c_term_iow }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \subsubsection{Special characters for writing}
%
% \begin{macro}{\iow_newline:}
%   Global variable holding the character that forces a new line when
%   something is written to an output stream.
%    \begin{macrocode}
\cs_new:Npn \iow_newline: { ^^J }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\iow_char:N}
%   Function to write any escaped char to an output stream.
%    \begin{macrocode}
\cs_new_eq:NN \iow_char:N \cs_to_str:N
%    \end{macrocode}
% \end{macro}
%
% \subsubsection{Hard-wrapping lines to a character count}
%
% The code here implements a generic hard-wrapping function. This is
% used by the messaging system, but is designed such that it is
% available for other uses.
%
% \begin{variable}{\l_iow_line_count_int}
%   This is the \enquote{raw} number of characters in a line which
%   can be written to the terminal.
%   The standard value is the line length typically used by
%   \TeX{}Live and \hologo{MiKTeX}.
%    \begin{macrocode}
\int_new:N  \l_iow_line_count_int
\int_set:Nn \l_iow_line_count_int { 78 }
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_newline_tl}
%   The token list inserted to produce a new line, with the
%   \meta{run-on text}.
%    \begin{macrocode}
\tl_new:N \l_@@_newline_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_line_target_int}
%   This stores the target line count: the full number of characters
%   in a line, minus any part for a leader at the start of each line.
%    \begin{macrocode}
\int_new:N \l_@@_line_target_int
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}{\@@_set_indent:n}
% \begin{macro}{\@@_unindent:w}
% \begin{variable}{\l_@@_one_indent_tl, \l_@@_one_indent_int}
%   The \texttt{one_indent} variables hold one indentation marker and
%   its length.  The \cs{@@_unindent:w} auxiliary removes one
%   indentation.  The function \cs{@@_set_indent:n} (that could possibly
%   be public) sets the indentation in a consistent way.  We set it to
%   four spaces by default.
%    \begin{macrocode}
\tl_new:N \l_@@_one_indent_tl
\int_new:N \l_@@_one_indent_int
\cs_new:Npn \@@_unindent:w { }
\cs_new_protected:Npn \@@_set_indent:n #1
  {
    \tl_set:Nx \l_@@_one_indent_tl
      { \exp_args:No \__kernel_str_to_other_fast:n { \tl_to_str:n {#1} } }
    \int_set:Nn \l_@@_one_indent_int
      { \str_count:N \l_@@_one_indent_tl }
    \exp_last_unbraced:NNo
      \cs_set:Npn \@@_unindent:w \l_@@_one_indent_tl { }
  }
\exp_args:Nx \@@_set_indent:n { \prg_replicate:nn { 4 } { ~ } }
%    \end{macrocode}
% \end{variable}
% \end{macro}
% \end{macro}
%
% \begin{variable}{\l_@@_indent_tl, \l_@@_indent_int}
%   The current indentation (some copies of \cs{l_@@_one_indent_tl}) and
%   its number of characters.
%    \begin{macrocode}
\tl_new:N \l_@@_indent_tl
\int_new:N \l_@@_indent_int
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_line_tl, \l_@@_line_part_tl}
%   These hold the current line of text and a partial line to be added
%   to it, respectively.
%    \begin{macrocode}
\tl_new:N \l_@@_line_tl
\tl_new:N \l_@@_line_part_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_line_break_bool}
%   Indicates whether the line was broken precisely at a chunk boundary.
%    \begin{macrocode}
\bool_new:N \l_@@_line_break_bool
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_wrap_tl}
%   Used for the expansion step before detokenizing, and for the output
%   from wrapping text: fully expanded and with lines which are not
%   overly long.
%    \begin{macrocode}
\tl_new:N \l_@@_wrap_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\c_@@_wrap_marker_tl}
% \begin{variable}
%   {
%     \c_@@_wrap_end_marker_tl,
%     \c_@@_wrap_newline_marker_tl,
%     \c_@@_wrap_allow_break_marker_tl,
%     \c_@@_wrap_indent_marker_tl,
%     \c_@@_wrap_unindent_marker_tl
%   }
%   Every special action of the wrapping code is starts with
%   the same recognizable string, \cs{c_@@_wrap_marker_tl}.
%   Upon seeing that \enquote{word}, the wrapping code reads
%   one space-delimited argument to know what operation to
%   perform. The setting of \tn{escapechar} here is not
%   very important, but makes \cs{c_@@_wrap_marker_tl} look
%   marginally nicer.
%    \begin{macrocode}
\group_begin:
  \int_set:Nn \tex_escapechar:D { -1 }
  \tl_const:Nx \c_@@_wrap_marker_tl
    { \tl_to_str:n { \^^I \^^O \^^W \^^_ \^^W \^^R \^^A \^^P } }
\group_end:
\tl_map_inline:nn
  { { end } { newline } { allow_break } { indent } { unindent } }
  {
    \tl_const:cx { c_@@_wrap_ #1 _marker_tl }
      {
        \c_@@_wrap_marker_tl
        #1
        \c_catcode_other_space_tl
      }
  }
%    \end{macrocode}
% \end{variable}
% \end{variable}
%
% \begin{macro}{\iow_allow_break:}
% \begin{macro}[EXP]{\@@_allow_break:}
% \begin{macro}[EXP]{\@@_allow_break_error:}
%   We set \cs{iow_allow_break:n} to produce an error when outside
%   messages. Within wrapped message, it is set to \cs{@@_allow_break:}
%   when valid and otherwise to \cs{@@_allow_break_error:}.  The second
%   produces an error expandably.
%    \begin{macrocode}
\cs_new_protected:Npn \iow_allow_break:
  {
    \__kernel_msg_error:nnnn { kernel } { iow-indent }
      { \iow_wrap:nnnN } { \iow_allow_break: }
  }
\cs_new:Npx \@@_allow_break: { \c_@@_wrap_allow_break_marker_tl }
\cs_new:Npn \@@_allow_break_error:
  {
    \__kernel_msg_expandable_error:nnnn { kernel } { iow-indent }
      { \iow_wrap:nnnN } { \iow_allow_break: }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\iow_indent:n}
% \begin{macro}[EXP]{\@@_indent:n}
% \begin{macro}[EXP]{\@@_indent_error:n}
%   We set \cs{iow_indent:n} to produce an error when outside
%   messages. Within wrapped message, it is set to \cs{@@_indent:n} when
%   valid and otherwise to \cs{@@_indent_error:n}.  The first places the
%   instruction for increasing the indentation before its argument, and
%   the instruction for unindenting afterwards.  The second produces an
%   error expandably.  Note that there are no forced line-break, so
%   the indentation only changes when the next line is started.
%    \begin{macrocode}
\cs_new_protected:Npn \iow_indent:n #1
  {
    \__kernel_msg_error:nnnnn { kernel } { iow-indent }
      { \iow_wrap:nnnN } { \iow_indent:n } {#1}
    #1
  }
\cs_new:Npx \@@_indent:n #1
  {
    \c_@@_wrap_indent_marker_tl
    #1
    \c_@@_wrap_unindent_marker_tl
  }
\cs_new:Npn \@@_indent_error:n #1
  {
    \__kernel_msg_expandable_error:nnnnn { kernel } { iow-indent }
      { \iow_wrap:nnnN } { \iow_indent:n } {#1}
    #1
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\iow_wrap:nnnN, \iow_wrap:nxnN}
%   The main wrapping function works as follows.  First give |\\|,
%   \verb*|\ | and other formatting commands the correct definition for
%   messages and perform the given setup~|#3|.  The definition of
%   \verb*|\ | uses an \enquote{other} space rather than a normal space,
%   because the latter might be absorbed by \TeX{} to end a number or
%   other \texttt{f}-type expansions.
%   Use \cs{conditionally@traceoff} if defined; it is introduced by the
%   \pkg{trace} package and suppresses uninteresting tracing of the
%   wrapping code.
%    \begin{macrocode}
\cs_new_protected:Npn \iow_wrap:nnnN #1#2#3#4
  {
    \group_begin:
%<package>      \cs_if_exist_use:N \conditionally@traceoff
      \int_set:Nn \tex_escapechar:D { -1 }
      \cs_set:Npx \{ { \token_to_str:N \{ }
      \cs_set:Npx \# { \token_to_str:N \# }
      \cs_set:Npx \} { \token_to_str:N \} }
      \cs_set:Npx \% { \token_to_str:N \% }
      \cs_set:Npx \~ { \token_to_str:N \~ }
      \int_set:Nn \tex_escapechar:D { 92 }
      \cs_set_eq:NN \\ \iow_newline:
      \cs_set_eq:NN \  \c_catcode_other_space_tl
      \cs_set_eq:NN \iow_allow_break: \@@_allow_break:
      \cs_set_eq:NN \iow_indent:n \@@_indent:n
      #3
%    \end{macrocode}
%   Then fully-expand the input: in package mode, the expansion uses
%   \LaTeXe{}'s \tn{protect} mechanism in the same way as \tn{typeout}.
%   In generic mode this setting is useless but harmless.  As soon as
%   the expansion is done, reset \cs{iow_indent:n} to its error
%   definition: it only works in the first argument of
%   \cs{iow_wrap:nnnN}.
%    \begin{macrocode}
%<package>      \cs_set_eq:NN \protect \token_to_str:N
      \tl_set:Nx \l_@@_wrap_tl {#1}
      \cs_set_eq:NN \iow_allow_break: \@@_allow_break_error:
      \cs_set_eq:NN \iow_indent:n \@@_indent_error:n
%    \end{macrocode}
%   Afterwards, set the newline marker (two assignments to fully expand,
%   then convert to a string) and initialize the target count for lines
%   (the first line has target count \cs{l_iow_line_count_int} instead).
%    \begin{macrocode}
      \tl_set:Nx \l_@@_newline_tl { \iow_newline: #2 }
      \tl_set:Nx \l_@@_newline_tl { \tl_to_str:N \l_@@_newline_tl }
      \int_set:Nn \l_@@_line_target_int
        { \l_iow_line_count_int - \str_count:N \l_@@_newline_tl + 1 }
%    \end{macrocode}
%   Sanity check.
%    \begin{macrocode}
       \int_compare:nNnT { \l_@@_line_target_int } < 0
         {
           \tl_set:Nn \l_@@_newline_tl { \iow_newline: }
           \int_set:Nn \l_@@_line_target_int
             { \l_iow_line_count_int + 1 }
         }
%    \end{macrocode}
%   There is then a loop over the input, which stores the wrapped
%   result in \cs{l_@@_wrap_tl}.  After the loop, the resulting text is
%   passed on to the function which has been given as a post-processor.
%   The \cs{tl_to_str:N} step converts the \enquote{other} spaces back
%   to normal spaces.  The \texttt{f}-expansion removes a leading space
%   from \cs{l_@@_wrap_tl}.
%    \begin{macrocode}
      \@@_wrap_do:
    \exp_args:NNf \group_end:
    #4 { \tl_to_str:N \l_@@_wrap_tl }
  }
\cs_generate_variant:Nn \iow_wrap:nnnN { nx }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_wrap_do:, \@@_wrap_fix_newline:w, \@@_wrap_start:w}
%   Escape spaces and change newlines to \cs{c_@@_wrap_newline_marker_tl}.
%   Set up a few variables, in particular the initial
%   value of \cs{l_@@_wrap_tl}: the space stops the
%   \texttt{f}-expansion of the main wrapping function and
%   \cs{use_none:n} removes a newline marker inserted by later code.
%   The main loop consists of repeatedly calling the \texttt{chunk}
%   auxiliary to wrap chunks delimited by (newline or indentation)
%   markers.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_wrap_do:
  {
    \tl_set:Nx \l_@@_wrap_tl
      {
        \exp_args:No \__kernel_str_to_other_fast:n \l_@@_wrap_tl
        \c_@@_wrap_end_marker_tl
      }
    \tl_set:Nx \l_@@_wrap_tl
      {
        \exp_after:wN \@@_wrap_fix_newline:w \l_@@_wrap_tl
          ^^J \q_nil ^^J \q_stop
      }
    \exp_after:wN \@@_wrap_start:w \l_@@_wrap_tl
  }
\cs_new:Npn \@@_wrap_fix_newline:w #1 ^^J #2 ^^J
  {
    #1
    \if_meaning:w \q_nil #2
      \use_i_delimit_by_q_stop:nw
    \fi:
    \c_@@_wrap_newline_marker_tl
    \@@_wrap_fix_newline:w #2 ^^J
  }
\cs_new_protected:Npn \@@_wrap_start:w
  {
    \bool_set_false:N \l_@@_line_break_bool
    \tl_clear:N \l_@@_line_tl
    \tl_clear:N \l_@@_line_part_tl
    \tl_set:Nn \l_@@_wrap_tl { ~ \use_none:n }
    \int_zero:N \l_@@_indent_int
    \tl_clear:N \l_@@_indent_tl
    \@@_wrap_chunk:nw { \l_iow_line_count_int }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_wrap_chunk:nw, \@@_wrap_next:nw}
%   The \texttt{chunk} and \texttt{next} auxiliaries are defined
%   indirectly to obtain the expansions of \cs{c_catcode_other_space_tl}
%   and \cs{c_@@_wrap_marker_tl} in their definition.  The \texttt{next}
%   auxiliary calls a function corresponding to the type of marker (its
%   |##2|), which can be \texttt{newline} or \texttt{indent} or
%   \texttt{unindent} or \texttt{end}.  The first argument of the
%   \texttt{chunk} auxiliary is a target number of characters and the
%   second is some string to wrap.  If the chunk is empty simply call
%   \texttt{next}.  Otherwise, set up a call to \cs{@@_wrap_line:nw},
%   including the indentation if the current line is empty, and
%   including a trailing space (|#1|) before the
%   \cs{@@_wrap_end_chunk:w} auxiliary.
%    \begin{macrocode}
\cs_set_protected:Npn \@@_tmp:w #1#2
  {
    \cs_new_protected:Npn \@@_wrap_chunk:nw ##1##2 #2
      {
        \tl_if_empty:nTF {##2}
          {
            \tl_clear:N \l_@@_line_part_tl
            \@@_wrap_next:nw {##1}
          }
          {
            \tl_if_empty:NTF \l_@@_line_tl
              {
                \@@_wrap_line:nw
                  { \l_@@_indent_tl }
                  ##1 - \l_@@_indent_int ;
              }
              { \@@_wrap_line:nw { } ##1 ; }
            ##2 #1
            \@@_wrap_end_chunk:w 7 6 5 4 3 2 1 0 \q_stop
          }
      }
    \cs_new_protected:Npn \@@_wrap_next:nw ##1##2 #1
      { \use:c { @@_wrap_##2:n } {##1} }
  }
\exp_args:NVV \@@_tmp:w \c_catcode_other_space_tl \c_@@_wrap_marker_tl
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_wrap_line:nw}
% \begin{macro}[EXP]
%   {
%     \@@_wrap_line_loop:w,
%     \@@_wrap_line_aux:Nw,
%     \@@_wrap_line_seven:nnnnnnn,
%     \@@_wrap_line_end:NnnnnnnnN,
%     \@@_wrap_line_end:nw,
%     \@@_wrap_end_chunk:w
%   }
%   This is followed by \Arg{string} \meta{intexpr} |;|.  It stores the
%   \meta{string} and up to \meta{intexpr} characters from the current
%   chunk into \cs{l_@@_line_part_tl}.  Characters are grabbed 8~at a
%   time and left in \cs{l_@@_line_part_tl} by the \texttt{line_loop}
%   auxiliary.  When $k<8$ remain to be found, the \texttt{line_aux}
%   auxiliary calls the \texttt{line_end} auxiliary followed by (the
%   single digit) $k$, then $7-k$ empty brace groups, then the chunk's
%   remaining characters.  The \texttt{line_end} auxiliary leaves
%   $k$~characters from the chunk in the line part, then ends the
%   assignment.  Ignore the \cs{use_none:nnnnn} line for now.  If the
%   next character is a space the line can be broken there:
%   store what we found into the result and get the next line.
%   Otherwise some work is needed to find a break-point.  So far we have
%   ignored what happens if the chunk is shorter than the requested
%   number of characters: this is dealt with by the \texttt{end_chunk}
%   auxiliary, which gets treated like a character by the rest of the
%   code.  It ends up being called either as one of the arguments
%   |#2|--|#9| of the \texttt{line_loop} auxiliary or as one of the
%   arguments |#2|--|#8| of the \texttt{line_end} auxiliary.  In both
%   cases stop the assignment and work out how many characters are still
%   needed. Notice that when we have exactly seven arguments to clean up,
%   a \cs{exp_stop_f:} has to be inserted to stop the \cs{exp:w}.
%   The weird \cs{use_none:nnnnn} ensures that the required
%   data is in the right place.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_wrap_line:nw #1
  {
    \tex_edef:D \l_@@_line_part_tl { \if_false: } \fi:
    #1
    \exp_after:wN \@@_wrap_line_loop:w
    \int_value:w \int_eval:w
  }
\cs_new:Npn \@@_wrap_line_loop:w #1 ; #2#3#4#5#6#7#8#9
  {
    \if_int_compare:w #1 < 8 \exp_stop_f:
      \@@_wrap_line_aux:Nw #1
    \fi:
    #2 #3 #4 #5 #6 #7 #8 #9
    \exp_after:wN \@@_wrap_line_loop:w
    \int_value:w \int_eval:w #1 - 8 ;
  }
\cs_new:Npn \@@_wrap_line_aux:Nw #1#2#3 \exp_after:wN #4 ;
  {
    #2
    \exp_after:wN \@@_wrap_line_end:NnnnnnnnN
    \exp_after:wN #1
    \exp:w \exp_end_continue_f:w
    \exp_after:wN \exp_after:wN
    \if_case:w #1 \exp_stop_f:
         \prg_do_nothing:
    \or: \use_none:n
    \or: \use_none:nn
    \or: \use_none:nnn
    \or: \use_none:nnnn
    \or: \use_none:nnnnn
    \or: \use_none:nnnnnn
    \or: \@@_wrap_line_seven:nnnnnnn
    \fi:
    { } { } { } { } { } { } { } #3
  }
\cs_new:Npn \@@_wrap_line_seven:nnnnnnn #1#2#3#4#5#6#7 { \exp_stop_f: }
\cs_new:Npn \@@_wrap_line_end:NnnnnnnnN #1#2#3#4#5#6#7#8#9
  {
    #2 #3 #4 #5 #6 #7 #8
    \use_none:nnnnn \int_eval:w 8 - ; #9
    \token_if_eq_charcode:NNTF \c_space_token #9
      { \@@_wrap_line_end:nw { } }
      { \if_false: { \fi: } \@@_wrap_break:w #9 }
  }
\cs_new:Npn \@@_wrap_line_end:nw #1
  {
    \if_false: { \fi: }
    \@@_wrap_store_do:n {#1}
    \@@_wrap_next_line:w
  }
\cs_new:Npn \@@_wrap_end_chunk:w
    #1 \int_eval:w #2 - #3 ; #4#5 \q_stop
  {
    \if_false: { \fi: }
    \exp_args:Nf \@@_wrap_next:nw { \int_eval:n { #2 - #4 } }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}[EXP]{\@@_wrap_break:w}
% \begin{macro}[EXP]
%   {
%     \@@_wrap_break_first:w,
%     \@@_wrap_break_none:w,
%     \@@_wrap_break_loop:w,
%     \@@_wrap_break_end:w,
%   }
%   Functions here are defined indirectly: \cs{@@_tmp:w} is eventually
%   called with an \enquote{other} space as its argument.  The goal is
%   to remove from \cs{l_@@_line_part_tl} the part after the last space.
%   In most cases this is done by repeatedly calling the
%   \texttt{break_loop} auxiliary, which leaves \enquote{words}
%   (delimited by spaces) until it hits the trailing space: then its
%   argument |##3| is |?| \cs{@@_wrap_break_end:w} instead of a single
%   token, and that \texttt{break_end} auxiliary leaves in the
%   assignment the line until the last space, then calls
%   \cs{@@_wrap_line_end:nw} to finish up the line and move on to the
%   next.  If there is no space in \cs{l_@@_line_part_tl} then the
%   \texttt{break_first} auxiliary calls the \texttt{break_none}
%   auxiliary.  In that case, if the current line is empty, the complete
%   word (including |##4|, characters beyond what we had grabbed) is
%   added to the line, making it over-long.  Otherwise, the word is
%   used for the following line (and the last space of the line so far
%   is removed because it was inserted due to the presence of a marker).
%    \begin{macrocode}
\cs_set_protected:Npn \@@_tmp:w #1
  {
    \cs_new:Npn \@@_wrap_break:w
      {
        \tex_edef:D \l_@@_line_part_tl
          { \if_false: } \fi:
            \exp_after:wN \@@_wrap_break_first:w
            \l_@@_line_part_tl
            #1
            { ? \@@_wrap_break_end:w }
            \q_mark
      }
    \cs_new:Npn \@@_wrap_break_first:w ##1 #1 ##2
      {
        \use_none:nn ##2 \@@_wrap_break_none:w
        \@@_wrap_break_loop:w ##1 #1 ##2
      }
    \cs_new:Npn \@@_wrap_break_none:w ##1##2 #1 ##3 \q_mark ##4 #1
      {
        \tl_if_empty:NTF \l_@@_line_tl
          { ##2 ##4 \@@_wrap_line_end:nw { } }
          { \@@_wrap_line_end:nw { \@@_wrap_trim:N } ##2 ##4 #1 }
      }
    \cs_new:Npn \@@_wrap_break_loop:w ##1 #1 ##2 #1 ##3
      {
        \use_none:n ##3
        ##1 #1
        \@@_wrap_break_loop:w ##2 #1 ##3
      }
    \cs_new:Npn \@@_wrap_break_end:w ##1 #1 ##2 ##3 #1 ##4 \q_mark
      { ##1 \@@_wrap_line_end:nw { } ##3 }
  }
\exp_args:NV \@@_tmp:w \c_catcode_other_space_tl
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_wrap_next_line:w}
%   The special case where the end of a line coincides with the end of a
%   chunk is detected here, to avoid a spurious empty line.  Otherwise,
%   call \cs{@@_wrap_line:nw} to find characters for the next line
%   (remembering to account for the indentation).
%    \begin{macrocode}
\cs_new_protected:Npn \@@_wrap_next_line:w #1#2 \q_stop
  {
    \tl_clear:N \l_@@_line_tl
    \token_if_eq_meaning:NNTF #1 \@@_wrap_end_chunk:w
      {
        \tl_clear:N \l_@@_line_part_tl
        \bool_set_true:N \l_@@_line_break_bool
        \@@_wrap_next:nw { \l_@@_line_target_int }
      }
      {
        \@@_wrap_line:nw
          { \l_@@_indent_tl }
          \l_@@_line_target_int - \l_@@_indent_int ;
          #1 #2 \q_stop
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_wrap_allow_break:n}
%   This is called after a chunk has been wrapped.  The
%   \cs{l_@@_line_part_tl} typically ends with a space (except at the
%   beginning of a line?), which we remove since the
%   \texttt{allow\_break} marker should not insert a space.  Then move
%   on with the next chunk, making sure to adjust the target number of
%   characters for the line in case we did remove a space.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_wrap_allow_break:n #1
  {
    \tl_set:Nx \l_@@_line_tl
      { \l_@@_line_tl \@@_wrap_trim:N \l_@@_line_part_tl }
    \bool_set_false:N \l_@@_line_break_bool
    \tl_if_empty:NTF \l_@@_line_part_tl
      { \@@_wrap_chunk:nw {#1} }
      { \exp_args:Nf \@@_wrap_chunk:nw { \int_eval:n { #1 + 1 } } }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_wrap_indent:n, \@@_wrap_unindent:n}
%   These functions are called after a chunk has been wrapped, when
%   encountering \texttt{indent}/\texttt{unindent} markers.  Add the
%   line part (last line part of the previous chunk) to the line so far
%   and reset a boolean denoting the presence of a line-break.  Most
%   importantly, add or remove one indent from the current indent (both
%   the integer and the token list).  Finally, continue wrapping.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_wrap_indent:n #1
  {
    \tl_put_right:Nx \l_@@_line_tl { \l_@@_line_part_tl }
    \bool_set_false:N \l_@@_line_break_bool
    \int_add:Nn \l_@@_indent_int { \l_@@_one_indent_int }
    \tl_put_right:No \l_@@_indent_tl { \l_@@_one_indent_tl }
    \@@_wrap_chunk:nw {#1}
  }
\cs_new_protected:Npn \@@_wrap_unindent:n #1
  {
    \tl_put_right:Nx \l_@@_line_tl { \l_@@_line_part_tl }
    \bool_set_false:N \l_@@_line_break_bool
    \int_sub:Nn \l_@@_indent_int { \l_@@_one_indent_int }
    \tl_set:Nx \l_@@_indent_tl
      { \exp_after:wN \@@_unindent:w \l_@@_indent_tl }
    \@@_wrap_chunk:nw {#1}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_wrap_newline:n, \@@_wrap_end:n}
%   These functions are called after a chunk has been line-wrapped, when
%   encountering a \texttt{newline}/\texttt{end} marker.  Unless we just
%   took a line-break, store the line part and the line so far into the
%   whole \cs{l_@@_wrap_tl}, trimming a trailing space.  In the
%   \texttt{newline} case look for a new line (of length
%   \cs{l_@@_line_target_int}) in a new chunk.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_wrap_newline:n #1
  {
    \bool_if:NF \l_@@_line_break_bool
      { \@@_wrap_store_do:n { \@@_wrap_trim:N } }
    \bool_set_false:N \l_@@_line_break_bool
    \@@_wrap_chunk:nw { \l_@@_line_target_int }
  }
\cs_new_protected:Npn \@@_wrap_end:n #1
  {
    \bool_if:NF \l_@@_line_break_bool
      { \@@_wrap_store_do:n { \@@_wrap_trim:N } }
    \bool_set_false:N \l_@@_line_break_bool
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\@@_wrap_store_do:n}
%   First add the last line part to the line, then append it to
%   \cs{l_@@_wrap_tl} with the appropriate new line (with
%   \enquote{run-on} text), possibly with its last space removed (|#1|
%   is empty or \cs{@@_wrap_trim:N}).
%    \begin{macrocode}
\cs_new_protected:Npn \@@_wrap_store_do:n #1
  {
    \tl_set:Nx \l_@@_line_tl
      { \l_@@_line_tl \l_@@_line_part_tl }
    \tl_set:Nx \l_@@_wrap_tl
      {
        \l_@@_wrap_tl
        \l_@@_newline_tl
        #1 \l_@@_line_tl
      }
    \tl_clear:N \l_@@_line_tl
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]{\@@_wrap_trim:N, \@@_wrap_trim:w, \@@_wrap_trim_aux:w}
%   Remove one trailing \enquote{other} space from the argument if present.
%    \begin{macrocode}
\cs_set_protected:Npn \@@_tmp:w #1
  {
    \cs_new:Npn \@@_wrap_trim:N ##1
      { \exp_after:wN \@@_wrap_trim:w ##1 \q_mark #1 \q_mark \q_stop }
    \cs_new:Npn \@@_wrap_trim:w ##1 #1 \q_mark
      { \@@_wrap_trim_aux:w ##1 \q_mark }
    \cs_new:Npn \@@_wrap_trim_aux:w ##1 \q_mark ##2 \q_stop {##1}
  }
\exp_args:NV \@@_tmp:w \c_catcode_other_space_tl
%    \end{macrocode}
% \end{macro}
%
%    \begin{macrocode}
%<@@=file>
%    \end{macrocode}
%
% \subsection{File operations}
%
% \begin{variable}{\l_@@_internal_tl}
%   Used as a short-term scratch variable.
%    \begin{macrocode}
\tl_new:N \l_@@_internal_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}
%   {
%     \g_file_curr_dir_str ,
%     \g_file_curr_ext_str ,
%     \g_file_curr_name_str
%   }
%   The name of the current file should be available at all times:
%   the name itself is set dynamically.
%    \begin{macrocode}
\str_new:N \g_file_curr_dir_str
\str_new:N \g_file_curr_ext_str
\str_new:N \g_file_curr_name_str
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\g_@@_stack_seq}
%   The input list of files is stored as a sequence stack. In package
%   mode we can recover the information from the details held by
%   \LaTeXe{} (we must be in the preamble and loaded using \tn{usepackage}
%   or \tn{RequirePackage}). As \LaTeXe{} doesn't store directory and
%   name separately, we stick to the same convention here. In pre-loading,
%   \tn{@currnamestack} is empty so is skipped.
%    \begin{macrocode}
\seq_new:N \g_@@_stack_seq
%<*package>
\group_begin:
  \cs_set_protected:Npn \@@_tmp:w #1#2#3
    {
      \tl_if_blank:nTF {#1}
        {
          \cs_set:Npn \@@_tmp:w ##1 " ##2 " ##3 \q_stop
            { { } {##2} {  } }
          \seq_gput_right:Nx \g_@@_stack_seq
            {
              \exp_after:wN \@@_tmp:w \tex_jobname:D
                " \tex_jobname:D " \q_stop
            }
        }
        {
          \seq_gput_right:Nn \g_@@_stack_seq { { } {#1} {#2} }
          \@@_tmp:w
        }
    }
  \cs_if_exist:NT \@currnamestack
    {
      \tl_if_empty:NF \@currnamestack
        { \exp_after:wN \@@_tmp:w \@currnamestack }
    }
\group_end:
%</package>
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\g_@@_record_seq}
%   The total list of files used is recorded separately from the current
%   file stack, as nothing is ever popped from this list.  The current
%   file name should be included in the file list!  In format mode, this
%   is done at the very start of the \TeX{} run.  In package mode we
%   will eventually copy the contents of \cs{@filelist}.
%    \begin{macrocode}
\seq_new:N \g_@@_record_seq
%<*initex>
\tex_everyjob:D \exp_after:wN
  {
    \tex_the:D \tex_everyjob:D
    \seq_gput_right:NV \g_@@_record_seq \g_file_curr_name_str
  }
%</initex>
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_base_name_tl, \l_@@_full_name_tl}
%   For storing the basename and full path whilst passing data internally.
%    \begin{macrocode}
\tl_new:N \l_@@_base_name_tl
\tl_new:N \l_@@_full_name_tl
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_dir_str, \l_@@_ext_str, \l_@@_name_str}
%   Used in parsing a path into parts: in contrast to the above, these are
%   never used outside of the current module.
%    \begin{macrocode}
\str_new:N \l_@@_dir_str
\str_new:N \l_@@_ext_str
\str_new:N \l_@@_name_str
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_file_search_path_seq}
%   The current search path.
%    \begin{macrocode}
\seq_new:N \l_file_search_path_seq
%    \end{macrocode}
% \end{variable}
%
% \begin{variable}{\l_@@_tmp_seq}
%   Scratch space for comma list conversion in package mode.
%    \begin{macrocode}
%<*package>
\seq_new:N \l_@@_tmp_seq
%</package>
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}[EXP]{\__kernel_file_name_sanitize:n}
% \begin{macro}[EXP]{\__kernel_file_name_expand_loop:w}
% \begin{macro}[EXP]{\__kernel_file_name_expand_N_type:Nw}
% \begin{macro}[EXP]{\__kernel_file_name_expand_group:nw}
% \begin{macro}[EXP]{\__kernel_file_name_expand_space:w}
% \begin{macro}[EXP]{\__kernel_file_name_strip_quotes:n}
% \begin{macro}[EXP]{\__kernel_file_name_strip_quotes:nnnw}
% \begin{macro}[EXP]{\__kernel_file_name_strip_quotes:nnn}
% \begin{macro}[EXP]{\__kernel_file_name_trim_spaces:n}
% \begin{macro}[EXP]{\__kernel_file_name_trim_spaces:nw}
% \begin{macro}[EXP]{\__kernel_file_name_trim_spaces_aux:n}
% \begin{macro}[EXP]{\__kernel_file_name_trim_spaces_aux:w}
%   Expanding the file name without expanding active characters is done
%   using the same token-by-token approach as for example case changing.
%   The finale outcome only need be \texttt{e}-type expandable, so there
%   is no need for the shuffling that is seen in other locations.
%    \begin{macrocode}
\cs_new:Npn \__kernel_file_name_sanitize:n #1
  {
    \exp_args:Ne \__kernel_file_name_trim_spaces:n
      {
        \exp_args:Ne \__kernel_file_name_strip_quotes:n
          {
            \__kernel_file_name_expand_loop:w #1
              \q_recursion_tail \q_recursion_stop
          }
      }
  }
\cs_new:Npn \__kernel_file_name_expand_loop:w #1 \q_recursion_stop
  {
    \tl_if_head_is_N_type:nTF {#1}
      { \__kernel_file_name_expand_N_type:Nw }
      {
        \tl_if_head_is_group:nTF {#1}
          { \__kernel_file_name_expand_group:nw }
          { \__kernel_file_name_expand_space:w }
      }
    #1 \q_recursion_stop
  }
\cs_new:Npn \__kernel_file_name_expand_N_type:Nw #1
  {
    \quark_if_recursion_tail_stop:N #1
    \bool_lazy_and:nnTF
      { \token_if_expandable_p:N #1 }
      {
        \bool_not_p:n
          {
            \bool_lazy_any_p:n
              {
                { \token_if_protected_macro_p:N #1 }
                { \token_if_protected_long_macro_p:N #1 }
                { \token_if_active_p:N #1 }
              }
          }
      }
      { \exp_after:wN \__kernel_file_name_expand_loop:w #1 }
      {
        \token_to_str:N #1
        \__kernel_file_name_expand_loop:w
      }
  }
\cs_new:Npx \__kernel_file_name_expand_group:nw #1
  {
    \c_left_brace_str 
    \exp_not:N \__kernel_file_name_expand_loop:w
     #1
     \c_right_brace_str 
  }
\exp_last_unbraced:NNo
  \cs_new:Npx \__kernel_file_name_expand_space:w \c_space_tl
    {
      \c_space_tl
      \exp_not:N \__kernel_file_name_expand_loop:w
    }
%    \end{macrocode}
%   Quoting file name uses basically the same approach as for
%   \texttt{luaquotejobname}: count the |"| tokens and remove them.
%    \begin{macrocode}
\cs_new:Npn \__kernel_file_name_strip_quotes:n #1
  {
    \__kernel_file_name_strip_quotes:nnnw {#1} { 0 } { }
      #1 " \q_recursion_tail " \q_recursion_stop
  }
\cs_new:Npn \__kernel_file_name_strip_quotes:nnnw #1#2#3#4 "
  {
    \quark_if_recursion_tail_stop_do:nn {#4}
      { \__kernel_file_name_strip_quotes:nnn {#1} {#2} {#3} }
    \__kernel_file_name_strip_quotes:nnnw {#1} { #2 + 1 } { #3#4 }
  }
\cs_new:Npn \__kernel_file_name_strip_quotes:nnn #1#2#3
  {
    \int_if_even:nT {#2}
      {
        \__kernel_msg_expandable_error:nnn
          { kernel } { unbalanced-quote-in-filename } {#1}
      }
    #3
  }
%    \end{macrocode}
%   Spaces need to be trimmed from the start of the name and from the end of
%   any extension. However, the name we are passed might not have an extension:
%   that means we have to look for one. If there is no extension, we still use
%   the standard trimming function but deliberately prevent any spaces being 
%   removed at the end.
%    \begin{macrocode}
\cs_new:Npn \__kernel_file_name_trim_spaces:n #1
  { \__kernel_file_name_trim_spaces:nw {#1} #1 . \q_nil . \q_stop }
\cs_new:Npn \__kernel_file_name_trim_spaces:nw #1#2 . #3 . #4 \q_stop
  {
    \quark_if_nil:nTF {#3}
      {
        \exp_args:Ne \__kernel_file_name_trim_spaces_aux:n
          { \tl_trim_spaces:n { #1 \s_stop } }
      }
      { \tl_trim_spaces:n {#1} }
  }
\cs_new:Npn \__kernel_file_name_trim_spaces_aux:n #1
  { \__kernel_file_name_trim_spaces_aux:w #1 }
\cs_new:Npn \__kernel_file_name_trim_spaces_aux:w #1 \s_stop {#1}
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}[EXP]{\__kernel_file_name_quote:n}
% \begin{macro}[EXP]{\__kernel_file_name_quote:nw}
%    \begin{macrocode}
\cs_new:Npn \__kernel_file_name_quote:n #1
  { \__kernel_file_name_quote:nw {#1} #1 ~ \q_nil \q_stop }
\cs_new:Npn \__kernel_file_name_quote:nw #1 #2 ~ #3 \q_stop
  {
    \quark_if_nil:nTF {#3}
      { #1 }
      { "#1" }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{variable}{\c_@@_marker_tl}
%   The same idea as the marker for rescanning token lists: this pair of
%   tokens cannot appear in a file that is being input.
%    \begin{macrocode}
\tl_const:Nx \c_@@_marker_tl { : \token_to_str:N : }
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}[TF]{\file_get:nnN}
% \begin{macro}{\file_get:nnN,\@@_get_aux:nnN,\@@_get_do:Nw}
%   The approach here is similar to that for \cs{tl_set_rescan:Nnn}.
%   The file contents are grabbed as an argument delimited by
%   \cs{c_@@_marker_tl}.  A few subtleties: braces in \cs{if_false:}
%   \ldots{} \cs{fi:} to deal with possible alignment tabs,
%   \tn{tracingnesting} to avoid a warning about a group being closed
%   inside the \tn{scantokens}, and \cs{prg_return_true:} is placed
%   after the end-of-file marker.
%    \begin{macrocode}
\cs_new_protected:Npn \file_get:nnN #1#2#3
  {
    \file_get:nnNF {#1} {#2} #3
      { \tl_set:Nn #3 { \q_no_value } }
  }
\prg_new_protected_conditional:Npnn \file_get:nnN #1#2#3 { T , F , TF }
  {
    \file_get_full_name:nNTF {#1} \l_@@_full_name_tl
      {
        \exp_args:NV \@@_get_aux:nnN
          \l_@@_full_name_tl
          {#2} #3
        \prg_return_true:
      }
      { \prg_return_false: }
  }
\cs_new_protected:Npx \@@_get_aux:nnN #1#2#3
  {
    \exp_not:N \if_false: { \exp_not:N \fi:
    \group_begin:
      \int_set_eq:NN \tex_tracingnesting:D \c_zero_int
      \exp_not:N \exp_args:No \tex_everyeof:D
        { \exp_not:N \c_@@_marker_tl }
      #2 \scan_stop:
      \exp_not:N \exp_after:wN \exp_not:N \@@_get_do:Nw
      \exp_not:N \exp_after:wN #3
      \exp_not:N \exp_after:wN \exp_not:N \prg_do_nothing:
      \exp_not:N \tex_input:D
      \sys_if_engine_luatex:TF
        { {#1} }
        { \exp_not:N \__kernel_file_name_quote:n {#1} \scan_stop: }
    \exp_not:N \if_false: } \exp_not:N \fi:
  }
\exp_args:Nno \use:nn
  { \cs_new_protected:Npn \@@_get_do:Nw #1#2 }
  { \c_@@_marker_tl }
  {
    \group_end:
    \tl_set:No #1 {#2}
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\@@_size:n}
%   A copy of the primitive where it's available, or the \LuaTeX{}
%   equivalent if relevant.
%    \begin{macrocode}
\cs_new_eq:NN \@@_size:n \tex_filesize:D
\sys_if_engine_luatex:T
  {
    \cs_gset:Npn \@@_size:n #1
      {
        \lua_now:e
          { l3kernel.filesize ( " \lua_escape:e {#1} " ) }
      }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]{\file_full_name:n, \@@_full_name:n}
% \begin{macro}[EXP]{\@@_full_name_aux:nn}
% \begin{macro}[EXP]{\@@_full_name_aux:n}
% \begin{macro}[EXP]{\@@_name_cleanup:w}
% \begin{macro}[EXP]{\@@_name_end:}
% \begin{macro}[EXP]{\@@_name_ext_check:n}
% \begin{macro}[EXP]{\@@_name_ext_check:nw}
% \begin{macro}[EXP]{\@@_name_ext_check:nnw}
% \begin{macro}[EXP]{\@@_name_ext_check:nn}
%   File searching can be carried out if the \tn{pdffilesize} primitive
%   or an equivalent is available. That of course means we need to
%   arrange for everything else to here to be done by expansion too.
%   We start off by sanitizing the name and quoting if required: we
%   may need to remove those quotes, so the raw name is passed too.
%    \begin{macrocode}
\cs_new:Npn \file_full_name:n #1
  {
    \exp_args:Ne \@@_full_name:n
      { \__kernel_file_name_sanitize:n {#1} }
  }
%    \end{macrocode}
%   First, we check of the file is just here: no mapping so we do not
%   need the break part of the broader auxiliary. We are using the fact
%   that the primitive here returns nothing if the file is entirely absent.
%   For package mode, \tn{input@path} is a token list not a sequence.
%    \begin{macrocode}
\cs_new:Npn \@@_full_name:n #1
  {
    \tl_if_blank:nF {#1}
      {
        \tl_if_blank:eTF { \@@_size:n {#1} }
          {
            \seq_map_tokens:Nn \l_file_search_path_seq
              { \@@_full_name_aux:nn {#1} }
%<*package>
            \cs_if_exist:NT \input@path
              {
                \tl_map_tokens:Nn \input@path
                  { \@@_full_name_aux:nn {#1} }
              }
%</package>
            \@@_name_end:
          }
          { \@@_ext_check:n {#1} }
      }
  }
%    \end{macrocode}
%   Two pars to the auxiliary here so we can avoid doing quoting
%   twice in the event we find the right file.
%    \begin{macrocode}
\cs_new:Npn \@@_full_name_aux:nn #1#2
  { \exp_args:Ne \@@_full_name_aux:n { \tl_to_str:n {#2} / #1 } }
\cs_new:Npn \@@_full_name_aux:n #1
  {
    \tl_if_blank:eF { \@@_size:n {#1} }
      {
        \seq_map_break:n
          {
            \@@_ext_check:n {#1}
            \@@_name_cleanup:w
          }
      }
  }
\cs_new:Npn \@@_name_cleanup:w #1 \@@_name_end: { }
\cs_new:Npn \@@_name_end: { }
%    \end{macrocode}
%   As \TeX{} automatically adds |.tex| if there is no extension,
%   there is a little clean up to do here. First, make sure we are not in the
%   directory part, saving that. Then check for an extension.
%    \begin{macrocode}
\cs_new:Npn \@@_ext_check:n #1
  { \@@_ext_check:nw { / } #1 / \q_nil / \q_stop }
\cs_new:Npn \@@_ext_check:nw #1 #2 / #3 / #4 \q_stop
  {
    \quark_if_nil:nTF {#3}
      {
        \exp_args:No \@@_ext_check:nnw
          { \use_none:n #1 } {#2} #2 . \q_nil . \q_stop
      }
      { \@@_ext_check:nw { #1 #2 / } #3 / #4 \q_stop }
  }
\cs_new:Npx \@@_ext_check:nnw #1#2#3 . #4 . #5 \q_stop
  {
    \exp_not:N \quark_if_nil:nTF {#4}
      {
        \exp_not:N \@@_ext_check:nn
          { #1 #2 } { #1 #2 \tl_to_str:n { .tex } }
      }
      { #1 #2 }
  }
\cs_new:Npn \@@_ext_check:nn #1#2
  {
    \tl_if_blank:eTF { \@@_size:n {#2} }
      {#1}
      {
        \int_compare:nNnTF
          { \@@_size:n {#1} } = { \@@_size:n {#2} }
          {#2}
          {#1}
      }
  }
%    \end{macrocode}
%   Deal with the fact that the primitive might not be available.
%    \begin{macrocode}
\bool_lazy_or:nnF
  { \cs_if_exist_p:N \tex_filesize:D }
  { \sys_if_engine_luatex_p: }
  {
    \cs_gset:Npn \file_full_name:n #1
      {
        \__kernel_msg_expandable_error:nnn
          { kernel } { primitive-not-available }
          { \(pdf)filesize }
      }
  }
\__kernel_msg_new:nnnn { kernel } { primitive-not-available }
  { Primitive~\token_to_str:N #1 not~available }
  {
    The~version~of~your~TeX~engine~does~not~provide~functionality~equivalent~to~
    the~#1~primitive.
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\file_get_full_name:nN, \file_get_full_name:VN}
% \begin{macro}[TF]{\file_get_full_name:nN, \file_get_full_name:VN}
% \begin{macro}{\@@_get_full_name_search:nN}
%   These functions pre-date using \cs{tex_filesize:D} for file searching,
%   so are |get| functions with protection. To avoid having different
%   search set ups, they are simply wrappers around the code above.
%    \begin{macrocode}
\cs_new_protected:Npn \file_get_full_name:nN #1#2
  {
    \file_get_full_name:nNF {#1} #2
      { \tl_set:Nn #2 { \q_no_value } }
  }
\cs_generate_variant:Nn \file_get_full_name:nN { V }
\prg_new_protected_conditional:Npnn \file_get_full_name:nN #1#2 { T , F , TF }
  {
    \tl_set:Nx #2
      { \file_full_name:n {#1} }
    \tl_if_empty:NTF #2
      { \prg_return_false: }
      { \prg_return_true: }
  }
\cs_generate_variant:Nn \file_get_full_name:nNT  { V }
\cs_generate_variant:Nn \file_get_full_name:nNF  { V }
\cs_generate_variant:Nn \file_get_full_name:nNTF { V }
%    \end{macrocode}
%   If \cs{tex_filesize:D} is not available, the
%   way to test if a file exists is to try to open it: if it does
%   not exist then \TeX{} reports end-of-file. A search is made
%   looking at each potential path in turn (starting from the current
%   directory). The first location is of course treated as the correct
%   one: this is done by jumping to \cs{prg_break_point:}. If nothing
%   is found, |#2| is returned empty. A special case when there is no
%   extension is that once the first location is found we test the
%   existence of the file with |.tex| extension in that directory, and
%   if it exists we include the |.tex| extension in the result.
%    \begin{macrocode}
\bool_lazy_or:nnF
  { \cs_if_exist_p:N \tex_filesize:D }
  { \sys_if_engine_luatex_p: }
  {
    \prg_set_protected_conditional:Npnn \file_get_full_name:nN #1#2 { T , F , TF }
      {
        \tl_set:Nx \l_@@_base_name_tl
          { \__kernel_file_name_sanitize:n {#1} }
        \@@_get_full_name_search:nN { } \use:n
        \seq_map_inline:Nn \l_file_search_path_seq
          { \@@_get_full_name_search:nN { ##1 / } \seq_map_break:n }
%<*package>
        \cs_if_exist:NT \input@path
          {
            \tl_map_inline:Nn \input@path
              { \@@_get_full_name_search:nN { ##1 } \tl_map_break:n }
          }
%</package>
        \tl_set:Nn \l_@@_full_name_tl { \q_no_value }
        \prg_break_point:
        \quark_if_no_value:NTF \l_@@_full_name_tl
          {
            \ior_close:N \g_@@_internal_ior
            \prg_return_false:
          }
          {
            \file_parse_full_name:VNNN \l_@@_full_name_tl
              \l_@@_dir_str \l_@@_name_str \l_@@_ext_str
            \str_if_empty:NT \l_@@_ext_str
              {
                \__kernel_ior_open:No \g_@@_internal_ior
                  { \l_@@_full_name_tl .tex }
                \ior_if_eof:NF \g_@@_internal_ior
                   { \tl_put_right:Nn \l_@@_full_name_tl { .tex } }
              }
            \ior_close:N \g_@@_internal_ior
            \tl_set_eq:NN #2 \l_@@_full_name_tl
            \prg_return_true:
          }
      }
  }
\cs_new_protected:Npn \@@_get_full_name_search:nN #1#2
  {
    \tl_set:Nx \l_@@_full_name_tl
      { \tl_to_str:n {#1} \l_@@_base_name_tl }
    \__kernel_ior_open:No \g_@@_internal_ior \l_@@_full_name_tl
    \ior_if_eof:NF \g_@@_internal_ior { #2 { \prg_break: } }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \begin{variable}{\g_@@_internal_ior}
%   A reserved stream to test for file existence, if required.
%    \begin{macrocode}
\bool_lazy_or:nnF
  { \cs_if_exist_p:N \tex_filesize:D }
  { \sys_if_engine_luatex_p: }
  { \ior_new:N \g_@@_internal_ior }
%    \end{macrocode}
% \end{variable}
%
% \begin{macro}[rEXP]{\file_mdfive_hash:n, \file_size:n, \file_timestamp:n}
% \begin{macro}[rEXP]{\@@_details:nn, \@@_details_aux:nn}
% \begin{macro}[rEXP]{\@@_mdfive_hash:n}
%   Getting file details by expansion is relatively easy if a bit repetitive.
%   As the MD5 function has a slightly different syntax from the other commands,
%   there is a little cleaning up to do.
%    \begin{macrocode}
\cs_new:Npn \file_mdfive_hash:n #1
  { \@@_details:nn {#1} { mdfivesum } }
\cs_new:Npn \file_size:n #1
  { \@@_details:nn {#1} { size } }
\cs_new:Npn \file_timestamp:n #1
  { \@@_details:nn {#1} { moddate } }
\cs_new:Npn \@@_details:nn #1#2
  {
    \exp_args:Ne \@@_details_aux:nn
      { \file_full_name:n {#1} } {#2}
  }
\cs_new:Npn \@@_details_aux:nn #1#2
  {
    \tl_if_blank:nF {#1}
      { \use:c { tex_file #2 :D } {#1} }
  }
\sys_if_engine_luatex:TF
  {
    \cs_gset:Npn \@@_details_aux:nn #1#2
      {
        \lua_now:e
          { l3kernel.file#2 ( " \lua_escape:e { #1 } " ) }
      }
  }
  {
    \cs_gset:Npn \file_mdfive_hash:n #1
      { \exp_args:Ne \@@_mdfive_hash:n { \file_full_name:n {#1} } }
    \cs_new:Npn \@@_mdfive_hash:n #1
      { \tex_mdfivesum:D file {#1} }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}[rEXP]{\file_hex_dump:nnn, \@@_hex_dump_auxi:nnn}
% \begin{macro}[rEXP]{\@@_hex_dump_auxii:nnnn, \@@_hex_dump_auxiii:nnnn}
% \begin{macro}[rEXP]{\@@_hex_dump_auxiiv:nnn}
% \begin{macro}[rEXP]{\file_hex_dump:n, \@@_hex_dump:n}
%   These are separate as they need multiple arguments \emph{or} the
%   file size. For \LuaTeX{}, the emulation does not need the file
%   size so we save a little on expansion.
%    \begin{macrocode}
\cs_new:Npn \file_hex_dump:nnn #1#2#3
  {
    \exp_args:Neee \@@_hex_dump_auxi:nnn
      { \file_full_name:n {#1} }
      { \int_eval:n {#2} }
      { \int_eval:n {#3} }
  }
\cs_new:Npn \@@_hex_dump_auxi:nnn #1#2#3
  {
    \bool_lazy_any:nF
      {
        { \tl_if_blank_p:n {#1} }
        { \int_compare_p:nNn {#2} = 0 }
        { \int_compare_p:nNn {#3} = 0 }
      }
      {
        \exp_args:Ne \@@_hex_dump_auxii:nnnn
          { \@@_details_aux:nn {#1} { size } }
          {#1} {#2} {#3}
      }
  }
\cs_new:Npn \@@_hex_dump_auxii:nnnn #1#2#3#4
  {
    \int_compare:nNnTF {#3} > 0
      { \@@_hex_dump_auxiii:nnnn {#3} }
      {
        \exp_args:Ne \@@_hex_dump_auxiii:nnnn
          { \int_eval:n { #1 + #3 } }
      }
        {#1} {#2} {#4}
  }
\cs_new:Npn \@@_hex_dump_auxiii:nnnn #1#2#3#4
  {
    \int_compare:nNnTF {#4} > 0
      { \@@_hex_dump_auxiv:nnn {#4} }
      {
        \exp_args:Ne \@@_hex_dump_auxiv:nnn
          { \int_eval:n { #2 + #4 } }
      }
        {#1} {#3}
  }
\cs_new:Npn \@@_hex_dump_auxiv:nnn #1#2#3
  {
    \tex_filedump:D
      offset ~ \int_eval:n { #2 - 1 } ~
      length ~ \int_eval:n { #1 - #2 + 1 }
      {#3}
  }
\sys_if_engine_luatex:T
  {
    \cs_gset:Npn \@@_hex_dump_auxiv:nnn #1#2#3
      {
        \lua_now:e
          {
            l3kernel.filedump
              (
                " \lua_escape:e {#3} " ,
                \int_eval:n { #2 - 1 } ,
                \int_eval:n { #1 - #2 + 1 }
              )
          }
      }
  }
\cs_new:Npn \file_hex_dump:n #1
  { \exp_args:Ne \@@_hex_dump:n { \file_full_name:n {#1} } }
\cs_new:Npn \@@_hex_dump:n #1
  {
    \tl_if_blank:nF {#1}
      { \tex_filedump:D length \tex_filesize:D {#1} {#1} }
  }
\sys_if_engine_luatex:T
  {
    \cs_gset:Npn \@@_hex_dump:n #1
      {
        \lua_now:e
          { l3kernel.filedump ( " \lua_escape:e { #1 } " ) }
      }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}[noTF]
%   {
%     \file_get_hex_dump:nN,
%     \file_get_mdfive_hash:nN
%     \file_get_size:nN,
%     \file_get_timestamp:nN
%   }
% \begin{macro}{\@@_get_details:nnN}
%   Non-expandable wrappers around the above in the case where appropriate
%   primitive support exists.
%    \begin{macrocode}
\cs_new_protected:Npn \file_get_hex_dump:nN #1#2
  { \file_get_hex_dump:nNF {#1} #2 { \tl_set:Nn #2 { \q_no_value } } }
\cs_new_protected:Npn \file_get_mdfive_hash:nN #1#2
  { \file_get_mdfive_hash:nNF {#1} #2 { \tl_set:Nn #2 { \q_no_value } } }
\cs_new_protected:Npn \file_get_size:nN #1#2
  { \file_get_size:nNF {#1} #2 { \tl_set:Nn #2 { \q_no_value } } }
\cs_new_protected:Npn \file_get_timestamp:nN #1#2
  { \file_get_timestamp:nNF {#1} #2 { \tl_set:Nn #2 { \q_no_value } } }
\prg_new_protected_conditional:Npnn \file_get_hex_dump:nN #1#2 { T , F , TF }
  { \@@_get_details:nnN {#1} { hex_dump } #2 }
\prg_new_protected_conditional:Npnn \file_get_mdfive_hash:nN #1#2 { T , F , TF }
  { \@@_get_details:nnN {#1} { mdfive_hash } #2 }
\prg_new_protected_conditional:Npnn \file_get_size:nN #1#2 { T , F , TF }
  { \@@_get_details:nnN {#1} { size } #2 }
\prg_new_protected_conditional:Npnn \file_get_timestamp:nN #1#2 { T , F , TF }
  { \@@_get_details:nnN {#1} { timestamp } #2 }
\cs_new_protected:Npn \@@_get_details:nnN #1#2#3
  {
    \tl_set:Nx #3
      { \use:c { file_ #2 :n } {#1} }
    \tl_if_empty:NTF #3
      { \prg_return_false: }
      { \prg_return_true: }
  }
%    \end{macrocode}
%   Where the primitive is not available, issue an error: this is a little
%   more conservative than absolutely needed, but does work.
%    \begin{macrocode}
\bool_lazy_or:nnF
  { \cs_if_exist_p:N \tex_filesize:D }
  { \sys_if_engine_luatex_p: }
  {
    \cs_set_protected:Npn \@@_get_details:nnN #1#2#3
       {
        \tl_clear:N #3
        \__kernel_msg_error:nnx
          { kernel } { primitive-not-available }
          {
            \token_to_str:N \(pdf)file
            \str_case:nn {#2}
              {
                { hex_dump } { dump }
                { mdfive_hash } { mdfivesum }
                { timestamp } { moddate }
                { size } { size }
              }
          }
        \prg_return_false:
      }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}[noTF]{\file_get_hex_dump:nnnN}
%   Custom code due to the additional arguments.
%    \begin{macrocode}
\cs_new_protected:Npn \file_get_hex_dump:nnnN #1#2#3#4
  {
    \file_get_hex_dump:nnnNF {#1} {#2} {#3} #4
      { \tl_set:Nn #4 { \q_no_value } } 
  }
\prg_new_protected_conditional:Npnn \file_get_hex_dump:nnnN #1#2#3#4
  { T , F , TF }
  {
    \tl_set:Nx #4
      { \file_hex_dump:nnn {#1} {#2} {#3} }
    \tl_if_empty:NTF #4
      { \prg_return_false: }
      { \prg_return_true: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}[EXP]{\@@_str_cmp:nn}
% \begin{macro}[EXP]{\@@_str_escape:n}
%   As we are doing a fixed-length \enquote{big} integer comparison, it
%   is easiest to use the low-level behavior  of string comparisons.
%    \begin{macrocode}
\cs_new:Npn \@@_str_cmp:nn #1#2 { \tex_strcmp:D {#1} {#2} }
\sys_if_engine_luatex:T
  {
    \cs_set:Npn \@@_str_cmp:nn #1#2
      {
        \lua_now:e
          {
            l3kernel.strcmp
              (
                " \@@_str_escape:n {#1}",
                " \@@_str_escape:n {#2}"
              )
          }
      }
   \cs_new:Npn \@@_str_escape:n #1
     {
       \lua_escape:e
         { \__kernel_tl_to_str:w \use:e { {#1} } }
     }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}[EXP, pTF]{\file_compare_timestamp:nNn}
% \begin{macro}[EXP]{\@@_compare_timestamp:nnN}
% \begin{macro}[EXP]{\@@_timestamp:n}
%   Comparison of file date can be done by using the low-level nature of the
%   string comparison functions.
%    \begin{macrocode}
\prg_new_conditional:Npnn \file_compare_timestamp:nNn #1#2#3
  { p , T , F , TF }
  {
    \exp_args:Nee \@@_compare_timestamp:nnN
      { \file_full_name:n {#1} }
      { \file_full_name:n {#3} }
      #2
   }
\cs_new:Npn \@@_compare_timestamp:nnN #1#2#3
  {
    \tl_if_blank:nTF {#1}
      {
        \if_charcode:w #3 <
          \prg_return_true:
        \else:
          \prg_return_false:
        \fi:
      }
      {
        \tl_if_blank:nTF {#2}
          {
             \if_charcode:w #3 >
                \prg_return_true:
              \else:
                \prg_return_false:
              \fi:
          }
          {
            \if_int_compare:w
              \@@_str_cmp:nn
                { \@@_timestamp:n {#1} }
                { \@@_timestamp:n {#2} }
                #3 0 \exp_stop_f:
              \prg_return_true:
            \else:
              \prg_return_false:
            \fi:
          }
      }
  }
\sys_if_engine_luatex:TF
  {
    \cs_new:Npn \@@_timestamp:n #1
      {
        \lua_now:e
          { l3kernel.filemoddate ( " \lua_escape:e {#1} " ) }
      }
  }
  { \cs_new_eq:NN \@@_timestamp:n \tex_filemoddate:D }
\cs_if_exist:NF \tex_filemoddate:D
  {
    \prg_set_conditional:Npnn \file_compare_timestamp:nNn #1#2#3
      { p , T , F , TF }
      {
        \__kernel_msg_expandable_error:nnn
          { kernel } { primitive-not-available }
          { \(pdf)filemoddate }
        \prg_return_false:
      }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}[TF]{\file_if_exist:n}
%   The test for the existence of a file is a wrapper around the function to
%   add a path to a file. If the file was found, the path contains
%   something, whereas if the file was not located then the return value
%   is empty.
%    \begin{macrocode}
\prg_new_protected_conditional:Npnn \file_if_exist:n #1 { T , F , TF }
  {
    \file_get_full_name:nNTF {#1} \l_@@_full_name_tl
      { \prg_return_true: }
      { \prg_return_false: }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\file_if_exist_input:n, \file_if_exist_input:nF}
%   Input of a file with a test for existence.  We do not define the |T|
%   or |TF| variants because the most useful place to place the
%   \meta{true code} would be inconsistent with other conditionals.
%    \begin{macrocode}
\cs_new_protected:Npn \file_if_exist_input:n #1
  {
    \file_get_full_name:nNT {#1} \l_@@_full_name_tl
      { \@@_input:V \l_@@_full_name_tl }
  }
\cs_new_protected:Npn \file_if_exist_input:nF #1#2
  {
    \file_get_full_name:nNTF {#1} \l_@@_full_name_tl
      { \@@_input:V \l_@@_full_name_tl }
      {#2}
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\file_input_stop:}
%   A simple rename.
%    \begin{macrocode}
\cs_new_protected:Npn \file_input_stop: { \tex_endinput:D }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\__kernel_file_missing:n}
%   An error message for a missing file, also used in \cs{ior_open:Nn}.
%    \begin{macrocode}
\cs_new_protected:Npn \__kernel_file_missing:n #1
  {
    \__kernel_msg_error:nnx { kernel } { file-not-found }
      { \__kernel_file_name_sanitize:n {#1} }
  }
%    \end{macrocode}
% \end{macro}
%
% \begin{macro}{\file_input:n}
% \begin{macro}{\@@_input:n, \@@_input:V}
% \begin{macro}{\@@_input_push:n, \__kernel_file_input_push:n}
% \begin{macro}{\@@_input_pop:, \__kernel_file_input_pop:}
% \begin{macro}{\@@_input_pop:nnn}
%   Loading a file is done in a safe way, checking first that the file
%   exists and loading only if it does.  Push the file name on the
%   \cs{g_@@_stack_seq}, and add it to the file list, either
%   \cs{g_@@_record_seq}, or \cs{@filelist} in package mode.
%    \begin{macrocode}
\cs_new_protected:Npn \file_input:n #1
  {
    \file_get_full_name:nNTF {#1} \l_@@_full_name_tl
      { \@@_input:V \l_@@_full_name_tl }
      { \__kernel_file_missing:n {#1} }
  }
\cs_new_protected:Npx \@@_input:n #1
  {
%<*initex>
    \seq_gput_right:Nn \exp_not:N \g_@@_record_seq {#1}
%</initex>
%<*package>
    \exp_not:N \clist_if_exist:NTF \exp_not:N \@filelist
      { \exp_not:N \@addtofilelist {#1} }
      { \seq_gput_right:Nn \exp_not:N \g_@@_record_seq {#1} }
%</package>
    \exp_not:N \@@_input_push:n {#1}
    \exp_not:N \tex_input:D
    \sys_if_engine_luatex:TF
      { {#1} }
      { \exp_not:N \__kernel_file_name_quote:n {#1} \scan_stop: }
    \exp_not:N \@@_input_pop:
  }
\cs_generate_variant:Nn \@@_input:n { V }
%    \end{macrocode}
%   Keeping a track of the file data is easy enough: we store the separated
%   parts so we do not need to parse them twice.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_input_push:n #1
  {
    \seq_gpush:Nx \g_@@_stack_seq
      {
        { \g_file_curr_dir_str }
        { \g_file_curr_name_str }
        { \g_file_curr_ext_str }
      }
    \file_parse_full_name:nNNN {#1}
      \l_@@_dir_str \l_@@_name_str \l_@@_ext_str
    \str_gset_eq:NN \g_file_curr_dir_str  \l_@@_dir_str
    \str_gset_eq:NN \g_file_curr_name_str \l_@@_name_str
    \str_gset_eq:NN \g_file_curr_ext_str  \l_@@_ext_str
  }
%<*package>
\cs_new_eq:NN \__kernel_file_input_push:n \@@_input_push:n
%</package>
\cs_new_protected:Npn \@@_input_pop:
  {
    \seq_gpop:NN \g_@@_stack_seq \l_@@_internal_tl
    \exp_after:wN \@@_input_pop:nnn \l_@@_internal_tl
  }
%<*package>
\cs_new_eq:NN \__kernel_file_input_pop: \@@_input_pop:
%</package>
\cs_new_protected:Npn \@@_input_pop:nnn #1#2#3
  {
    \str_gset:Nn \g_file_curr_dir_str  {#1}
    \str_gset:Nn \g_file_curr_name_str {#2}
    \str_gset:Nn \g_file_curr_ext_str  {#3}
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\file_parse_full_name:nNNN, \file_parse_full_name:VNNN}
% \begin{macro}
%   {\@@_parse_full_name_auxi:w, \@@_parse_full_name_split:nNNNTF}
%   Parsing starts by stripping off any surrounding quotes.  Then find
%   the directory |#4| by splitting at the last~|/|.  (The auxiliary
%   returns \texttt{true}/\texttt{false} depending on whether it found
%   the delimiter.)  We correct for the case of a file in the root |/|,
%   as in that case we wish to keep the trailing (and only) slash.  Then
%   split the base name |#5| at the last dot.  If there was indeed a
%   dot, |#5| contains the name and |#6| the extension without the dot,
%   which we add back for convenience.  In the special case of no
%   extension given, the auxiliary stored the name into |#6|, we just
%   have to move it to |#5|.
%    \begin{macrocode}
\cs_new_protected:Npn \file_parse_full_name:nNNN #1#2#3#4
  {
    \exp_after:wN \@@_parse_full_name_auxi:w
      \tl_to_str:n { #1 " #1 " } \q_stop #2#3#4
  }
\cs_generate_variant:Nn \file_parse_full_name:nNNN { V }
\cs_new_protected:Npn \@@_parse_full_name_auxi:w
  #1 " #2 " #3 \q_stop #4#5#6
  {
    \@@_parse_full_name_split:nNNNTF {#2} / #4 #5
      { \str_if_empty:NT #4 { \str_set:Nn #4 { / } } }
      { }
    \exp_args:No \@@_parse_full_name_split:nNNNTF {#5} . #5 #6
      { \str_put_left:Nn #6 { . } }
      {
        \str_set_eq:NN #5 #6
        \str_clear:N #6
      }
  }
\cs_new_protected:Npn \@@_parse_full_name_split:nNNNTF #1#2#3#4
  {
    \cs_set_protected:Npn \@@_tmp:w ##1 ##2 #2 ##3 \q_stop
      {
        \tl_if_empty:nTF {##3}
          {
            \str_set:Nn #4 {##2}
            \tl_if_empty:nTF {##1}
              {
                \str_clear:N #3
                \use_ii:nn
              }
              {
                \str_set:Nx #3 { \str_tail:n {##1} }
                \use_i:nn
              }
          }
          { \@@_tmp:w { ##1 #2 ##2 } ##3 \q_stop }
      }
    \@@_tmp:w { } #1 #2 \q_stop
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \begin{macro}{\file_show_list:, \file_log_list:, \@@_list:N}
% \begin{macro}[EXP]{\@@_list_aux:n}
%   A function to list all files used to the log, without duplicates.
%   In package mode, if \cs{@filelist} is still defined, we need to take
%   this list of file names into account (we capture it
%   \cs{AtBeginDocument} into \cs{g_@@_record_seq}), turning it to a
%   string (this does not affect the commas of this comma list).
%    \begin{macrocode}
\cs_new_protected:Npn \file_show_list: { \@@_list:N \msg_show:nnxxxx }
\cs_new_protected:Npn \file_log_list: { \@@_list:N \msg_log:nnxxxx }
\cs_new_protected:Npn \@@_list:N #1
  {
    \seq_clear:N \l_@@_tmp_seq
%<*package>
    \clist_if_exist:NT \@filelist
      {
        \exp_args:NNx \seq_set_from_clist:Nn \l_@@_tmp_seq
          { \tl_to_str:N \@filelist }
      }
%</package>
    \seq_concat:NNN \l_@@_tmp_seq \l_@@_tmp_seq \g_@@_record_seq
    \seq_remove_duplicates:N \l_@@_tmp_seq
    #1 { LaTeX/kernel } { file-list }
      { \seq_map_function:NN \l_@@_tmp_seq \@@_list_aux:n }
        { } { } { }
  }
\cs_new:Npn \@@_list_aux:n #1 { \iow_newline: #1 }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% When used as a package, there is a need to hold onto the standard file
% list as well as the new one here.  File names recorded in
% \cs{@filelist} must be turned to strings before being added to
% \cs{g_@@_record_seq}.
%    \begin{macrocode}
%<*package>
\cs_if_exist:NT \@filelist
  {
    \AtBeginDocument
      {
        \exp_args:NNx \seq_set_from_clist:Nn \l_@@_tmp_seq
          { \tl_to_str:N \@filelist }
        \seq_gconcat:NNN
          \g_@@_record_seq
          \g_@@_record_seq
          \l_@@_tmp_seq
      }
  }
%</package>
%    \end{macrocode}
%
% \subsection{GetIfInfo}
%
% \begin{macro}{\GetIdInfo}
% \begin{macro}{\@@_id_info_auxi:w, \@@_id_info_auxii:w, \@@_id_info_auxiii:w}
%   As documented in \pkg{expl3.dtx} this function extracts file name
%   etc from an \textsc{svn} \texttt{Id} line.  This used to be how we
%   got version number and so on in all modules, so it had to be defined
%   in \pkg{l3bootstrap}.  Now it's more convenient to define it after
%   we have set up quite a lot of tools, and \pkg{l3file} seems the
%   least unreasonable place for it.
%
%   The idea here is to extract out the information needed from a standard
%   \textsc{svn} \texttt{Id} line, but to avoid a line that would get
%   changed when the file is checked in. Hence the fact that none of the
%   lines here include both a dollar sign and the \texttt{Id} keyword!
%    \begin{macrocode}
\cs_new_protected:Npn \GetIdInfo
  {
    \tl_clear_new:N \ExplFileDescription
    \tl_clear_new:N \ExplFileDate
    \tl_clear_new:N \ExplFileName
    \tl_clear_new:N \ExplFileExtension
    \tl_clear_new:N \ExplFileVersion
    \group_begin:
    \char_set_catcode_space:n { 32 }
    \exp_after:wN
    \group_end:
    \@@_id_info_auxi:w
  }
%    \end{macrocode}
%   A first check for a completely empty \textsc{svn} field. If that is
%   not the case, there is a second case when a file created using
%   \texttt{svn cp} but has not been checked in. That leaves a special
%   marker \texttt{-1} version, which has no further data. Dealing
%   correctly with that is the reason for the space in the line to use
%   \cs{@@_id_info_auxii:w}.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_id_info_auxi:w $ #1 $ #2
  {
    \tl_set:Nn \ExplFileDescription {#2}
    \str_if_eq:nnTF {#1} { Id }
      {
        \tl_set:Nn \ExplFileDate { 0000/00/00 }
        \tl_set:Nn \ExplFileName { [unknown] }
        \tl_set:Nn \ExplFileExtension { [unknown~extension] }
        \tl_set:Nn \ExplFileVersion {-1}
      }
      { \@@_id_info_auxii:w #1 ~ \q_stop }
  }
%    \end{macrocode}
%   Here, |#1| is |Id|, |#2| is the file name, |#3| is the extension,
%   |#4| is the version, |#5| is the check in date and |#6| is the check
%   in time and user, plus some trailing spaces. If |#4| is the marker
%   |-1| value then |#5| and |#6| are empty.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_id_info_auxii:w
    #1 ~ #2.#3 ~ #4 ~ #5 ~ #6 \q_stop
  {
    \tl_set:Nn \ExplFileName {#2}
    \tl_set:Nn \ExplFileExtension {#3}
    \tl_set:Nn \ExplFileVersion {#4}
    \str_if_eq:nnTF {#4} {-1}
      { \tl_set:Nn \ExplFileDate { 0000/00/00 } }
      { \@@_id_info_auxiii:w #5 - 0 - 0 - \q_stop }
  }
%    \end{macrocode}
%   Convert an \textsc{svn}-style date into a \LaTeX{}-style one.
%    \begin{macrocode}
\cs_new_protected:Npn \@@_id_info_auxiii:w #1 - #2 - #3 - #4 \q_stop
  { \tl_set:Nn \ExplFileDate { #1/#2/#3 } }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
% \subsection{Messages}
%
%    \begin{macrocode}
\__kernel_msg_new:nnnn { kernel } { file-not-found }
  { File~'#1'~not~found. }
  {
    The~requested~file~could~not~be~found~in~the~current~directory,~
    in~the~TeX~search~path~or~in~the~LaTeX~search~path.
  }
\__kernel_msg_new:nnn { kernel } { file-list }
  {
    >~File~List~<
    #1 \\
    .............
  }
\__kernel_msg_new:nnnn { kernel } { input-streams-exhausted }
  { Input~streams~exhausted }
  {
    TeX~can~only~open~up~to~16~input~streams~at~one~time.\\
    All~16~are~currently~in~use,~and~something~wanted~to~open~
    another~one.
  }
\__kernel_msg_new:nnnn { kernel } { output-streams-exhausted }
  { Output~streams~exhausted }
  {
    TeX~can~only~open~up~to~16~output~streams~at~one~time.\\
    All~16~are~currently~in~use,~and~something~wanted~to~open~
    another~one.
  }
\__kernel_msg_new:nnnn { kernel } { unbalanced-quote-in-filename }
  { Unbalanced~quotes~in~file~name~'#1'. }
  {
    File~names~must~contain~balanced~numbers~of~quotes~(").
  }
\__kernel_msg_new:nnnn { kernel } { iow-indent }
  { Only~#1 (arg~1)~allows~#2 }
  {
    The~command~#2 can~only~be~used~in~messages~
    which~will~be~wrapped~using~#1.
    \tl_if_empty:nF {#3} { ~ It~was~called~with~argument~'#3'. }
  }
%    \end{macrocode}
%
% \subsection{Functions delayed from earlier modules}
%
%<@@=sys>
%
% \begin{variable}{\c_sys_platform_str}
%   Detecting the platform on \LuaTeX{} is easy: for other engines, we use
%   the fact that the two common cases have special null files. It is possible
%   to probe further (see package \pkg{platform}), but that requires shell
%   escape and seems unlikely to be useful. This is set up here as it requires
%   file searching.
%    \begin{macrocode}
\sys_if_engine_luatex:TF
  {
    \str_const:Nx \c_sys_platform_str
      { \tex_directlua:D { tex.print(os.type) } }
  }
  {
    \file_if_exist:nTF { nul: }
      {
        \file_if_exist:nF { /dev/null }
          { \str_const:Nn \c_sys_platform_str { windows } }
      }
      {
        \file_if_exist:nT { /dev/null }
          { \str_const:Nn \c_sys_platform_str { unix } }
      }
  }
\cs_if_exist:NF \c_sys_platform_str
  { \str_const:Nn \c_sys_platform_str { unknown }  }
%    \end{macrocode}
% \end{variable}
% \begin{macro}[pTF]{\sys_if_platform_unix:}
% \begin{macro}[pTF]{\sys_if_platform_windows:}
%   We can now set up the tests.
%    \begin{macrocode}
\clist_map_inline:nn { unix , windows }
  {
    \@@_const:nn { sys_if_platform_ #1 }
      { \str_if_eq_p:Vn \c_sys_platform_str { #1 } }
  }
%    \end{macrocode}
% \end{macro}
% \end{macro}
%
%    \begin{macrocode}
%</initex|package>
%    \end{macrocode}
%
% \end{implementation}
%
% \PrintIndex
