From e0c6872cf40896c7be36b11dcc744620f10adf1d Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Mon, 2 Sep 2019 13:46:59 +0900 Subject: Initial commit --- macros/latex/contrib/morewrites/README.md | 16 + macros/latex/contrib/morewrites/morewrites.dtx | 1289 ++++++++++++++++++++++++ macros/latex/contrib/morewrites/morewrites.ins | 39 + macros/latex/contrib/morewrites/morewrites.pdf | Bin 0 -> 715831 bytes macros/latex/contrib/morewrites/primargs.dtx | 918 +++++++++++++++++ macros/latex/contrib/morewrites/primargs.pdf | Bin 0 -> 601061 bytes 6 files changed, 2262 insertions(+) create mode 100644 macros/latex/contrib/morewrites/README.md create mode 100644 macros/latex/contrib/morewrites/morewrites.dtx create mode 100644 macros/latex/contrib/morewrites/morewrites.ins create mode 100644 macros/latex/contrib/morewrites/morewrites.pdf create mode 100644 macros/latex/contrib/morewrites/primargs.dtx create mode 100644 macros/latex/contrib/morewrites/primargs.pdf (limited to 'macros/latex/contrib/morewrites') 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} +% +% \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} +% +% +% +% \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 Binary files /dev/null and b/macros/latex/contrib/morewrites/morewrites.pdf 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} +% +% \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} +% +% +% +% \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 Binary files /dev/null and b/macros/latex/contrib/morewrites/primargs.pdf differ -- cgit v1.2.3