% \iffalse meta-comment % %% File: morewrites.dtx Copyright (C) 2011-2024 Bruno Le Floch %% %% It may be distributed and/or modified under the conditions of the %% LaTeX Project Public License (LPPL), either version 1.3c of this %% license or (at your option) any later version. The latest version %% of this license is in the file %% %% http://www.latex-project.org/lppl.txt %% %% This work has the LPPL maintenance status 'maintained' %% and the current maintainer is Bruno Le Floch. %% ----------------------------------------------------------------------- % %<*driver> \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{2024/02/02} % % \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. The package only overwrites this % auxiliary file if it is empty, and otherwise uses a modified % file name, obtained by adding an integer to the name (previously % it was obtained by appending additional copies of |.mw| to the % name). Such files can be safely deleted. Be careful though to % not delete a Maple worksheet by accident when cleaning up your % files. % \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 = 2024-01-05]{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}[added = 2024-01-05]{verbose} % \begin{syntax} % \cs{morewritessetup} |{| |verbose| |}| % \end{syntax} % This boolean option (\texttt{false} by default) makes the package % write to the terminal all of the operations that it performs. This % can render \pkg{morewrites} useful for debugging some file-writing % operations. % \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 % $129$. 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 {primargs} [2024/01/05] \ProvidesExplPackage {morewrites} {2024/02/02} {} {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[129,\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 $[129,\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 stream number $128$ is also often used like $16$ to avoid % distinguishing \LuaTeX{}. Rather than special-casing it we skip % directly to larger stream numbers. % % 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} % % 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} % % \subsubsection{Variables} % % \begin{variable}{\l_@@_internal_tl, \l_@@_internal_seq} % Used for temporary scratch purposes. % \begin{macrocode} \tl_new:N \l_@@_internal_tl \seq_new:N \l_@@_internal_seq % \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}{\l_@@_verbose_bool} % When this boolean is set true, \pkg{morewrites} will print to the terminal all of the operations that it does. % \begin{macrocode} \bool_new:N \l_@@_verbose_bool % \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{Verbosity} % % \begin{macro}{\@@_verbose:n} % Messages to put in the terminal if the \texttt{verbose} option is % selected. % \begin{macrocode} \cs_new_protected:Npn \@@_verbose:n #1 { \bool_if:NT \l_@@_verbose_bool { \iow_term:e { morewrites:~#1 } } } % \end{macrocode} % \end{macro} % % \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|). We do not yet expand, delaying that % to the time where we start opening/closing the file, in case |#1| % contains something that has not yet been fixed. 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 a file 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} \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:} % Expand the file name in \cs{g_@@_tmp_file_tl} once and for all. % Check that the file does not exist or is % blank. If not, try another file name obtained as follows: if it % ends with |.mw| pick up any number that lies just before |.mw| and % increment it, and otherwise just add |.mw| at the end of the file. % This avoids clobbering files that the user would not want to lose. % \begin{macrocode} \cs_new_protected:Npn \@@_chk_file: { \tl_gset:Ne \g_@@_tmp_file_tl { \tl_to_str:e { \g_@@_tmp_file_tl } } \@@_if_file_trivial:nTF { \g_@@_tmp_file_tl } { \bool_gset_true:N \g_@@_tmp_file_bool } { \@@_chk_file_aux: \msg_warning:nnxx { morewrites } { file-exists } { \g_@@_tmp_file_tl } { \l_@@_internal_tl } \tl_gset_eq:NN \g_@@_tmp_file_tl \l_@@_internal_tl \@@_chk_file: } } \cs_new_protected:Npn \@@_chk_file_aux: { \regex_extract_once:nVNTF { \A (\D*) (\d*) .mw \Z } \g_@@_tmp_file_tl \l_@@_internal_seq { \tl_set:Ne \l_@@_internal_tl { \seq_item:Nn \l_@@_internal_seq { 2 } \int_eval:n { \seq_item:Nn \l_@@_internal_seq { 3 } + 1 } .mw } } { \tl_set:Ne \l_@@_internal_tl { \g_@@_tmp_file_tl .mw } } } \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_input_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 $129$ are not managed by \pkg{morewrites}: actual \TeX{} % streams in $[0,15]$; negative for writing to \texttt{log}; $16$, % $17$, $128$ 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 } < { 129 } { \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. In contrived % situations involving nonsensical uses of \tn{noexpand} after % \tn{immediate}, this code does not perfectly match how \TeX{} % expands. % \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 } { \@@_verbose:n { \tl_to_str:n { \immediate } \token_to_meaning:N \g_primargs_token } \@@_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. In this subsubsection we implement the immediate versions % only. % % \begin{macro}{\@@_closeout:w, \@@_closeout_now:, \@@_closeout_now_silent:, \@@_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: { \@@_verbose:n { \tl_to_str:n { \immediate \closeout } \int_use:N \l_@@_user_int } \@@_closeout_now_silent: } \cs_new_protected:Npn \@@_closeout_now_silent: { \@@_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} \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, \@@_openout_now_silent: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 { \@@_verbose:n { \tl_to_str:n { \immediate\openout } \int_use:N \l_@@_user_int \c_space_tl = ~ {#1} } \@@_openout_now_silent:n {#1} } \cs_new_protected:Npn \@@_openout_now_silent:n #1 { \@@_closeout_now_silent: \int_compare:nNnTF { \l_@@_user_int } < { 129 } { \@@_tex_immediate:w \@@_tex_openout:w \l_@@_user_int = { \tl_to_str:n {#1} } } { \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} } } { \@@_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 } } } } } \sys_if_engine_xetex:T { \cs_new_eq:NN \@@_openout_now_silent_aux:n \@@_openout_now_silent:n \cs_gset_protected:Npn \@@_openout_now_silent:n #1 { \tl_set:Nn \l_@@_internal_tl {#1} \tl_remove_all:Nn \l_@@_internal_tl { " } % { " } \exp_args:No \@@_openout_now_silent_aux:n \l_@@_internal_tl } } % \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 { \int_compare:nNnT \l_@@_user_int = { 18 } { \use_iii:nnn } \int_compare:nT { -1 < \l_@@_user_int < 16 } { \@@_verbose:n { \tl_to_str:n { \immediate \write } \int_use:N \l_@@_user_int } } \@@_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 #1 { \prop_get:NVNTF \g_@@_write_file_prop \l_@@_user_int \l_@@_internal_tl { \@@_verbose:n { \tl_to_str:n { \immediate \write } \int_use:N \l_@@_user_int \tl_to_str:n { ~ {#1} } } \@@_write_now_open:n {#1} } { \@@_verbose:n { \tl_to_str:n { \immediate \write } \int_use:N \l_@@_user_int \tl_to_str:n { ~ (closed) ~ {#1} } } \@@_tex_immediate:w \@@_tex_write:w 16 {#1} } } % \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 } \@@_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 } \@@_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_silent:} for later. % \begin{macrocode} \cs_new_protected:Npn \@@_closeout_later: { \@@_verbose:n { \tl_to_str:n { \closeout (later) ~ } \int_use:N \l_@@_user_int } \int_compare:nNnTF \l_@@_user_int < { 129 } { \@@_tex_closeout:w \l_@@_user_int } { \@@_later:n { \@@_closeout_now_silent: } } } % \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_silent:n} later. % \begin{macrocode} \cs_new_protected:Npn \@@_openout_later:w { \int_compare:nNnTF \l_@@_user_int < { 129 } { \@@_verbose:n { \tl_to_str:n { \openout (later) ~ } \int_use:N \l_@@_user_int } \@@_tex_openout:w \l_@@_user_int } { \@@_equals_file:N \@@_openout_later:n } } \cs_new_protected:Npn \@@_openout_later:n #1 { \@@_verbose:n { \tl_to_str:n { \openout (later)~ } \int_use:N \l_@@_user_int \c_space_tl = ~ {#1} } \@@_later:n { \@@_openout_now_silent: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 < { 129 } { \@@_verbose:n { \tl_to_str:n { \write (later)~ } \int_use:N \l_@@_user_int } \@@_tex_write:w \l_@@_user_int } { \primargs_get_general_text:N \@@_write_later:n } } \cs_new_protected:Npn \@@_write_later:n #1 { \@@_verbose:n { \tl_to_str:n { \write (later)~ } \int_use:N \l_@@_user_int \tl_to_str: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: } { \prop_get:NVNTF \g_@@_write_file_prop \l_@@_user_int \l_@@_internal_tl { \@@_write_now_open:n } { \@@_tex_immediate:w \@@_tex_write:w 16 \exp_stop_f: } } } % \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 } } % \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 } } \@@_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 { \@@_verbose:n { \tl_to_str:n { \immediate \closeout } ##1 ~ (at~end) } \@@_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. We used to initialize it to $18$ % so that the first user stream allocated by \pkg{morewrites} was % $19$. Indeed, $18$ is reserved for shell commands and packages may % expect $16$ or $17$ to write to the terminal. This is now changed % to start allocation at $129$, since some packages that do not want % to distinguish \LuaTeX{} from other engines simply use $128$ as a % never-open stream. % \begin{macrocode} \int_new:N \g_@@_alloc_write_int \int_gset:Nn \g_@@_alloc_write_int { 128 } % \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 , verbose .bool_set:N = \l_@@_verbose_bool } % \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