summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/morewrites
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /macros/latex/contrib/morewrites
Initial commit
Diffstat (limited to 'macros/latex/contrib/morewrites')
-rw-r--r--macros/latex/contrib/morewrites/README.md16
-rw-r--r--macros/latex/contrib/morewrites/morewrites.dtx1289
-rw-r--r--macros/latex/contrib/morewrites/morewrites.ins39
-rw-r--r--macros/latex/contrib/morewrites/morewrites.pdfbin0 -> 715831 bytes
-rw-r--r--macros/latex/contrib/morewrites/primargs.dtx918
-rw-r--r--macros/latex/contrib/morewrites/primargs.pdfbin0 -> 601061 bytes
6 files changed, 2262 insertions, 0 deletions
diff --git a/macros/latex/contrib/morewrites/README.md b/macros/latex/contrib/morewrites/README.md
new file mode 100644
index 0000000000..aa31c5370a
--- /dev/null
+++ b/macros/latex/contrib/morewrites/README.md
@@ -0,0 +1,16 @@
+Provide more writing streams in LaTeX
+=====================================
+* E-mail: blflatex+morewrites@gmail.com
+* Released under the LaTeX Project Public License v1.3c or later
+ See http://www.latex-project.org/lppl.txt
+
+The morewrites LaTeX package should be used when encountering the
+`No room for a new \write ` error. This is done by redefining a
+few TeX primitives related to output.
+
+This package uses the `l3kernel` bundle.
+
+This is work in progress, all suggestions/comments/bug reports
+are welcome! See https://github.com/blefloch/latex-morewrites
+
+
diff --git a/macros/latex/contrib/morewrites/morewrites.dtx b/macros/latex/contrib/morewrites/morewrites.dtx
new file mode 100644
index 0000000000..6e52eff394
--- /dev/null
+++ b/macros/latex/contrib/morewrites/morewrites.dtx
@@ -0,0 +1,1289 @@
+% \iffalse meta-comment
+%
+%% File: morewrites.dtx Copyright (C) 2011-2018 Bruno Le Floch
+%%
+%% It may be distributed and/or modified under the conditions of the
+%% LaTeX Project Public License (LPPL), either version 1.3c of this
+%% license or (at your option) any later version. The latest version
+%% of this license is in the file
+%%
+%% http://www.latex-project.org/lppl.txt
+%%
+%% This work has the LPPL maintenance status 'maintained'
+%% and the current maintainer is Bruno Le Floch.
+%% -----------------------------------------------------------------------
+%
+%<*driver>
+\RequirePackage{morewrites}
+\ExplSyntaxOn
+\prg_replicate:nn { 300 } { \newwrite \foo }
+\ExplSyntaxOff
+\documentclass[full]{l3doc}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{The \textsf{morewrites} package: \\
+% Always room for a new \tn{write}}
+% \author{Bruno Le Floch}
+% \date{2018/12/29}
+%
+% \maketitle
+% \tableofcontents
+%
+% \begin{documentation}
+%
+% \section{\pkg{morewrites} documentation}
+%
+% This \LaTeX{} package is a solution for the error
+% \enquote{no room for a new \tn{write}},
+% which occurs when a document reserves too many streams to write data
+% to various auxiliary files.
+% It is in principle possible to rewrite other packages so that they
+% are less greedy on resources, but that is often unpractical for the
+% end-user. Instead, \pkg{morewrites} hooks at the lowest level (\TeX{}
+% primitives).
+%
+% Simply add the line |\usepackage{morewrites}| near the beginning of
+% your \LaTeX{} file's preamble: the \enquote{no room for a new
+% \tn{write}} error should vanish. If it does not, please
+% contact me so that I can correct the problem. This can be done by
+% posting a question on the \url{tex.stackexchange.com} question and
+% answers website, logging an issue on GitHub
+% (\url{https://github.com/blefloch/latex-morewrites}), or emailing me a
+% minimal file showing the problem.
+%
+% Notes.
+% \begin{itemize}
+% \item This package loads the \pkg{expl3} package, hence the
+% \pkg{l3kernel} bundle needs to be up to date.
+% \item This package uses an auxiliary file, \meta{job~name}|.mw|,
+% which can safely be deleted. Versions from 2015 and later will only
+% use the auxiliary file if it is originally empty, to avoid
+% destroying data (such as |.mw| files used by Maple). This means
+% that |.mw| files generated by versions before 2015 should be
+% deleted by hand.
+% \item \LuaTeX{} allows $128$ \tn{write} streams, so this package does
+% nothing (with a warning) when used with \LuaTeX{}.
+% \end{itemize}
+%
+% \subsection{Commands defined or altered by \pkg{morewrites}}
+%
+% \begin{function}[added = 2014-07-26]{\morewritessetup}
+% \begin{syntax}
+% \cs{morewritessetup} \Arg{key--value list}
+% \end{syntax}
+% Sets the options described by the \meta{key--value list}.
+% \end{function}
+%
+% \begin{function}[added = 2017-04-10]{allocate}
+% \begin{syntax}
+% \cs{morewritessetup} |{| |allocate| |=| \meta{integer} |}|
+% \end{syntax}
+% Sets to (at least) \meta{integer} the number of \tn{write} streams
+% allocated to the inner workings of \pkg{morewrites}. By default
+% this is zero but increasing this value to 10 (or so) may speed up
+% \pkg{morewrites}.
+% \end{function}
+%
+% \begin{function}[added = 2014-07-26, updated = 2015-08-01]{file}
+% \begin{syntax}
+% \cs{morewritessetup} |{| |file| |=| \meta{file name} |}|
+% \end{syntax}
+% Sets (globally) the name of the file which will be used by internal
+% processes of \pkg{morewrites}. The file name is \tn{jobname}|.mw|
+% by default (technically, \cs{c_sys_jobname_str}|.mw|). Contrarily
+% to earlier versions of \pkg{morewrites} non-empty files will not be
+% overwritten; this design choice may lead to unwanted |.mw| files
+% remaining.
+% \end{function}
+%
+% \begin{function}[updated = 2015-08-01]{\newwrite}
+% This macro is redefined by \pkg{morewrites}. Since \pkg{morewrites}
+% allows more than~$16$ write streams, it removes the corresponding
+% restrictions in \tn{newwrite}.
+% \begin{texnote}
+% The revised \tn{newwrite} allocate stream numbers starting at
+% $19$. This might break some code that expects stream numbers to
+% be less than $16$.
+% \end{texnote}
+% \end{function}
+%
+% \begin{function}[updated = 2015-08-01]{\immediate}
+% This primitive is altered by \pkg{morewrites}, to detect a following
+% \tn{write} or \tn{openout} or \tn{closeout} and perform the
+% appropriate action.
+% \end{function}
+%
+% \begin{function}[updated = 2017-04-20]{\openout, \write, \closeout}
+% These three primitives are altered by \pkg{morewrites} so that they
+% accept stream numbers outside the normal range $[0,15]$ and
+% open/write/close files as appropriate.
+% \begin{texnote}
+% System calls using \tn{write}|18| are detected and forwarded to the engine.
+% \end{texnote}
+% \end{function}
+%
+% \begin{function}{\shipout}
+% This primitive is altered by \pkg{morewrites} to ensure that delayed
+% \tn{openout}, \tn{write} and \tn{closeout} commands are performed at
+% \tn{shipout} time, and in the correct order.
+% \end{function}
+%
+% \subsection{Known deficiencies and open questions}
+%
+% See the bug tracker \url{https://github.com/blefloch/latex-morewrites/issues/}
+% for a list of issues with \pkg{morewrites}.
+%
+% The package code is not good \pkg{expl3} code. \emph{Do not take this
+% package as an example of how to code with \pkg{expl3}; go and see
+% Joseph Wright's \pkg{siunitx} instead.} It uses
+% \cs[no-index]{\ldots{}:D} primitives directly (the |:D| stands for
+% \enquote{do not use}). This is unavoidable in order to hook into the
+% primitives \tn{immediate}, \tn{write}, \emph{etc.\@} and to keep a
+% very strong control on what every command does.
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{morewrites} implementation}
+%
+%<*package>
+% \begin{macrocode}
+\RequirePackage {expl3} [2018/02/21]
+\RequirePackage {primargs} [2018/12/29]
+\ProvidesExplPackage
+ {morewrites} {2018/12/29} {} {Always room for a new write}
+% \end{macrocode}
+%
+% Quit early under \LuaTeX{}.
+% \begin{macrocode}
+\sys_if_engine_luatex:T
+ {
+ \cs_new_protected:Npn \morewritessetup #1 { }
+ \msg_new:nnn { morewrites } { luatex }
+ { The~morewrites~package~is~unnecessary~in~LuaTeX. }
+ \msg_warning:nn { morewrites } { luatex }
+ \tex_endinput:D
+ }%
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=morewrites>
+% \end{macrocode}
+%
+% \subsection{Overview of relevant \TeX{} facts}
+%
+% The aim of the \pkg{morewrites} package is to lift \TeX{}'s
+% restriction of only having $16$ files open for writing at the same
+% time. This requires patching the primitives \tn{immediate},
+% \tn{openout}, \tn{write}, \tn{closeout}, and \tn{shipout}, and the
+% macro \tn{newwrite} present in plain \TeX{} and \LaTeXe{}.
+%
+% Note that doing the same for \tn{read} streams is impossible due to
+% the \tn{ifeof} primitive: that primitive cannot be replaced by a macro
+% without breaking nesting of conditionals.
+%
+% The \pkg{morewrites} package should be loaded as early as possible, so
+% that any package loaded later uses the redefined macros instead of the
+% primitives. However, the format (plain \TeX{} or \LaTeXe{}) and the
+% \pkg{expl3} programming language are always loaded before
+% \pkg{morewrites}, and their interaction must be carefully monitored.
+%
+% Henceforth, ``\TeX{} stream'' will refer to stream numbers in the
+% range $[0,15]$ provided to \TeX{}'s write primitives, while
+% ``user stream'' will denote stream numbers in $[0,15]\cup[19,\infty)$
+% manipulated by the redefined \tn{openout}, \tn{write}, \tn{closeout},
+% and \tn{newwrite}. A user stream in $[0,15]$ (reserved by \LaTeXe{}
+% or allocated by \pkg{expl3}) is mapped to the same \TeX{} stream
+% number, while a user stream in $[19,\infty)$ is mapped to a \TeX{}
+% stream according to the property list (with integer keys and values)
+% \cs{l_@@_write_prop}. Stream numbers $16$,
+% $17$ and $18$ are unused because \tn{write}16 is often used to write
+% to the terminal, and \tn{write}18 sends its argument to a shell.
+%
+% The primitives \tn{openout}, \tn{write}, and \tn{closeout} expect to
+% be followed by an \meta{integer}, normally in the range $[0,15]$, then
+% some further arguments.
+% \begin{quote}
+% \tn{openout} \meta{integer} \meta{equals} \meta{file name} \\
+% \tn{write} \meta{integer} \meta{filler} \meta{general text} \\
+% \tn{closeout} \meta{integer}
+% \end{quote}
+% All of the primitives above perform full expansion of all tokens when
+% looking for their operands.
+% \begin{itemize}
+% \item \meta{integer} denotes an integer in any form that \TeX{}
+% accepts as the right-hand side of a primitive integer assignment of
+% the form \tn{count}|0=|\meta{integer};
+% \item \meta{equals} is an arbitrary (optional) number of explicit or
+% implicit space characters, an optional explicit equal sign of
+% category other, and further (optional) explicit or implicit space
+% characters;
+% \item \meta{file name} is an arbitrary sequence of explicit or implicit
+% characters with arbitrary category codes (except active characters,
+% which are expanded before reaching \TeX{}'s mouth), ending either
+% with a space character (character code $32$, arbitrary non-active
+% category code, explicit or implicit), which is removed, or with a
+% non-expandable token, with some care needed for the case of a
+% \tn{notexpanded:} expandable token;
+% \item \meta{filler} is an arbitrary combination of tokens whose
+% meaning is \tn{relax} or whose category code is $10$;
+% \item \meta{general text} is formed of braced tokens, starting with an
+% explicit or implicit begin-group character, and ending with the
+% matching explicit end-group character (both with any character
+% code), with an equal number of explicit begin-group and end-group
+% characters in between: this is precisely the right-hand side of an
+% assignment of the form \tn{toks}|0=|\meta{general text}.
+% \end{itemize}
+%
+% The \pkg{morewrites} package redefines these three control sequences
+% to expect a user stream number rather than a \TeX{} stream number as
+% the \meta{integer}, then map such a user stream to a \TeX{} stream to
+% call the primitive with the appropriate argument. The primitive
+% \tn{immediate} must also be redefined to detect \tn{openout},
+% \tn{write}, and \tn{closeout} and make them immediate, while still
+% working with other primitives that can be made immediate. Finally,
+% \tn{newwrite} must be patched to allocate stream numbers beyond $15$.
+%
+% A few comments on the behaviour of primitives concerning the
+% \meta{integer} (\TeX{} stream). The \tn{openout} primitive trigger
+% errors if the \meta{integer} is not in $[0,15]$. The primitive
+% \tn{write} outputs to the log if the \meta{integer} is negative, and
+% to the terminal if the \TeX{} stream is closed or greater than $15$,
+% with the exception of \tn{write}|18| which runs code in a shell. The
+% \tn{closeout} primitive triggers an error if the \meta{integer} is not
+% in $[0,15]$ and silently do nothing if the \TeX{} stream is not open,
+% with the exception of \tn{closeout}|18| which causes a segfault at
+% least in some versions.
+%
+% By default, \tn{openout}, \tn{write} and \tn{closeout} are recorded in
+% a whatsit node in the current list, and will be performed when the box
+% containing the whatsit node is sent to the final \texttt{pdf},
+% \emph{i.e.}, at \enquote{shipout} time. In particular, the
+% \meta{general text} for the \tn{write} primitive is expanded at
+% shipout time. This behaviour may be modified by putting
+% \tn{immediate} before any of these three primitives to force \TeX{} to
+% perform the action immediately instead of recording it in a whatsit
+% node.
+%
+% Since the \tn{openout}, \tn{write}, and \tn{closeout} primitives
+% operate at \tn{shipout} time, we will have to hook into this primitive
+% too. It expects to be followed by a box specification, for instance
+% \tn{box}\meta{integer} or \tn{hbox}\Arg{material to typeset}.
+%
+% Finally, the \tn{newwrite} macro expects one token as its argument,
+% and defines this token (with \tn{chardef}) to be an integer
+% corresponding to the first available (\TeX{}) write stream. This must
+% be extended to allocate higher (user) streams.
+%
+% \subsection{Preliminaries}
+%
+% \subsubsection{Copying some commands}
+%
+% \begin{macro}
+% {
+% \@@_tex_immediate:w ,
+% \@@_tex_openout:w ,
+% \@@_tex_write:w ,
+% \@@_tex_closeout:w ,
+% }
+% Aliases for the write-related primitives, to avoid
+% having |:D| throughout the code.
+% \begin{macrocode}
+\cs_new_eq:NN \@@_tex_immediate:w \tex_immediate:D
+\cs_new_eq:NN \@@_tex_openout:w \tex_openout:D
+\cs_new_eq:NN \@@_tex_write:w \tex_write:D
+\cs_new_eq:NN \@@_tex_closeout:w \tex_closeout:D
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_tex_newwrite:N}
+% Copy \tn{newwrite} but making sure that it is
+% not \tn{outer}. This copy will not be affected by redefinitions
+% of \tn{newwrite} later on.
+% \begin{macrocode}
+\exp_args:NNf \cs_new_protected:Npn \@@_tex_newwrite:N
+ { \exp_args:NNc \exp_after:wN \exp_stop_f: { newwrite } }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsubsection{Variants}
+%
+% \begin{macro}{\prop_gpop:NVNT, \prop_gput:NVx, \tl_gput_right:Nv}
+% We need these variants.
+% \begin{macrocode}
+\cs_generate_variant:Nn \prop_gpop:NnNT { NV }
+\cs_generate_variant:Nn \prop_gput:Nnn { NVx }
+\cs_generate_variant:Nn \tl_gput_right:Nn { Nv }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsubsection{Variables}
+%
+% \begin{variable}{\l_@@_internal_tl}
+% Used for temporary scratch purposes.
+% \begin{macrocode}
+\tl_new:N \l_@@_internal_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\@@_tmp:w}
+% Used for temporary definitions.
+% \begin{macrocode}
+\cs_new_eq:NN \@@_tmp:w ?
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_later_int}
+% The integer \cs{g_@@_later_int} labels the various
+% non-immediate operations in the order in which they appear in the
+% source. We can never reuse a number because there is no way to know
+% if a whatsit was recorded in a box register, which could be reused
+% in a shipped-out box:
+% \begin{quote}
+% \cs{vbox_set:Nn} \cs{l_my_box} \\
+% ~~|{| \cs{iow_shipout_x:Nn} \cs{c_term_iow} \Arg{text} |}|
+% \tn{shipout} \tn{copy} \cs{l_my_box}
+% \tn{shipout} \tn{copy} \cs{l_my_box}
+% \end{quote}
+% will print \meta{text} to the terminal twice.
+% \begin{macrocode}
+\int_new:N \g_@@_later_int
+% \end{macrocode}
+% \end{variable}
+%
+% ^^A todo: populate \g_@@_write_seq because we don't use \newwrite; this should be done in \morewritessetup
+% \begin{variable}{\g_@@_write_seq}
+% Keep track of \TeX{} stream numbers managed by \pkg{morewrites} that
+% are currently not in use as user streams.
+% \begin{macrocode}
+\seq_new:N \g_@@_write_seq
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_write_prop}
+% Map user streams to \TeX{} streams.
+% \begin{macrocode}
+\prop_new:N \g_@@_write_prop
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_write_file_prop}
+% Map user streams with no associated \TeX{} streams to file names.
+% \begin{macrocode}
+\prop_new:N \g_@@_write_file_prop
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_code_tl}
+% Stores the code to run after finding a user stream, in
+% \cs{@@_get_user:n}.
+% \begin{macrocode}
+\tl_new:N \l_@@_code_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_user_int, \l_@@_tstr_tl}
+% The user stream number following redefined primitives is stored in
+% \cs{l_@@_user_int} (see \cs{@@_get_user:N}). The corresponding
+% \TeX{} stream number is eventually stored in \cs{l_@@_tstr_tl} (a
+% token list).
+% \begin{macrocode}
+\int_new:N \l_@@_user_int
+\tl_new:N \l_@@_tstr_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\l_@@_tstr_token}
+% This token is given as an argument to \cs{@@_tex_newwrite:N}.
+% \begin{macrocode}
+\cs_new_eq:NN \l_@@_tstr_token ?
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\s_@@}
+% A recognizable version of \cs{scan_stop:}. This is inspired
+% by\footnote{Historically, this might have happened the other way
+% around, since the author of this package is also on the \LaTeX3
+% Team.} scan marks (see the \pkg{l3quark} module of \LaTeX3), but
+% \cs{scan_new:N} is not used directly, since it is has been made
+% available in \LaTeX3 too recently.
+% \begin{macrocode}
+\cs_new_eq:NN \s_@@ \scan_stop:
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{variable}{\g_@@_iow, \g_@@_ior}
+% The expansion that \tn{write} performs is impossible to emulate (in \XeTeX{} at least) with
+% anything else than \tn{write}. We will write on the stream
+% \cs{g_@@_iow} to the file \cs{g_@@_tmp_file_tl} and read back from
+% it in the stream \cs{g_@@_ior} for things to work properly.
+% Unfortunately, this means that the file is repeatedly opened and
+% closed, leaving a trace of that in the log.
+% \begin{macrocode}
+\newwrite \g_@@_iow
+\newread \g_@@_ior
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_tmp_file_tl, \g_@@_tmp_file_bool}
+% Temporary file used to do the correct expansion for each \tn{write}.
+% Boolean indicating whether we have already checked that the file can
+% be used by \pkg{morewrites}: before using a file, the
+% \pkg{morewrites} package now checks it is empty, so as to avoid
+% clobbering user data.
+% \begin{macrocode}
+\tl_new:N \g_@@_tmp_file_tl
+\bool_new:N \g_@@_tmp_file_bool
+\bool_gset_false:N \g_@@_tmp_file_bool
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_group_level_int}
+% The group level when \tn{shipout} is called: this is used to
+% distinguish between explicit boxes and box registers.
+% \begin{macrocode}
+\int_new:N \g_@@_group_level_int
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{variable}{\g_@@_shipout_box}
+% The page to be shipped out.
+% \begin{macrocode}
+\box_new:N \g_@@_shipout_box
+% \end{macrocode}
+% \end{variable}
+%
+% \subsubsection{Helpers for auxiliary file}
+%
+% \begin{macro}{\@@_set_file:n}
+% Sets \cs{g_@@_tmp_file_tl} to the given value (initially
+% \cs{c_sys_jobname_str}|.mw|). Mark that the file has not been
+% checked.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_set_file:n #1
+ {
+ \bool_gset_false:N \g_@@_tmp_file_bool
+ \tl_gset:Nn \g_@@_tmp_file_tl {#1}
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_empty_file:n}
+% Empties the file \cs{g_@@_tmp_file_tl} by opening it and closing it
+% right away. This is used when performing \tn{immediate}
+% \tn{openout}. It is also used to ensure the file used by
+% \pkg{morewrites} is left empty. We do this every time the auxiliary
+% file is used, in case that run ends with an error mid-document.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_empty_file:n #1
+ {
+ \@@_tex_immediate:w \@@_tex_openout:w
+ \g_@@_iow = #1 \scan_stop:
+ \@@_tex_immediate:w \@@_tex_closeout:w
+ \g_@@_iow
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[TF]{\@@_if_file_trivial:n}
+% True if the file does not exist, or if it is empty.
+% Only the \texttt{TF} variant is defined. We set
+% \cs{@@_tmp:w} to \cs{prg_return_true:} or \cs{prg_return_false:}
+% within the group and use it after cleaning up. The first
+% \texttt{eof} test is \texttt{true} if the file does not exist.
+% Then we read one line, the second \texttt{eof} test is
+% \texttt{true} if the file was empty (it is \texttt{false} if
+% the file contained anything, even a single space).
+% \begin{macrocode}
+\prg_new_conditional:Npnn \@@_if_file_trivial:n #1 { TF }
+ {
+ \group_begin:
+ \tex_openin:D \g_@@_ior = #1 \scan_stop:
+ \if_eof:w \g_@@_ior
+ \cs_gset_eq:NN \@@_tmp:w \prg_return_true:
+ \else:
+ \int_set:Nn \tex_endlinechar:D { -1 }
+ \tex_readline:D \g_@@_ior to \l_@@_internal_tl
+ \if_eof:w \g_@@_ior
+ \cs_gset_eq:NN \@@_tmp:w \prg_return_true:
+ \else:
+ \cs_gset_eq:NN \@@_tmp:w \prg_return_false:
+ \fi:
+ \fi:
+ \tex_closein:D \g_@@_ior
+ \group_end:
+ \@@_tmp:w
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_chk_file:}
+% Check that the file \cs{g_@@_tmp_file_tl} does not exist or is blank.
+% If not, try the file name obtained by adding |.mw|.
+% This avoids clobbering files that the user would not want to lose.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_chk_file:
+ {
+ \@@_if_file_trivial:nTF { \g_@@_tmp_file_tl }
+ { \bool_gset_true:N \g_@@_tmp_file_bool }
+ {
+ \msg_warning:nnxx { morewrites } { file-exists }
+ { \g_@@_tmp_file_tl }
+ { \g_@@_tmp_file_tl .mw }
+ \tl_gput_right:Nn \g_@@_tmp_file_tl { .mw }
+ \@@_chk_file:
+ }
+ }
+\msg_new:nnnn { morewrites } { file-exists }
+ { File~'#1'~exists,~using~'#2'~instead. }
+ {
+ The~file~`#1'~exists~and~was~not~created~by~this~version~of~the~
+ `morewrites'~package.~Please~move~or~delete~that~file,~or~provide~
+ another~file~name~by~adding
+ \\ \\
+ \iow_indent:n { \iow_char:N\\morewritessetup~{~file~=~other-name~} }
+ \\ \\
+ to~your~source~file.~In~the~meantime,~the~file~`#2'~will~be~used.
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsubsection{Parsing and other helpers}
+%
+% \begin{macro}{\@@_equals_file:N}
+% Most of the parsing for primitive arguments is done using
+% \pkg{primargs}, except for one case we care about: after its
+% \meta{number} argument, the \tn{openout} primitive expects an
+% \meta{equals} (optional spaces and |=|) and a \meta{file name}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_equals_file:N #1
+ {
+ \group_begin:
+ \tex_aftergroup:D \primargs_get_file_name:N
+ \tex_aftergroup:D #1
+ \primargs_remove_equals:N \group_end:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_get_user:n}
+% \pkg{primargs} commands only take \texttt{N}-type arguments, but we
+% often need to find an integer, save it in \cs{l_@@_user_int}, and
+% run some code |#1|. This is analogous to \cs{primargs_get_number:N}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_get_user:n #1
+ {
+ \tl_set:Nn \l_@@_code_tl {#1}
+ \tex_afterassignment:D \l_@@_code_tl
+ \l_@@_user_int =
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_user_to_tstr:NTF}
+% The goal is to go from a user stream \cs{l_@@_user_int} to a \TeX{}
+% stream \cs{l_@@_tstr_tl} (it defaults to the user stream). Streams
+% less than $19$ are not managed by \pkg{morewrites}: actual \TeX{}
+% streams in $[0,15]$; negative for writing to \texttt{log}; $16$,
+% $17$ for writing to terminal; $18$ for shell escape. Larger stream
+% numbers are looked up in the property list |#1|, namely
+% \cs{g_@@_write_prop}. If present, use the corresponding value as
+% the \TeX{} stream, otherwise run the \texttt{false} branch.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_user_to_tstr:NTF #1
+ {
+ \tl_set:NV \l_@@_tstr_tl \l_@@_user_int
+ \int_compare:nNnTF { \l_@@_user_int } < { 19 }
+ { \use_i:nn }
+ { \prop_get:NVNTF #1 \l_@@_user_int \l_@@_tstr_tl }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{variable}{\l_@@_collect_next_int}
+% \begin{macro}
+% {
+% \@@_collect:x, \@@_collect_aux:Nn,
+% \@@_collect_aux:cf, \@@_collect_gput_right:N, \@@_collect_gput_right:c
+% }
+% When encountering very large \tn{write} statements we may need to
+% collect many lines. This can easily become an $O(n^2)$ task, and
+% here we make sure that it remains around $O(n\log n)$, with a large
+% constant unfortunately. Each of the token lists
+% \cs[no-index]{l_@@_$k$_tl} is empty or contains $2^k$ lines. As
+% lines accumulate, they move to token lists with larger values
+% of~$k$, and eventually all are combined. The integer
+% \cs{l_@@_collect_next_int} is (one plus) the maximal $k$ among
+% non-empty token lists.
+% \begin{macrocode}
+\int_new:N \l_@@_collect_next_int
+\cs_new_protected:Npn \@@_collect:x #1
+ {
+ \tl_set:Nx \l_@@_internal_tl {#1}
+ \@@_collect_aux:cf { l_@@_0_tl } { 1 }
+ }
+\cs_new_protected:Npn \@@_collect_aux:Nn #1#2
+ {
+ \int_compare:nNnT {#2} > \l_@@_collect_next_int
+ {
+ \tl_clear_new:N #1
+ \int_set:Nn \l_@@_collect_next_int {#2}
+ }
+ \tl_if_empty:NTF #1
+ { \tl_set_eq:NN #1 \l_@@_internal_tl }
+ {
+ \tl_put_left:No \l_@@_internal_tl {#1}
+ \tl_clear:N #1
+ \@@_collect_aux:cf { l_@@_#2_tl }
+ { \int_eval:n { #2 + 1 } }
+ }
+ }
+\cs_generate_variant:Nn \@@_collect_aux:Nn { cf }
+\cs_new_protected:Npn \@@_collect_gput_right:N #1
+ {
+ \int_compare:nNnF \l_@@_collect_next_int = 0
+ {
+ \int_decr:N \l_@@_collect_next_int
+ \tl_gput_right:Nv #1
+ {
+ l_@@_
+ \int_use:N \l_@@_collect_next_int
+ _tl
+ }
+ \@@_collect_gput_right:N #1
+ }
+ }
+\cs_generate_variant:Nn \@@_collect_gput_right:N { c }
+% \end{macrocode}
+% \end{macro}
+% \end{variable}
+%
+% \begin{macro}[EXP]{\@@_user_tl_name:n}
+% The name of a global token list variable holding the text of a given
+% user stream.
+% \begin{macrocode}
+\cs_new:Npn \@@_user_tl_name:n #1
+ { g_@@_iow_ \int_eval:n {#1} _tl }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Writing}
+%
+% We can hold on to material while a file is being written and only
+% write it in one go once the file closes, to avoid using a stream
+% throughout.
+%
+% At any given time, each user stream may point to an open \TeX{}
+% stream, given in \cs{g_@@_write_prop}, or may point to a token list
+% that will eventually be written to a file whose file name is stored in
+% \cs{g_@@_write_file_prop}, or may be closed.
+%
+% When a user stream points to a token list rather than a \TeX{} stream,
+% any material to be written must be written to our temporary file and
+% read back in to apply the same expansion as \tn{write} does.
+%
+% Another difficulty is that users may mix immediate and non-immediate
+% operations. The biggest difficulty comes from the possibility of
+% copying boxes containing delayed actions. If we ever produced a
+% whatsit \tn{write}\meta{number}\Arg{text} then the \TeX{} stream
+% \meta{number} would have to be reserved forever, as as copies of the
+% box containing this delayed actions may be shipped out at any later
+% point in the document.
+%
+% Each delayed action is thus saved in a separate numbered token list
+% and \tn{write}\cs{g_@@_iow}\Arg{number} is inserted instead of the
+% delayed action. At each \tn{shipout}, the stream \cs{g_@@_iow} is
+% opened, to catch the \meta{number} of each action that should be
+% performed at this \tn{shipout}.
+%
+% \subsubsection{Redefining \tn{immediate}}
+%
+% To accomodate the \tn{immediate} primitive, our versions of
+% \tn{openout}, \tn{write} and \tn{closeout} will take the form
+% \begin{quote}
+% \cs{s_@@} \cs{use_i:nn}
+% \quad \Arg{code for delayed action} \\
+% \quad \Arg{code for immediate action} \\
+% \meta{further code}
+% \end{quote}
+% The leading \cs{s_@@} allows the redefined \tn{immediate} to detect
+% these redefined primitives, and to run the \meta{code for immediate
+% action} instead of the \meta{code for delayed action} which is run by
+% default. In both cases, any \meta{further code} is run.
+%
+% \begin{macro}[updated = 2012-12-05]{\@@_immediate:w}
+% \begin{macro}{\@@_immediate_auxii:, \@@_immediate_auxiii:N}
+% \TeX{}'s \tn{immediate} primitive raises a flag which is cancelled
+% after \TeX{} sees a non-expandable token. We use
+% \cs{primargs_read_x_token:N} to find the next non-expandable token
+% then test for \tn{openout}, \tn{write}, and \tn{closeout}. More
+% precisely we test for the marker \cs{s_@@} and run the appropriate
+% code as described above. Otherwise we call the primitive, for cases
+% where the next token is \tn{pdfobj} or similar. This code performs
+% too much expansion for some nonsensical uses of \tn{noexpand} after
+% \tn{immediate}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_immediate:w
+ { \primargs_read_x_token:N \@@_immediate_auxii: }
+\cs_new_protected:Npn \@@_immediate_auxii:
+ {
+ \token_if_eq_meaning:NNTF \g_primargs_token \s_@@
+ { \@@_immediate_auxiii:N }
+ { \@@_tex_immediate:w }
+ }
+\cs_new_protected:Npn \@@_immediate_auxiii:N #1
+ { \str_if_eq:nnTF { #1 } { \s_@@ } { \use_iii:nnn } { #1 } }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsubsection{Immediate actions}
+%
+% The \tn{openout}, \tn{write}, and \tn{closeout} primitive can be
+% either delayed or immediate. In all cases they begin by looking for a
+% user stream. Here, we implement the immediate versions only.
+%
+% \begin{macro}{\@@_closeout:w, \@@_closeout_now:, \@@_closeout_now:nn}
+% In the immediate case \cs{@@_closeout_now:}, there are three cases.
+% The stream may point to a \TeX{} stream, in which case it is closed,
+% removed from \cs{g_@@_write_prop}, and put back in the list of
+% usable streams. The stream may point to a token list, in which case
+% that token list should be written to the appropriate file. The
+% stream may be closed, in which case nothing happens.
+% The auxiliary \cs{@@_closeout_now:nn} writes the material collected
+% so far for a given user stream |#1| to the file |#2|. This uses the
+% \TeX{} stream \cs{g_@@_iow}. The token list consists of multiple
+% \tn{immediate} \tn{write} \cs{g_@@_iow} \Arg{text} statements
+% because that is the only safe way to obtain new lines. We do not
+% remove the stream/file pair from \cs{g_@@_write_file_prop}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_closeout:w
+ {
+ \s_@@
+ \use_i:nn
+ { \@@_get_user:n { \@@_closeout_later: } }
+ { \@@_get_user:n { \@@_closeout_now: } }
+ }
+\cs_new_protected:Npn \@@_closeout_now:
+ {
+ \@@_user_to_tstr:NTF \g_@@_write_prop
+ {
+ \@@_tex_immediate:w \@@_tex_closeout:w \l_@@_tstr_tl \exp_stop_f:
+ \int_compare:nNnF { \l_@@_tstr_tl } = { \l_@@_user_int }
+ {
+ \prop_gremove:NV \g_@@_write_prop \l_@@_user_int
+ \seq_gput_left:NV \g_@@_write_seq \l_@@_tstr_tl
+ }
+ }
+ {
+ \prop_gpop:NVNT \g_@@_write_file_prop \l_@@_user_int \l_@@_internal_tl
+ { \@@_closeout_now:nn { \l_@@_user_int } { \l_@@_internal_tl } }
+ }
+ }
+\cs_new_protected:Npn \@@_closeout_now:nn #1#2
+ {
+ \@@_tex_immediate:w \@@_tex_openout:w \g_@@_iow = #2 \scan_stop:
+ \group_begin:
+ \int_set:Nn \tex_newlinechar:D { -1 }
+ \tl_use:c { \@@_user_tl_name:n {#1} }
+ \tl_gclear:c { \@@_user_tl_name:n {#1} }
+ \group_end:
+ \@@_tex_immediate:w \@@_tex_closeout:w \g_@@_iow
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_openout:w, \@@_openout_now:n}
+% In the immediate case find a file name, then allocate a \TeX{}
+% stream if possible, and otherwise point the user stream to a token
+% list. In all cases, close the stream to avoid losing any material
+% that \TeX{} would have written, and empty the file by opening and
+% closing it (actually that's done automatically by the primitive).
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_openout:w
+ {
+ \s_@@
+ \use_i:nn
+ { \@@_get_user:n { \@@_openout_later:w } }
+ { \@@_get_user:n { \@@_equals_file:N \@@_openout_now:n } }
+ }
+\cs_new_protected:Npn \@@_openout_now:n #1
+ {
+ \@@_closeout_now:
+ \int_compare:nNnTF { \l_@@_user_int } < { 19 }
+ {
+ \@@_tex_immediate:w \@@_tex_openout:w \l_@@_user_int
+ = \tl_to_str:n {#1} \scan_stop:
+ }
+ {
+ \seq_gpop:NNTF \g_@@_write_seq \l_@@_tstr_tl
+ {
+ \prop_gput:NVV \g_@@_write_prop \l_@@_user_int \l_@@_tstr_tl
+ \@@_tex_immediate:w \@@_tex_openout:w \l_@@_tstr_tl \exp_stop_f:
+ = \tl_to_str:n {#1} \scan_stop:
+ }
+ {
+ \@@_empty_file:n {#1}
+ \prop_gput:NVx \g_@@_write_file_prop \l_@@_user_int
+ { \tl_to_str:n {#1} }
+ \tl_gclear_new:c { \@@_user_tl_name:n { \l_@@_user_int } }
+ }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_write:w, \@@_write_now:w, \@@_write_now:n}
+% In the immediate case we use \cs{@@_write_now_open:n} if the stream
+% points to a token list, and otherwise use the primitive, with the
+% dummy stream $16$ if closed (the text is then written to the
+% terminal).
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_write:w
+ {
+ \s_@@
+ \use_i:nn
+ { \@@_get_user:n { \@@_write_later:w } }
+ { \@@_get_user:n { \@@_write_now:w } }
+ }
+\cs_new_protected:Npn \@@_write_now:w
+ {
+ \@@_user_to_tstr:NTF \g_@@_write_prop
+ { \@@_tex_immediate:w \@@_tex_write:w \l_@@_tstr_tl \exp_stop_f: }
+ { \primargs_get_general_text:N \@@_write_now:n }
+ }
+\cs_new_protected:Npn \@@_write_now:n
+ {
+ \prop_get:NVNTF \g_@@_write_file_prop \l_@@_user_int \l_@@_internal_tl
+ { \@@_write_now_open:n }
+ { \@@_tex_immediate:w \@@_tex_write:w 16 }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_write_now_open:n}
+% \begin{macro}{\@@_write_now_loop:}
+% Only \tn{write} itself can emulate how \tn{write} expands tokens,
+% because |#| don't have to be doubled, and because the
+% \tn{newlinechar} has to be changed to new lines. Hence, we start by
+% writing |#1| to a file (after making sure we are allowed to alter
+% it), yielding some lines. The lines are then read one at a time
+% using \eTeX{}'s \tn{readline} with \tn{endlinechar} set to $-1$ to
+% avoid spurious characters. Each line becomes a \tn{immediate}
+% \tn{write} statement added to a token list whose name is constructed
+% using \cs{@@_user_tl_name:n}. This token list will be called when
+% it is time to actually write to the file. At that time,
+% \tn{newlinechar} will be $-1$, so that writing each line will
+% produce no extra line.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_write_now_open:n #1
+ {
+ \bool_if:NF \g_@@_tmp_file_bool { \@@_chk_file: }
+ \@@_tex_immediate:w \@@_tex_openout:w
+ \g_@@_iow = \g_@@_tmp_file_tl \scan_stop:
+ \@@_tex_immediate:w \@@_tex_write:w
+ \g_@@_iow {#1}
+ \@@_tex_immediate:w \@@_tex_closeout:w
+ \g_@@_iow
+ \group_begin:
+ \int_set:Nn \tex_endlinechar:D { -1 }
+ \tex_openin:D \g_@@_ior = \g_@@_tmp_file_tl \scan_stop:
+ \@@_write_now_loop:
+ \tex_closein:D \g_@@_ior
+ \@@_collect_gput_right:c
+ { \@@_user_tl_name:n { \l_@@_user_int } }
+ \group_end:
+ \@@_empty_file:n { \g_@@_tmp_file_tl }
+ }
+\cs_new_protected:Npn \@@_write_now_loop:
+ {
+ \tex_readline:D \g_@@_ior to \l_@@_internal_tl
+ \ior_if_eof:NF \g_@@_ior
+ {
+ \@@_collect:x
+ {
+ \@@_tex_immediate:w \@@_tex_write:w
+ \g_@@_iow { \l_@@_internal_tl }
+ }
+ \@@_write_now_loop:
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+%
+% \subsubsection{Delayed actions}
+%
+% \begin{macro}{\@@_later:n, \@@_later_do:n}
+% Store the action to be done at shipout in a token list, and
+% non-immediately write the label \cs{g_@@_later_int} of the
+% output operation to the temporary file.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_later:n #1
+ {
+ \int_gincr:N \g_@@_later_int
+ \tl_const:cx
+ {
+ c_@@_later_
+ \int_use:N \g_@@_later_int
+ _tl
+ }
+ {
+ \int_set:Nn \exp_not:N \l_@@_user_int
+ { \exp_not:V \l_@@_user_int }
+ \exp_not:n {#1}
+ }
+ \exp_args:NNx \@@_tex_write:w \g_@@_iow
+ { `( \int_use:N \g_@@_later_int ) }
+ }
+\cs_new_protected:Npn \@@_later_do:n #1
+ { \tl_use:c { c_@@_later_ \int_eval:n {#1} _tl } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_closeout_later:}
+% If the user stream is a \TeX{} stream, use the primitive, otherwise
+% save \cs{@@_closeout_now:} for later.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_closeout_later:
+ {
+ \int_compare:nNnTF \l_@@_user_int < { 19 }
+ { \@@_tex_closeout:w \l_@@_user_int }
+ { \@@_later:n { \@@_closeout_now: } }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_openout_later:w, \@@_openout_later:n}
+% If the user stream is a \TeX{} stream use the primitive, otherwise
+% find a file name and call \cs{@@_openout_now:n} later.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_openout_later:w
+ {
+ \int_compare:nNnTF \l_@@_user_int < { 19 }
+ { \@@_tex_openout:w \l_@@_user_int }
+ { \@@_equals_file:N \@@_openout_later:n }
+ }
+\cs_new_protected:Npn \@@_openout_later:n #1
+ { \@@_later:n { \@@_openout_now:n {#1} } }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_write_later:w, \@@_write_later:n, \@@_write_later_aux:n}
+% For \TeX{} streams use the primitive, otherwise find a general text
+% and save it for later; the auxiliary is very similar to
+% \cs{@@_write_now:w}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_write_later:w
+ {
+ \int_compare:nNnTF \l_@@_user_int < { 19 }
+ { \@@_tex_write:w \l_@@_user_int }
+ { \primargs_get_general_text:N \@@_write_later:n }
+ }
+\cs_new_protected:Npn \@@_write_later:n #1
+ { \@@_later:n { \@@_write_later_aux:n {#1} } }
+\cs_new_protected:Npn \@@_write_later_aux:n
+ {
+ \@@_user_to_tstr:NTF \g_@@_write_prop
+ { \@@_tex_immediate:w \@@_tex_write:w \l_@@_tstr_tl \exp_stop_f: }
+ { \@@_write_now:n }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsubsection{Shipout business}
+%
+% In this section, we hook into the \tn{shipout} primitive, and redefine
+% it to first build a box with the material to ship out, then perform
+% \begin{quote}
+% \cs{@@_before_shipout:} \\
+% \meta{primitive shipout} \meta{collected box} \\
+% \cs{@@_after_shipout:}
+% \end{quote}
+%
+% Each delayed output operation has been replaced by \tn{write}
+% \cs{g_@@_iow} |{`(|\meta{operation number}|)}|. The delimiters we
+% chose to put around numbers must be at least two distinct characters
+% on the left (then \cs{tex_newlinechar:D} cannot be equal to the
+% delimiter), and at least one non-digit character on the right.
+%
+% \begin{macro}{\@@_before_shipout:}
+% Immediately before the shipout, we must open the writing stream
+% \cs{g_@@_iow} (after making sure we are allowed to alter the
+% auxiliary file).
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_before_shipout:
+ {
+ \bool_if:NF \g_@@_tmp_file_bool { \@@_chk_file: }
+ \@@_tex_immediate:w \@@_tex_openout:w
+ \g_@@_iow = \g_@@_tmp_file_tl \scan_stop:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_after_shipout:}
+% \begin{macro}[rEXP]{\@@_after_shipout_loop:ww}
+% Immediately after all the \tn{write}s are performed, close the file,
+% then read the file with \tn{endlinechar} set to
+% \tn{newlinechar}\footnote{Note that the \tn{newlinechar} used by
+% \tn{write}s at \tn{shipout} time are those in effect when the page
+% is shipped out, \emph{i.e.}, just after the closing brace of the
+% \tn{shipout} construction, which is exactly where we have added
+% this hook.} to get exactly the original characters that have been
+% written, possibly with extra characters between |`(|\ldots{}|)|
+% groups. The file is then read with all the appropriate category
+% codes set up (no other character can appear in the file). The
+% looping auxiliary \cs{@@_after_shipout_loop:ww} extracts the
+% \meta{operation} numbers from the file, and makes a token list out
+% of those. This token list is then used in a mapping function to
+% perform the appropriate \tn{write} operations. Note that those
+% operations may reuse the file, so we have to fully parse the file
+% before moving on.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_after_shipout:
+ {
+ \@@_tex_immediate:w \@@_tex_closeout:w
+ \g_@@_iow
+ \group_begin:
+ \int_set_eq:NN \tex_endlinechar:D \tex_newlinechar:D
+ \char_set_catcode_other:n { \tex_endlinechar:D }
+ \tl_map_inline:nn { `(0123456789) }
+ { \char_set_catcode_other:n {`##1} }
+ \tex_everyeof:D { `() \exp_not:N }
+ \tl_set:Nx \l_@@_internal_tl
+ {
+ \exp_after:wN \@@_after_shipout_loop:ww
+ \tex_input:D \g_@@_tmp_file_tl \c_space_tl
+ }
+ \@@_empty_file:n { \g_@@_tmp_file_tl }
+ \exp_args:NNo
+ \group_end:
+ \tl_map_function:nN { \l_@@_internal_tl } \@@_later_do:n
+ }
+\cs_new:Npn \@@_after_shipout_loop:ww #1 `( #2 )
+ {
+ \tl_if_empty:nF {#2}
+ {
+ {#2}
+ \@@_after_shipout_loop:ww
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\@@_shipout:w}
+% \begin{macro}{\@@_shipout_i:, \@@_shipout_ii:}
+% Grab the shipped out box using \tn{setbox} and regain control using
+% \tn{afterassignment}. There are two cases: either the box is given
+% as \tn{box} or \tn{copy} followed by a number, in which case
+% \cs{@@_shipout_i:} is inserted afterwards at the same group level,
+% or the box is given as \tn{hbox} (or \tn{vtop} and so on) and an
+% additional \tn{aftergroup} is needed to reach a point where we can
+% use the box saved in \cs{g_@@_shipout_box}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_shipout:w
+ {
+ \int_gset_eq:NN \g_@@_group_level_int \tex_currentgrouplevel:D
+ \tex_afterassignment:D \@@_shipout_i:
+ \tex_global:D \tex_setbox:D \g_@@_shipout_box
+ }
+\cs_new_protected:Npn \@@_shipout_i:
+ {
+ \int_compare:nNnTF { \g_@@_group_level_int }
+ = { \tex_currentgrouplevel:D }
+ { \@@_shipout_ii: }
+ { \tex_aftergroup:D \@@_shipout_ii: }
+ }
+\cs_new_protected:Npn \@@_shipout_ii:
+ {
+ \@@_before_shipout:
+ \@@_tex_shipout:w \tex_box:D \g_@@_shipout_box
+ \@@_after_shipout:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\shipout, \@@_tex_shipout:w}
+% The task is now to locate the shipout primitive, which may have been
+% renamed and hooked into by many different packages loaded before
+% \pkg{morewrites}. Any of those control sequences which are equal to
+% the primitive are redefined to do \cs{@@_shipout:w} instead. If the
+% primitive is not located at all, the fallback is to hook into the
+% control sequence \tn{shipout}.
+% \begin{macrocode}
+\cs_gset_protected:Npn \@@_tmp:w #1
+ {
+ \cs_if_exist:NF \@@_tex_shipout:w
+ { \cs_new_eq:NN \@@_tex_shipout:w #1 }
+ \cs_gset_eq:NN #1 \@@_shipout:w
+ }
+\tl_map_inline:nn
+ {
+ \xyrealshipout@
+ \org@shipout
+ \PDFSYNCship@ut@ld
+ \CROP@shipout
+ \@soORI
+ \tex_shipout:D
+ \zwpl@Hship
+ \o@shipout@TP
+ \LL@shipout
+ \Shipout
+ \GXTorg@shipout
+ \AtBegShi@OrgShipout
+ \AtBeginShipoutOriginalShipout
+ \minidocument@orig@shipout
+ \shipout
+ }
+ {
+ \str_if_eq:eeT
+ { \cs_meaning:N #1 }
+ { \token_to_str:N \shipout }
+ { \@@_tmp:w #1 }
+ }
+\cs_if_exist:NF \@@_tex_shipout:w
+ {
+ \cs_new_eq:NN \@@_tex_shipout:w \shipout
+ \cs_gset_eq:NN \shipout \@@_shipout:w
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsubsection{Hook at the very end}
+%
+% \begin{macro}{\@@_close_all:}
+% At the end of the document, close all the files.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_close_all:
+ {
+ \prop_map_inline:Nn \g_@@_write_prop
+ { \@@_tex_immediate:w \@@_tex_closeout:w ##2 \scan_stop: }
+ \prop_gclear:N \g_@@_write_prop
+ \prop_map_function:NN \g_@@_write_file_prop
+ \@@_closeout_now:nn
+ \prop_gclear:N \g_@@_write_file_prop
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_close_all_at_end:nw}
+% At the end of the run, we try very hard to put some material at the
+% \tn{@@end}, just in case some other very late code writes to files
+% that are not yet closed. This is tried at most $5$~times, to avoid
+% infinite loops in case two packages compete for that last place.
+% The four |@| become two after \pkg{l3docstrip}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_close_all_at_end:nw #1#2 \@@@@end
+ {
+ \int_compare:nNnTF {#1} > 0
+ { #2 \@@_close_all_at_end:nw { #1 - 1 } }
+ { \@@_close_all: #2 }
+ \@@@@end
+ }
+\AtEndDocument { \@@_close_all_at_end:nw { 5 } }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Redefining commands}
+%
+% \subsubsection{Modified \tn{newwrite}}
+%
+% \begin{variable}{\g_@@_alloc_write_int}
+% Counter to allocate user streams. Initialized to $18$ so that the
+% first user stream allocated by \pkg{morewrites} is $19$. Indeed,
+% $18$ is reserved for shell commands and packages may expect $16$ or
+% $17$ to write to the terminal.
+% \begin{macrocode}
+\int_new:N \g_@@_alloc_write_int
+\int_gset:Nn \g_@@_alloc_write_int { 18 }
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_newwrite:N}
+% Reimplementation of \tn{newwrite} but protected and using a counter
+% \cs{g_@@_alloc_write_int} instead of what \TeX{}/\LaTeXe{} use.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_newwrite:N #1
+ {
+ \int_gincr:N \g_@@_alloc_write_int
+ \int_set_eq:NN \allocationnumber \g_@@_alloc_write_int
+ \cs_undefine:N #1
+ \int_const:Nn #1 { \allocationnumber }
+ \wlog
+ {
+ \token_to_str:N #1
+ = \token_to_str:N \write \int_use:N \allocationnumber
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_allocate:n}
+% Raise to |#1| the number of \tn{write} streams allocated to \pkg{morewrites}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_allocate:n #1
+ {
+ \prg_replicate:nn
+ {
+ \int_max:nn { 0 }
+ {
+ (#1) - \seq_count:N \g_@@_write_seq
+ - \prop_count:N \g_@@_write_prop
+ }
+ }
+ {
+ \@@_tex_newwrite:N \l_@@_tstr_token
+ \seq_gput_right:NV \g_@@_write_seq \l_@@_tstr_token
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{User commands and keys}
+%
+% \begin{macro}[added = 2014-07-26]{\morewritessetup}
+% Set whatever keys the user passes to \cs{morewritessetup}.
+% \begin{macrocode}
+\cs_new_protected:Npn \morewritessetup #1
+ { \keys_set:nn { @@ } {#1} }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[added = 2014-07-26]{file}
+% Because of our use of |.initial:n|, this code must appear after
+% \cs{@@_set_file:n} is defined.
+% \begin{macrocode}
+\keys_define:nn { @@ }
+ {
+ allocate .code:n = \@@_allocate:n {#1} ,
+ file .code:n = \@@_set_file:n {#1} ,
+ file .initial:n = \c_sys_jobname_str .mw
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}[updated = 2015-08-01]
+% {\immediate, \openout, \write, \closeout, \newwrite}
+% \begin{macrocode}
+\cs_gset_eq:NN \immediate \@@_immediate:w
+\cs_gset_eq:NN \openout \@@_openout:w
+\cs_gset_eq:NN \write \@@_write:w
+\cs_gset_eq:NN \closeout \@@_closeout:w
+\cs_gset_eq:NN \newwrite \@@_newwrite:N
+% \end{macrocode}
+% \end{macro}
+%
+%</package>
+%
+% \end{implementation}
+%
+% \clearpage
+% \PrintIndex
diff --git a/macros/latex/contrib/morewrites/morewrites.ins b/macros/latex/contrib/morewrites/morewrites.ins
new file mode 100644
index 0000000000..d9e17d55e0
--- /dev/null
+++ b/macros/latex/contrib/morewrites/morewrites.ins
@@ -0,0 +1,39 @@
+\iffalse meta-comment
+
+File morewrites.ins Copyright (C) 2012 Bruno Le Floch
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version. The latest version
+of this license is in the file
+
+ http://www.latex-project.org/lppl.txt
+
+This file is part of the "morewrites bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+The released version of this bundle is available from CTAN.
+
+\fi
+
+\let\jobname\relax
+\input l3docstrip.tex
+\askforoverwritefalse
+
+\preamble
+
+Communicate any suggestions for changing this package
+to Bruno Le Floch (blflatex@gmail.com).
+
+\endpreamble
+% stop docstrip adding \endinput
+\postamble
+\endpostamble
+
+\keepsilent
+
+% the package
+\generate{\file{morewrites.sty}{\from{morewrites.dtx}{package}}}
+\generate{\file{primargs.sty}{\from{primargs.dtx}{package}}}
+
+\endbatchfile
diff --git a/macros/latex/contrib/morewrites/morewrites.pdf b/macros/latex/contrib/morewrites/morewrites.pdf
new file mode 100644
index 0000000000..4b838b6779
--- /dev/null
+++ b/macros/latex/contrib/morewrites/morewrites.pdf
Binary files differ
diff --git a/macros/latex/contrib/morewrites/primargs.dtx b/macros/latex/contrib/morewrites/primargs.dtx
new file mode 100644
index 0000000000..5ba63d5e85
--- /dev/null
+++ b/macros/latex/contrib/morewrites/primargs.dtx
@@ -0,0 +1,918 @@
+% \iffalse meta-comment
+%
+%% File: primargs.dtx Copyright (C) 2012-2018 Bruno Le Floch
+%%
+%% It may be distributed and/or modified under the conditions of the
+%% LaTeX Project Public License (LPPL), either version 1.3c of this
+%% license or (at your option) any later version. The latest version
+%% of this license is in the file
+%%
+%% http://www.latex-project.org/lppl.txt
+%%
+%% This work has the LPPL maintenance status 'maintained'
+%% and the current maintainer is Bruno Le Floch.
+%% -----------------------------------------------------------------------
+%
+%<*driver>
+\documentclass[full]{l3doc}
+\newcommand{\proc}[1]{\texttt{#1}}
+\begin{document}
+ \DocInput{\jobname.dtx}
+\end{document}
+%</driver>
+% \fi
+%
+% \title{The \textsf{primargs} package: \\
+% Parsing arguments of primitives}
+% \author{Bruno Le Floch}
+% \date{2018/12/29}
+%
+% \maketitle
+% \tableofcontents
+%
+% \begin{documentation}
+%
+% \section{\pkg{primargs} documentation}
+%
+% This \TeX{} and \LaTeX{} package is currently used by \pkg{morewrites}
+% when redefining primitives: it allows to read arguments of primitives
+% in place of \TeX{}, which is useful to add hooks to primitives. Of
+% course, this is much slower than letting \TeX{} do things directly.
+%
+% All assignments done by this package are global. While a negative
+% value of the \tn{globaldefs} (primitive) parameter normally makes all
+% assignments local, this package makes sure \tn{globaldefs} is
+% non-negative before assignments.
+%
+% \subsection{Reading one token without removing it}
+%
+% \begin{variable}[tested = primargs001]{\g_primargs_token}
+% The token read by \cs{primargs_read_token:N} or
+% \cs{primargs_read_x_token:N}. Its value is always set globally.
+% It can be an \tn{outer} macro.
+% \end{variable}
+%
+% \begin{function}[tested = primargs001]{\primargs_read_token:N}
+% \begin{syntax}
+% \cs{primargs_read_token:N} \meta{function}
+% \end{syntax}
+% Sets \cs{g_primargs_token} equal to the token following the
+% \meta{function}, then calls the \meta{function}. The token
+% following the \meta{function} is not removed.
+% \begin{texnote}
+% This is essentially \tn{global} \tn{futurelet}
+% \cs{g_primargs_token} \meta{function}, with the added guarantee
+% that the assignment is global even when $\tn{globaldefs}$ is negative.
+% \end{texnote}
+% \end{function}
+%
+% \begin{function}[tested = primargs001]{\primargs_read_x_token:N}
+% \begin{syntax}
+% \cs{primargs_read_x_token:N} \meta{function}
+% \end{syntax}
+% Expands tokens recursively with \cs{exp_after:wN} until encountering
+% a non-expandable token and afterwards calls the \meta{function}.
+% The non-expandable token following the \meta{function} is not
+% removed and \cs{g_primargs_token} is also set (globally) equal to
+% that token.
+% \end{function}
+%
+% \subsection{Removing tokens}
+%
+% \begin{function}[tested = primargs005]{\primargs_remove_token:N}
+% \begin{syntax}
+% \cs{primargs_remove_token:N} \meta{function}
+% \end{syntax}
+% Removes the \meta{token} which follows the \meta{function}, then
+% calls the \meta{function}. This also sets \cs{g_primargs_token}
+% (globally) equal to the removed token.
+% \end{function}
+%
+% \begin{function}[tested = primargs005]{\primargs_remove_one_optional_space:N}
+% \begin{syntax}
+% \cs{primargs_remove_one_optional_space:N} \meta{function}
+% \end{syntax}
+% Expands tokens following the \meta{function} until a non-expandable
+% token is found, and sets \cs{g_primargs_token} (globally) equal to this token,
+% then removes the token if it has catcode~$10$ (space). Finally,
+% call the \meta{function}.
+% \end{function}
+%
+% \begin{function}[tested = primargs005]{\primargs_remove_optional_spaces:N}
+% \begin{syntax}
+% \cs{primargs_remove_optional_spaces:N} \meta{function}
+% \end{syntax}
+% Expands tokens following the \meta{function}, removing any token
+% with catcode~$10$ (space), then sets \cs{g_primargs_token} (globally) equal to
+% the first non-space token and calls the \meta{function}.
+% \end{function}
+%
+% \begin{function}[tested = primargs005]{\primargs_remove_equals:N}
+% \begin{syntax}
+% \cs{primargs_remove_equals:N} \meta{function}
+% \end{syntax}
+% Expands tokens following the \meta{function}, removing any token
+% with catcode~$10$ (space), then sets \cs{g_primargs_token} (globally) equal to
+% the first non-space token. If this token is an explicit~|=|
+% character token with catcode~$12$ (other), then it is removed as
+% well. Finally, calls the \meta{function}.
+% \end{function}
+%
+% \begin{function}[added = 2014-08-06, tested = primargs005]{\primargs_remove_filler:N}
+% \begin{syntax}
+% \cs{primargs_remove_filler:N} \meta{function}
+% \end{syntax}
+% Expands tokens following the \meta{function}, removing any token
+% with catcode~$10$ (space) or equal to \tn{relax}, then sets
+% \cs{g_primargs_token} (globally) equal to the next
+% token. Finally, calls the \meta{function}.
+% \end{function}
+%
+% \subsection{Grabbing arguments}
+%
+% \begin{function}[tested = primargs002]
+% {
+% \primargs_get_number:N,
+% \primargs_get_dimen:N,
+% \primargs_get_glue:N,
+% \primargs_get_mudimen:N,
+% \primargs_get_muglue:N,
+% }
+% \begin{syntax}
+% \cs{primargs_get_number:N} \meta{function}
+% \end{syntax}
+% Reads a number/dimension/glue/math dimension/math glue following the
+% \meta{function}, then calls the \meta{function} with a braced
+% argument containing the value found. For instance,
+% \begin{verbatim}
+% \primargs_get_glue:N \test 3sp plus \numexpr 2-3 fill X
+% \end{verbatim}
+% yields
+% \begin{verbatim}
+% \test {3sp plus -1fill}X
+% \end{verbatim}
+% A word of warning: the \cs{primargs_get_mudimen:N} function
+% currently parses a \meta{muskip} instead of a \meta{mudimen}.
+% \end{function}
+%
+% \begin{function}[updated = 2014-08-06, tested = primargs003]
+% {\primargs_get_general_text:N}
+% \begin{syntax}
+% \cs{primargs_get_general_text:N} \meta{function}
+% \end{syntax}
+% Finds what \TeX{}'s grammar calls a \meta{general text} (that is, a
+% \meta{filler}, a catcode~$1$ token, a \meta{balanced text}, and an
+% explicit catcode~$2$ token) following the \meta{function}, and calls
+% the \meta{function} with the \meta{balanced text} as a braced
+% argument.
+% \end{function}
+%
+% \begin{function}[updated = 2017-04-10, tested = primargs004]{\primargs_get_file_name:N, \primargs_get_input_file_name:N}
+% \begin{syntax}
+% \cs{primargs_get_file_name:N} \meta{function}
+% \cs{primargs_get_input_file_name:N} \meta{function}
+% \end{syntax}
+% Reads a \meta{file name} following the \meta{function} and calls the
+% \meta{function} with this \meta{file name} as a braced argument.
+% The two functions are identical except in the \LuaTeX{} engine where
+% \cs{primargs_get_input_file_name:N} allows braced file names:
+% \LuaTeX{} allows such braced file names for some primitives (\tn{input}
+% and \tn{openin}) but not others (\tn{openout}).
+% \begin{texnote}
+% When braced file names are disallowed, the file name is obtained
+% by discarding \meta{optional spaces} then repeatedly doing the
+% following. Fully expand what follows in the input stream. If
+% the next token is an explicit or implicit character token
+% (regardless of its catcode) then add that character to the
+% file name and remove it from the input stream, and go back to
+% expanding tokens, except in one case: if the character code
+% is~$32$ (space) and the number of quote characters (code~$34$)
+% already in the file name is even, then the space is removed from
+% the input stream, not included in the file name, and parsing
+% ends. Finally, if the next token is a non-expandable command
+% (be it a control sequence or an active character) then the file
+% name ends and the command is left in the input stream.
+%
+% When braced file names are allowed, the following steps are added
+% prior to the procedure above. First remove a \meta{filler}. If
+% the next token is of catcode~$1$ then fully expand tokens one by
+% one and add their string representation (with \cs{tl_to_str:n},
+% not \cs{token_to_str:N}) to the file name.
+% \end{texnote}
+% \end{function}
+%
+% \subsection{Comments}
+%
+% This package is not idiomatic \pkg{expl3} and should not be used as an
+% example of good coding practices. It uses \cs[no-index]{\ldots{}:D}
+% primitives directly:
+% \begin{itemize}
+% \item to cope with \tn{outer} tokens, since this package is meant to
+% be used quite broadly;
+% \item for primitives with (rightfully) no \pkg{expl3} interface (or a
+% slightly incomplete interface), namely \tn{afterassignment},
+% \tn{globaldefs}, \tn{aftergroup}, \tn{the}, \tn{deadcycles},
+% \tn{hoffset}, \tn{topskip}, \tn{thinmuskip}, \tn{unexpanded};
+% \item to test that a token's meaning is a given primitive when the
+% \pkg{expl3} interface is not (or not obviously) a copy of the primitive.
+% \end{itemize}
+% As a result, \emph{do not take this package as an example of how to
+% code with \pkg{expl3}; go and see Joseph Wright's \pkg{siunitx} for
+% instance.}
+%
+% Despite large efforts expended to make this package robust against
+% changes to the \tn{globaldefs} parameter, setting it to a non-zero
+% value may make some parts of this package crash.
+%
+% Tokens inserted using \tn{afterassignment} may be lost when using this
+% package, since it uses \tn{afterassignment} internally.
+%
+% Todo list.
+% \begin{itemize}
+% \item Test all functions within alignments and understand their
+% interaction with the master counter.
+% \item Correct the parsing of \meta{mudimen}.
+% \item Perhaps parse \meta{muglue} and \meta{glue} by hand to avoid bad
+% interactions with \tn{globaldefs}. Otherwise put up a warning about
+% \tn{globaldefs} when relevant. Better partial fix: declare a skip and a muskip.
+% \item Write tests of engine behaviour, especially \LuaTeX{}'s \tn{input},
+% \tn{openin}, \tn{openout} including behaviour of |#| and spaces and
+% character-code-zero, to detect unexpected changes. In
+% \tn{input}|{|\ldots{}\tn{input}\ldots{}|}|, \LuaTeX{} expands the
+% inner \tn{input} but uses the inner file name as the outer file name.
+% \end{itemize}
+%
+% \end{documentation}
+%
+% \begin{implementation}
+%
+% \section{\pkg{primargs} implementation}
+%
+%<*package>
+% \begin{macrocode}
+\RequirePackage {expl3} [2018/02/21]
+\ProvidesExplPackage
+ {primargs} {2018/12/29} {} {Parsing arguments of primitives}
+% \end{macrocode}
+%
+% \begin{macrocode}
+%<@@=primargs>
+% \end{macrocode}
+%
+% \begin{function}{\@@_get_rhs:NnN, \@@_get_rhs:NoN}
+% \begin{syntax}
+% \cs{@@_get_rhs:NnN} \meta{register} \Arg{register rhs} \meta{function}
+% \end{syntax}
+% Use the \meta{register} to find a right-hand side of a valid
+% assignment for this type of variable, and feed the value found to
+% the \meta{function}. The value of the \meta{register} is then
+% restored using \meta{register} |=| \meta{register rhs}, where the
+% \meta{register rhs} should be the initial value of the
+% \meta{register}. All those assignments are performed within a
+% group, but some are automatically global, and \tn{globaldefs} may
+% cause trouble with others.
+% \end{function}
+%
+% \subsection{Variables and helpers}
+%
+% \begin{macro}{\g_@@_code_tl}
+% Used to contain temporary code.
+% \begin{macrocode}
+\tl_new:N \g_@@_code_tl
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{variable}{\g_@@_file_name_tl, \g_@@_file_name_level_tl}
+% Token list used to build a file name, one character at a time.
+% Token list holding the level of nesting in quotes or braces.
+% \begin{macrocode}
+\tl_new:N \g_@@_file_name_tl
+\tl_new:N \g_@@_file_name_level_tl
+% \end{macrocode}
+% \end{variable}
+%
+% \begin{macro}{\@@_safe:}
+% This function, which must be called in a group, cancels any
+% \tn{afterassignment} token and makes the \tn{globaldefs} parameter
+% non-negative. This ensures that assignments prefixed with
+% \tn{global} are indeed global. When \tn{globaldefs} is positive,
+% every assignment is global, and it is not possible to safely
+% (locally) set it to zero.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_safe:
+ {
+ \tex_afterassignment:D \tex_relax:D
+ \if_int_compare:w 0 > \tex_globaldefs:D
+ \int_zero:N \tex_globaldefs:D
+ \fi:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Read token with or without expansion}
+%
+% \TeX{} often calls the \proc{get_x_token} procedure when parsing
+% various parts of its grammar. This expands tokens recursively until
+% reaching a non-expandable token. We emulate this by reading the next
+% token with \tn{futurelet}, checking whether it is expandable or not by
+% comparing its meaning to its meaning when acted upon by \tn{noexpand},
+% and expanding it with \tn{expandafter} if it is expandable.
+%
+% One thing to be careful about is that
+% \begin{quote}
+% \tn{expandafter} \tn{show} \tn{noexpand} \tn{space}
+% \end{quote}
+% shows the \tn{meaning} of the |\notexpanded: \space|,
+% namely \tn{relax} (frozen, in fact, hence a bit different from the
+% normal \tn{relax}), while expanding twice with
+% \begin{quote}
+% \tn{expandafter} \tn{expandafter} \tn{expandafter} \tn{show}
+% \tn{noexpand} \tn{space}
+% \end{quote}
+% expands the \tn{space} to the underlying space character token.
+% What this means is that we must first check if the token is expandable
+% or not, and only then expand, and that the token should not be queried
+% again using \tn{futurelet}. On this latter point, run
+% \begin{verbatim}
+% \def \test { \show \next \futurelet \next \test }
+% \expandafter \test \noexpand \space
+% \end{verbatim}
+% to see how \tn{next} changes from \tn{relax} to becoming a macro.
+%
+% \begin{macro}{\primargs_read_x_token:N}
+% \begin{macro}
+% {
+% \@@_read_x_token:N, \@@_read_x_token_aux:N,
+% \@@_read_x_token_std:N, \@@_read_x_token_file:N
+% }
+% This is a bit messy, because we need to support the fact that
+% \TeX{} does not consider \tn{input} as expandable when it is
+% looking for a file name. This variation is encapsulated by
+% letting \cs{@@_read_x_token_aux:N} equal to either a standard
+% (\texttt{std}) version or a version specific to file names
+% (\texttt{file}).
+%
+% First query the following token. Then test whether it is
+% expandable, using a variant of the \cs{token_if_expandable:NTF}
+% test.\footnote{This \LaTeX3 test returns \texttt{false} for
+% undefined tokens (by design), but \TeX{}'s \proc{get_x_token}
+% expands those undefined tokens, causing errors, so we should as
+% well.} If the token is expandable, \cs{exp_not:N} will change its
+% \tn{meaning} to \tn{relax}, the test is \texttt{false}, we expand,
+% and call the loop. Otherwise, we stop. In the \texttt{file}
+% version there is an extra test for \cs{tex_input:D}. By default
+% use the standard version.
+% \begin{macrocode}
+\cs_new_protected:Npn \primargs_read_x_token:N
+ {
+ \group_begin:
+ \@@_safe:
+ \@@_read_x_token:N
+ }
+\cs_new_protected:Npn \@@_read_x_token:N
+ {
+ \tex_afterassignment:D \@@_read_x_token_aux:N
+ \tex_global:D \tex_futurelet:D \g_primargs_token
+ }
+\cs_new_protected:Npn \@@_read_x_token_std:N
+ {
+ \exp_after:wN
+ \if_meaning:w \exp_not:N \g_primargs_token \g_primargs_token
+ \group_end: \use_i:nnnn
+ \fi:
+ \exp_after:wN \@@_read_x_token:N \exp_after:wN
+ }
+\cs_new_eq:NN \@@_read_x_token_aux:N
+ \@@_read_x_token_std:N
+\cs_new_protected:Npn \@@_read_x_token_file:N
+ {
+ \if_meaning:w \tex_input:D \g_primargs_token
+ \use_i_ii:nnn \group_end:
+ \fi:
+ \@@_read_x_token_std:N
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\primargs_read_token:N}
+% The same without expansion, useful for instance when we already know
+% that what follows is expanded. Interestingly, we don't ever need to
+% take the user's function as an argument.
+% \begin{macrocode}
+\cs_new_protected:Npn \primargs_read_token:N
+ {
+ \group_begin:
+ \@@_safe:
+ \tex_afterassignment:D \group_end:
+ \tex_global:D \tex_futurelet:D \g_primargs_token
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \subsection{Removing tokens}
+%
+% \begin{macro}{\primargs_remove_token:N}
+% Remove token using \tn{let} (note the presence of |=| and a space,
+% to correctly remove explicit space characters), then insert the
+% \meta{function} after closing the group.
+% \begin{macrocode}
+\cs_new_protected:Npn \primargs_remove_token:N #1
+ {
+ \group_begin:
+ \@@_safe:
+ \tex_aftergroup:D #1
+ \tex_afterassignment:D \group_end:
+ \tex_global:D \tex_let:D \g_primargs_token = ~
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\primargs_remove_one_optional_space:N}
+% \begin{macro}{\@@_remove_one_optional_space:}
+% Start a group: we will insert the \meta{function} at its end.
+% \begin{macrocode}
+\cs_new_protected:Npn \primargs_remove_one_optional_space:N #1
+ {
+ \group_begin:
+ \@@_safe:
+ \tex_aftergroup:D #1
+ \primargs_read_x_token:N \@@_remove_one_optional_space:
+ }
+\cs_new_protected:Npn \@@_remove_one_optional_space:
+ {
+ \if_catcode:w \c_space_token \exp_not:N \g_primargs_token
+ \exp_after:wN \primargs_remove_token:N
+ \fi:
+ \group_end:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\primargs_remove_optional_spaces:N}
+% \begin{macro}
+% {\@@_remove_optional_spaces:, \@@_remove_optional_spaces_aux:}
+% Start a group, make assignments safe, then recursively expand tokens
+% and remove any token with catcode~$10$ (space). Once another token
+% is found, close the group hence insert the \meta{function}~|#1|.
+% \begin{macrocode}
+\cs_new_protected:Npn \primargs_remove_optional_spaces:N #1
+ {
+ \group_begin:
+ \@@_safe:
+ \tex_aftergroup:D #1
+ \@@_remove_optional_spaces:
+ }
+\cs_new_protected:Npn \@@_remove_optional_spaces:
+ { \primargs_read_x_token:N \@@_remove_optional_spaces_aux: }
+\cs_new_protected:Npn \@@_remove_optional_spaces_aux:
+ {
+ \if_catcode:w \c_space_token \exp_not:N \g_primargs_token
+ \exp_after:wN \primargs_remove_token:N
+ \exp_after:wN \@@_remove_optional_spaces:
+ \else:
+ \exp_after:wN \group_end:
+ \fi:
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\primargs_remove_equals:N}
+% \begin{macro}{\@@_remove_equals:, \@@_remove_equals_aux:NN}
+% Remove \meta{optional spaces}, then test for an explicit~|=|, both
+% in \tn{meaning} and as a token list: once we know its \tn{meaning},
+% we can grab it safely.
+% \begin{macrocode}
+\cs_new_protected:Npn \primargs_remove_equals:N #1
+ {
+ \group_begin:
+ \tex_aftergroup:D #1
+ \primargs_remove_optional_spaces:N \@@_remove_equals:
+ }
+\cs_new_protected:Npn \@@_remove_equals:
+ {
+ \if_meaning:w = \g_primargs_token
+ \exp_after:wN \@@_remove_equals_aux:NN
+ \fi:
+ \group_end:
+ }
+\cs_new_protected:Npn \@@_remove_equals_aux:NN #1#2
+ { \tl_if_eq:nnTF { #2 } { = } { #1 } { #1 #2 } }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\primargs_remove_filler:N}
+% \begin{macro}
+% {
+% \@@_remove_filler:,
+% \@@_remove_filler_aux:,
+% \@@_remove_filler_end:NNNNN
+% }
+% Within a group remove a \meta{filler}, and insert the user's |#1|
+% after closing the group. A \meta{filler} consists of tokens with
+% catcode~$10$ (space) or equal to \tn{relax} or to the
+% \enquote{frozen \tn{relax}} command.
+% \begin{macrocode}
+\cs_new_protected:Npn \primargs_remove_filler:N #1
+ {
+ \group_begin:
+ \@@_safe:
+ \tex_aftergroup:D #1
+ \@@_remove_filler:
+ }
+\cs_new_protected:Npn \@@_remove_filler:
+ { \primargs_read_x_token:N \@@_remove_filler_aux: }
+\cs_new_protected:Npn \@@_remove_filler_aux:
+ {
+ \if_catcode:w \c_space_token \exp_not:N \g_primargs_token
+ \else:
+ \if_meaning:w \tex_relax:D \g_primargs_token
+ \else:
+ \exp_after:wN
+ \if_meaning:w \exp_not:N \prg_do_nothing: \g_primargs_token
+ \else:
+ \@@_remove_filler_end:NNNNN
+ \fi:
+ \fi:
+ \fi:
+ \primargs_remove_token:N \@@_remove_filler:
+ }
+\cs_new_protected:Npn \@@_remove_filler_end:NNNNN #1#2#3#4#5
+ { #1 #2 #3 \group_end: }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Right-hand sides of assignments}
+%
+% The naive approach to reading an integer, or a general text, is to let
+% \TeX{} perform an assignment to a \tn{count}, or a \tn{toks}, register
+% and regain control using \tn{afterassignment}. The question is then
+% to know which \tn{count} or \tn{toks} register to use. One might
+% think that any can be used as long as the assignment happens in a
+% group.
+%
+% However, there comes the question of the \tn{globaldefs} parameter.
+% If this parameter is positive, every assignment is global, including
+% assignments to the parameter itself, preventing us from setting it to
+% zero locally; hence, we are stuck with global assignments (if
+% \tn{globaldefs} is negative, we can change it, locally, to whatever
+% value pleases us, as done by \cs{@@_safe:}). We may thus not
+% use scratch registers to parse integers, general texts, and other
+% pieces of \TeX{}'s grammar.
+%
+% For integers, we will use \tn{deadcycles}, a parameter which is
+% automatically assigned globally, and we revert it to its previous
+% value afterwards.
+%
+% \begin{macro}{\@@_get_rhs:NnN, \@@_get_rhs:NoN}
+% The last two lines of this function are the key: assign to |#1|,
+% then take control using \tn{afterassignment}. After the assignment,
+% we expand the value found, |\tex_the:D #1|, within a brace group,
+% then restore |#1| using its initial value |#2|, and end the group.
+% The earlier use of \cs{tex_aftergroup:D} inserts the \meta{function}
+% |#3| before the brace group containing the value found.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_get_rhs:NnN #1#2#3
+ {
+ \group_begin:
+ \@@_safe:
+ \tex_aftergroup:D #3
+ \tl_gset:Nn \g_@@_code_tl
+ {
+ \use:x
+ {
+ \exp_not:n { #1 = #2 \group_end: }
+ { \tex_the:D #1 }
+ }
+ }
+ \tex_afterassignment:D \g_@@_code_tl
+ #1 =
+ }
+\cs_generate_variant:Nn \@@_get_rhs:NnN { No }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\primargs_get_number:N}
+% We use the general \cs{@@_get_rhs:NoN}, using the internal register
+% \tn{deadcycles}, for which all assignments are global: thus,
+% restoring its value will not interact badly with groups.
+% \begin{macrocode}
+\cs_new_protected:Npn \primargs_get_number:N
+ {
+ \@@_get_rhs:NoN \tex_deadcycles:D
+ { \tex_the:D \tex_deadcycles:D }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\primargs_get_dimen:N}
+% Use \tn{hoffset} as a register since it is not too likely to be
+% changed locally (anyways, which register we use is not that
+% important since normally, \tn{globaldefs} is zero, and everything is
+% done within a group).
+% \begin{macrocode}
+\cs_new_protected:Npn \primargs_get_dimen:N
+ {
+ \@@_get_rhs:NoN \tex_hoffset:D
+ { \tex_the:D \tex_hoffset:D }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\primargs_get_glue:N}
+% Use \tn{topskip}.
+% \begin{macrocode}
+\cs_new_protected:Npn \primargs_get_glue:N
+ {
+ \@@_get_rhs:NoN \tex_topskip:D
+ { \tex_the:D \tex_topskip:D }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\primargs_get_mudimen:N}
+% There is no such thing as a \meta{mudimen variable}, so we're on our
+% own to parse a \meta{mudimen}. Warn about that problem, and parse a
+% \meta{muglue} instead.
+% \begin{macrocode}
+\cs_new_protected:Npn \primargs_get_mudimen:N
+ {
+ \msg_warning:nn { primargs } { get-mudimen }
+ \primargs_get_muglue:N
+ }
+\msg_new:nnn { primargs } { get-mudimen }
+ { The~\iow_char:N\\primargs_get_mudimen:N~function~is~buggy. }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\primargs_get_muglue:N}
+% Use \tn{thinmuskip}.
+% \begin{macrocode}
+\cs_new_protected:Npn \primargs_get_muglue:N
+ {
+ \@@_get_rhs:NoN \tex_thinmuskip:D
+ { \tex_the:D \tex_thinmuskip:D }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\primargs_get_general_text:N}
+% \begin{macro}
+% {\@@_get_general_text:, \@@_get_general_text_error:n}
+% Getting a \meta{general text} is more tricky, as an assignment to
+% \tn{errhelp} (for instance) would also allow constructions such as
+% |\toks0|. Instead, we remove a \meta{filler} then test whether the
+% next token (already expanded) is a catcode~$1$ token, in which case
+% we replace it by an explicit left brace before calling the function.
+% When the next token is not of catcode~$1$, we produce an error,
+% attempting to imitate as closely as possible the \TeX{} error.
+% \begin{macrocode}
+\cs_new_protected:Npn \primargs_get_general_text:N #1
+ {
+ \group_begin:
+ \@@_safe:
+ \tex_aftergroup:D #1
+ \tex_aftergroup:D { \if_false: } \fi:
+ \primargs_remove_filler:N \@@_get_general_text:
+ }
+\cs_new_protected:Npn \@@_get_general_text:
+ {
+ \if_catcode:w \c_group_begin_token \g_primargs_token
+ \exp_after:wN \primargs_remove_token:N
+ \else:
+ \group_begin:
+ \tex_aftergroup:D \@@_get_general_text_error:n
+ \if_catcode:w \c_group_end_token \g_primargs_token
+ \tex_aftergroup:D {
+ \tex_aftergroup:D }
+ \fi:
+ \fi:
+ \group_end:
+ }
+\cs_new_protected:Npn \@@_get_general_text_error:n #1
+ {
+ \exp_after:wN \group_end:
+ \tex_unexpanded:D \if_int_compare:w `{ = \c_zero_int \fi: #1 }
+ }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \subsection{Get file name}
+%
+% \begin{macro}{\primargs_get_file_name:N}
+% Empty the file name (globally), and build it one character at a time.
+% The \meta{function} is added at the end of a group, started here.
+% As described in the \TeX{}book, a \meta{file name} should start with
+% \meta{optional spaces} (\LuaTeX{} changes that to \meta{filler}),
+% which we remove, then character tokens,
+% ending with a non-expandable character or control sequence. After
+% space removal, \cs{g_primargs_token} contains the next token, so no
+% need for \cs{primargs_read_token:N}.
+% When \TeX{} reads a file name, the \tn{input} primitive is
+% temporarily not expandable, so we temporarily change
+% \cs{primargs_read_x_token:N} to not expand this primitive. This is
+% reverted by \cs{@@_get_file_name_end:}.
+% \begin{macrocode}
+\cs_new_protected:Npn \primargs_get_file_name:N #1
+ {
+ \group_begin:
+ \@@_safe:
+ \cs_gset_eq:NN \@@_read_x_token_aux:N
+ \@@_read_x_token_file:N
+ \tex_aftergroup:D #1
+ \tl_gclear:N \g_@@_file_name_tl
+ \tl_gset:Nn \g_@@_file_name_level_tl { 0 }
+ \primargs_remove_optional_spaces:N \@@_get_file_name_test:
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_get_file_name_test:}
+% The token read is in \cs{g_primargs_token}, and is non-expandable.
+% If it is a control sequence, end the \meta{file name}. Spaces are
+% special (quotes too, but that is treated elsewhere). Otherwise,
+% we extract the character from the \tn{meaning} of the \meta{token},
+% which we remove anyways: in that case, we'll recurse.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_get_file_name_test:
+ {
+ \token_if_cs:NTF \g_primargs_token
+ { \@@_get_file_name_end: }
+ {
+ \token_if_eq_charcode:NNTF \c_space_token \g_primargs_token
+ { \primargs_remove_token:N \@@_get_file_name_space: }
+ { \primargs_remove_token:N \@@_get_file_name_char: }
+ }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_get_file_name_end:}
+% When the end of the file name is reached, reinstate the original
+% definition of |read_x_token| so as to make \tn{input} expandable
+% again, then end the group, after
+% expanding the contents of \cs{g_@@_file_name_tl}.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_get_file_name_end:
+ {
+ \cs_gset_eq:NN \@@_read_x_token_aux:N
+ \@@_read_x_token_std:N
+ \exp_args:No \group_end: \g_@@_file_name_tl
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_get_file_name_space:}
+% We have already removed the space from the input stream. If
+% there is an odd number of quotes so far, add a space to the
+% file name and continue. Otherwise the file name ends.
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_get_file_name_space:
+ {
+ \int_if_odd:nTF { \g_@@_file_name_level_tl }
+ {
+ \tl_gput_right:Nn \g_@@_file_name_tl { ~ }
+ \primargs_read_x_token:N \@@_get_file_name_test:
+ }
+ { \@@_get_file_name_end: }
+ }
+% \end{macrocode}
+% \end{macro}
+%
+% \begin{macro}{\@@_get_file_name_char:}
+% \begin{macro}[EXP]
+% {\@@_get_file_name_char_ii:w, \@@_get_file_name_char_iii:w}
+% Check for a quote, which switches \cs{g_@@_file_name_level_tl}
+% from $0$ to $1$ or back.
+% With an explicit character, applying \tn{string} would give the
+% character code. Here, implicit characters have to be converted too,
+% so we must work with the \tn{meaning}, which is two or three words
+% separated by spaces, then the character. The \texttt{ii} auxiliary
+% removes the first two words, and duplicates the remainder (either
+% one character, or a word and a character), and the second auxiliary
+% leaves the second piece in the definition (in both cases, the
+% character). Then loop with expansion. This technique would fail if
+% the character could be a space (character code~$32$).
+% \begin{macrocode}
+\cs_new_protected:Npn \@@_get_file_name_char:
+ {
+ \token_if_eq_charcode:NNT " \g_primargs_token
+ {
+ \tl_gset:Nx \g_@@_file_name_level_tl
+ { \int_eval:n { 1 - \g_@@_file_name_level_tl } }
+ }
+ \tl_gput_right:Nx \g_@@_file_name_tl
+ {
+ \exp_after:wN \@@_get_file_name_char_ii:w
+ \token_to_meaning:N \g_primargs_token
+ \q_stop
+ }
+ \primargs_read_x_token:N \@@_get_file_name_test:
+ }
+\cs_new:Npn \@@_get_file_name_char_ii:w #1 ~ #2 ~ #3 \q_stop
+ { \@@_get_file_name_char_iii:w #3 ~ #3 ~ \q_stop }
+\cs_new:Npn \@@_get_file_name_char_iii:w #1 ~ #2 ~ #3 \q_stop {#2}
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+% \begin{macro}{\primargs_get_input_file_name:N}
+% \begin{macro}
+% {
+% \@@_get_input_file_name_first:,
+% \@@_get_input_file_name_loop:, \@@_get_input_file_name_test:,
+% \@@_get_input_file_name_brace:, \@@_get_input_file_name_aux:N
+% }
+% For most engines this is an alias of \cs{primargs_get_file_name:N}.
+% In \LuaTeX{} we test for a catcode $1$ token (after a filler) then
+% expand and collect tokens (turned to strings) one by one, counting
+% begin-group and end-group tokens in \cs{g_@@_file_name_level_tl}.
+% The control sequence \tn{par} is ignored. After removing a filler
+% or after expansion, \cs{g_primargs_token} cannot be \tn{outer} hence
+% the tests are safe. We use primitives to cope with outer macro
+% hidden by \tn{noexpand} upon first expansion.
+% \begin{macrocode}
+\sys_if_engine_luatex:TF
+ {
+ \cs_new_protected:Npn \primargs_get_input_file_name:N #1
+ {
+ \group_begin:
+ \@@_safe:
+ \tex_aftergroup:D #1
+ \tl_gclear:N \g_@@_file_name_tl
+ \tl_gset:Nn \g_@@_file_name_level_tl { 1 }
+ \primargs_remove_filler:N \@@_get_input_file_name_first:
+ }
+ \cs_new_protected:Npn \@@_get_input_file_name_first:
+ {
+ \token_if_eq_catcode:NNTF \g_primargs_token \c_group_begin_token
+ { \primargs_remove_token:N \@@_get_input_file_name_loop: }
+ { \primargs_get_file_name:N \group_end: }
+ }
+ \cs_new_protected:Npn \@@_get_input_file_name_loop:
+ { \primargs_read_x_token:N \@@_get_input_file_name_test: }
+ \cs_new_protected:Npn \@@_get_input_file_name_test:
+ {
+ \token_if_eq_catcode:NNTF \g_primargs_token \c_group_begin_token
+ {
+ \tl_gset:Nx \g_@@_file_name_level_tl
+ { \int_eval:n { \g_@@_file_name_level_tl + 1 } }
+ \primargs_remove_token:N \@@_get_input_file_name_brace:
+ }
+ {
+ \token_if_eq_catcode:NNTF \g_primargs_token \c_group_end_token
+ {
+ \tl_gset:Nx \g_@@_file_name_level_tl
+ { \int_eval:n { \g_@@_file_name_level_tl - 1 } }
+ \int_compare:nNnTF { \g_@@_file_name_level_tl } > 0
+ { \primargs_remove_token:N \@@_get_input_file_name_brace: }
+ { \primargs_remove_token:N \@@_get_file_name_end: }
+ }
+ {
+ \token_if_eq_meaning:NNTF \g_primargs_token \c_space_token
+ {
+ \tl_gput_right:Nn \g_@@_file_name_tl { ~ }
+ \primargs_remove_token:N \@@_get_input_file_name_loop:
+ }
+ { \exp_after:wN \@@_get_input_file_name_aux:N \exp_not:N }
+ }
+ }
+ }
+ \cs_new_protected:Npn \@@_get_input_file_name_brace:
+ {
+ \tl_gput_right:Nx \g_@@_file_name_tl
+ {
+ \exp_after:wN \@@_get_file_name_char_ii:w
+ \token_to_meaning:N \g_primargs_token
+ \q_stop
+ }
+ \@@_get_input_file_name_loop:
+ }
+ \cs_new_protected:Npn \@@_get_input_file_name_aux:N #1
+ {
+ \exp_after:wN \str_if_eq:eeT
+ \exp_after:wN { \token_to_str:N #1 } { \token_to_str:N \par }
+ { \use_none:nnn }
+ \tex_xdef:D \g_@@_file_name_tl
+ {
+ \g_@@_file_name_tl
+ \exp_after:wN \tl_to_str:n \exp_after:wN { \exp_not:N #1 }
+ }
+ \@@_get_input_file_name_loop:
+ }
+ }
+ { \cs_new_eq:NN \primargs_get_input_file_name:N \primargs_get_file_name:N }
+% \end{macrocode}
+% \end{macro}
+% \end{macro}
+%
+%</package>
+%
+% \end{implementation}
+%
+% \PrintIndex
diff --git a/macros/latex/contrib/morewrites/primargs.pdf b/macros/latex/contrib/morewrites/primargs.pdf
new file mode 100644
index 0000000000..d63d7f97a2
--- /dev/null
+++ b/macros/latex/contrib/morewrites/primargs.pdf
Binary files differ