% \iffalse meta-comment % %% File: morewrites.dtx Copyright (C) 2011-2013 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 %% %% ----------------------------------------------------------------------- % %<*driver> \RequirePackage{morewrites} \documentclass[full]{l3doc} \newwrite\foo \newwrite\foo \newwrite\foo \newwrite\foo \newwrite\foo \newwrite\foo \newwrite\foo \newwrite\foo \newwrite\foo \newwrite\foo \newwrite\foo \newwrite\foo \newcommand{\proc}[1]{\texttt{#1}} \begin{document} \DocInput{\jobname.dtx} \end{document} % % \fi % % \title{The \textsf{morewrites} package: \\ % Always room for a new \cs{write}\thanks{This % file has version number v0.2e, last revised 2013/01/08.}} % \author{Bruno Le Floch} % \date{2013/01/08} % % \maketitle % \tableofcontents % % \begin{documentation} % % \section{\pkg{morewrites} documentation} % % This \LaTeX{} package is meant to be a solution for the error % \enquote{no room for a new \cs{write}}, which occurs when too many % macro packages reserve streams to write data to various auxiliary % files. It is in principle possible to rewrite 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). If I did my job correctly, you simply need to add the % line |\usepackage{morewrites}| somewhere near the beginning of your % \LaTeX{} file, and the \enquote{no room for a new \cs{write}} error % should vanish. % % I have tried to make the code as robust as possible, but there may % still be bugs lurking as this package has not been tested very % thoroughly yet. I thus encourage you to check that references are % correct after loading that package: if they are correct without % \pkg{morewrites}, but wrong with, please send me a minimal file % showing the problem, or post a question on the % \url{tex.stackexchange.com} question and answers website, or the % \url{comp.text.tex} newsgroup. % % This package loads the \pkg{expl3} package, hence the \pkg{l3kernel} % bundle needs to be up to date. If Heiko Oberdiek's package % \pkg{atbegshi} is available, it will be used. % % \section{Known deficiencies and open questions} % % Some distributions of \TeX{} allow a quoted syntax for file names with % spaces. I haven't yet coded that. A temporary fix is to avoid file % names with spaces. % % The current code trims spaces at the end of every line that is written % to a file. I might be able to change the code to avoid this. % % The package code is not very legible, and definitely uses too many % |:D| control sequences, whose name means \enquote{do not use}. The % author does not see a way to avoid using primitives in this package, % since hooking into the primitives \tn{immediate}, \tn{write}, % \emph{etc.} requires having a very strong control on what every % command does. \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.} % % Things that I need to do. % \begin{itemize} % \item Redefine the \LaTeX3 functions to use \pkg{morewrites} too? % \item Should \tn{newwrite} be protected? % \end{itemize} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{morewrites} implementation} % %<*package> % \begin{macrocode} \RequirePackage {expl3} [2012/08/14] \RequirePackage {primargs} [2013/01/08] \ProvidesExplPackage {morewrites} {2013/01/08} {0.2e} {Always room for a new write} % \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. We must thus patch $4$ primitives, \tn{openout}, \tn{write}, % \tn{closeout} and \tn{immediate}, and the \cs{newwrite} macro, defined % by \LaTeX{} (and plain \TeX{}). Each of those commands must be made % to accept numbers outside the range $[0,15]$. Let us review the % syntax of the various commands we need to alter (see Chapter~24 of the % \TeX{}book). % % We start with the three \enquote{actions}. % \begin{syntax} % \tn{openout} \meta{integer} \meta{equals} \meta{file name} % \end{syntax} % \TeX{} searches the path for a file with a name given by % \meta{file name}. If found, this file is opened in the writing stream % \meta{integer}, which must be a number in the range $[0,15]$. % \begin{syntax} % \tn{write} \meta{integer} \meta{filler} \meta{general text} % \end{syntax} % \TeX{} expands the \meta{general text} as for an \texttt{x}-type % expansion, with the caveat that macro parameter characters do not need % to be doubled; converts the result to a string, and writes it in the % writing stream \meta{integer}. If the writing stream \meta{integer} % is open (in particular it must be in the range $[0,15]$), then this % writes to the corresponding file. Otherwise, if the \meta{integer} is % negative, the text is written to the log file, and a non-negative % \meta{integer} writes to the terminal. One exception: if the % \meta{integer} is $18$, the text is sent to a shell to be run as shell % code. % \begin{syntax} % \tn{closeout} \meta{integer} % \end{syntax} % If the writing stream \meta{integer} is open, it is closed. % Otherwise, if the \meta{integer} is not in the range $[0,15]$ an error % may be raised, or nothing happens. % % By default, each one of those three \enquote{actions} 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 the three \enquote{actions} 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 such as % \tn{box}\meta{integer}, or \tn{hbox}\Arg{material to typeset}. % % Finally, the \cs{newwrite} macro expects one token as its argument, % and defines this token (with \tn{chardef}) to be an integer % corresponding to the first available writing stream. We must extend % it to let it allocate higher (virtual) write registers. % % All of the primitives above perform full expansion of all tokens when % looking for their operands. In most cases, only the \tn{meaning} of % tokens encountered in this way matters. Specifically, % \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 a character with category code $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} % % \subsection{Variants} % % \begin{macro}[aux]{\prop_gpop:NVNT} % We need this function later. % \begin{macrocode} \cs_generate_variant:Nn \prop_gpop:NnNT { NV } % \end{macrocode} % \end{macro} % % \subsection{Renaming primitives (again)} % % \begin{macro}[int] % { % \@@_tex_immediate:w , % \@@_tex_openout:w , % \@@_tex_write:w , % \@@_tex_closeout:w % } % First save the output-related primitives. % \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}[int]{\@@_tex_shipout:w} % Since the non-\tn{immediate} output primitives act at \tn{shipout} % time, we need to alter this primitive too. % %^^A Not sure if the shipout primitive is done right. % \begin{macrocode} \cs_new_eq:NN \@@_tex_shipout:w \tex_shipout:D % \end{macrocode} % \end{macro} % % \subsection{Variables} % % \begin{variable}{\g_@@_late_write_int} % The integer \cs{g_@@_late_write_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} |}| % \cs{tex_shipout:D} \cs{tex_copy:D} \cs{l_my_box} % \cs{tex_shipout:D} \cs{tex_copy:D} \cs{l_my_box} % \end{quote} % will print \meta{text} to the terminal twice. % \begin{macrocode} \int_new:N \g_@@_late_write_int % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_iow_prop} % The property list \cs{g_@@_iow_prop} associates a file name to each % open stream. % \begin{macrocode} \prop_new:N \g_@@_iow_prop % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_iow, \g_@@_ior, \g_@@_tmp_file_tl} % The expansion that \tn{write} performs is impossible to emulate 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 \tl_new:N \g_@@_tmp_file_tl \tl_gset:Nn \g_@@_tmp_file_tl { \jobname.mw } % \end{macrocode} % \end{variable} % % \begin{variable}{\g_@@_reserved_iow_clist} % Some of the writing streams are already allocated when loading this % package, and we let the engine manage them. This variable is a % clist because it only contains integers and the main task is to test % if a given integer is in the comma list. % \begin{macrocode} \clist_new:N \g_@@_reserved_iow_clist \int_step_inline:nnnn {0} {1} { \g_@@_iow - 1 } { \clist_gput_right:Nn \g_@@_reserved_iow_clist {#1} } \clist_gput_right:Nn \g_@@_reserved_iow_clist {18} % \end{macrocode} % \end{variable} % % \begin{variable}[int]{\g_@@_stream_int} % An integer holding the \meta{number} argument of various primitives, % namely a writing stream. % \begin{macrocode} \int_new:N \g_@@_stream_int % \end{macrocode} % \end{variable} % % \begin{macro}[int]{\s_@@} % A recognizable version of \cs{scan_stop:}. This is inspired % from\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 % note that we don't use \cs{__scan_new:N} directly, since it is % internal to \LaTeX3. % \begin{macrocode} \cs_new_eq:NN \s_@@ \scan_stop: % \end{macrocode} % \end{macro} % % \begin{variable}[int]{\l_@@_internal_tl} % Temporary token list, used for scratch purposes. % \begin{macrocode} \tl_new:N \l_@@_internal_tl % \end{macrocode} % \end{variable} % % \subsection{Parsing} % % \begin{macro}[aux]{\@@_equals_file_name: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_name:N #1 { \group_begin: \tex_aftergroup:D #1 \primargs_remove_equals:N \@@_parse_file_name: } \cs_new_protected_nopar:Npn \@@_parse_file_name: { \primargs_get_file_name:N \group_end: } % \end{macrocode} % \end{macro} % % \subsection{Immediate (writing)} % % In the context of immediate writing, we can store the text % in a token list, and only write it at the corresponding % \tn{closeout} command. We keep track of a property list, % \cs{g_@@_iow_prop}, of the writes which are % open (from the point of view of the user), with the % corresponding file name. % % \subsubsection{What follows \tn{immediate}} % % \begin{macro}[aux, updated = 2012-12-05]{\@@_immediate:w} % \begin{macro}[aux, updated = 2012-12-05]{\@@_immediate_ii:} % \begin{macro}[aux]{\@@_immediate_iii:N} % \begin{macro}[aux]{\@@_immediate_iv:NN} % \begin{macro}[aux, EXP]{\@@_immediate_v:w} % This is a little bit subtle: \TeX{}'s \tn{immediate} primitive % raises a flag which is cancelled once \TeX{} sees a non-expandable % token. We use \pkg{primargs}'s \texttt{read_x_token} function to % fully expand in the \TeX{} way, then test for \tn{openout}, % \tn{write}, or \tn{closeout}. We don't test for the primitives % themselves, but rather for a recognizable marker, \cs{s_@@}, equal % to \tn{relax}. If present, replace |morewrites| by % |morewrites_immediate| in the csname of the second token after it % (it turns out that this is the correct structure). If absent, what % follows may be a command that should not appear after % \tn{immediate}, but it may also be a non-\TeX\ primitive such as % \tn{pdfobj} that the \pkg{morewrite} does not know about; hence we % must still call the primitive \tn{immediate}. % \begin{macrocode} \cs_new_protected_nopar:Npn \@@_immediate:w { \primargs_read_x_token:N \@@_immediate_ii: } \cs_new_protected_nopar:Npn \@@_immediate_ii: { \token_if_eq_meaning:NNTF \g_primargs_token \s_@@ { \@@_immediate_iii:N } { \@@_tex_immediate:w } } \cs_new_protected:Npn \@@_immediate_iii:N #1 { \tl_if_eq:nnTF { #1 } { \s_@@ } { \@@_immediate_iv:NN } { #1 } } \cs_new_protected:Npn \@@_immediate_iv:NN #1 #2 { \exp_args:Nc #1 { \exp_after:wN \@@_immediate_v:w \token_to_str:N #2 } } \use:x { \cs_new:Npn \exp_not:N \@@_immediate_v:w ##1 \tl_to_str:n { @@ } { @@_immediate } } % \end{macrocode} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % \end{macro} % % \subsubsection{Immediate closeout} % % \begin{macro}[aux]{\@@_immediate_closeout_test:n} % When the user requests to close a stream, we look in % \cs{g_@@_reserved_iow_clist} to see if it is a reserved % stream: in this case, we simply use the primitive. % \begin{macrocode} \cs_new_protected:Npn \@@_immediate_closeout_test:n #1 { \int_gset:Nn \g_@@_stream_int {#1} \clist_if_in:NnTF \g_@@_reserved_iow_clist {#1} { \@@_tex_immediate:w \@@_tex_closeout:w \g_@@_stream_int } { \@@_immediate_closeout_aux: } } % \end{macrocode} % \end{macro} % % \begin{macro}[aux]{\@@_immediate_closeout_aux:} % We then look in \cs{g_@@_iow_prop} to find the file name % corresponding to that stream number. If the stream does not appear % as a key in the property list, then it was not open yet, and we do % nothing. Otherwise, the key is removed, and we write the collected % material to the file. % \begin{macrocode} \cs_new_protected_nopar:Npn \@@_immediate_closeout_aux: { \prop_gpop:NVNT \g_@@_iow_prop \g_@@_stream_int \l_@@_internal_tl { \@@_immediate_write_and_close:nn { \g_@@_stream_int } { \l_@@_internal_tl } } } % \end{macrocode} % \end{macro} % % \begin{macro}[aux]{\@@_immediate_write_and_close:nn} % The code to write the material collected so far for a given output % \meta{stream} is in the token list \cs{g_@@_iow_\meta{stream}_tl}. % We do this writing in the actual stream \cs{g_@@_iow}, briefly % opened and closed on the file |#2|. % \begin{macrocode} \cs_new_protected:Npn \@@_immediate_write_and_close:nn #1#2 { \@@_tex_immediate:w \@@_tex_openout:w \g_@@_iow #2 \scan_stop: \group_begin: \int_set_eq:NN \tex_newlinechar:D \c_minus_one \tl_use:c { g_@@_iow_ \int_eval:n {#1} _tl } \tl_gclear:c { g_@@_iow_ \int_eval:n {#1} _tl } \group_end: \@@_tex_immediate:w \@@_tex_closeout:w \g_@@_iow } % \end{macrocode} % \end{macro} % % \subsubsection{Immediate openout} % % \begin{macro}[aux]{\@@_immediate_openout_test:n} % Read the stream number. If it is one of the reserved streams, we % use the primitive. Otherwise, parse an optional equal sign, % followed by the file name. % \begin{macrocode} \cs_new_protected:Npn \@@_immediate_openout_test:n #1 { \int_gset:Nn \g_@@_stream_int {#1} \clist_if_in:NnTF \g_@@_reserved_iow_clist {#1} { \@@_tex_immediate:w \@@_tex_openout:w \g_@@_stream_int } { \@@_equals_file_name:N \@@_immediate_openout_aux:n } } % \end{macrocode} % \end{macro} % % \begin{macro}[aux]{\@@_immediate_openout_aux:n} % When the user requests to open a stream, it might already be open, % with another file as its destination. We thus need to first close % the stream, writing all that we collected so far to that other % file. This has no effect if the stream was not open yet. % % We then put the stream and its associated file name in the property % list, and empty/create the corresponding token list. % \begin{macrocode} \cs_new_protected:Npn \@@_immediate_openout_aux:n #1 { \@@_immediate_closeout_aux: \prop_gput:NVn \g_@@_iow_prop \g_@@_stream_int {#1} \tl_gclear_new:c { g_@@_iow_ \int_use:N \g_@@_stream_int _tl } } % \end{macrocode} % \end{macro} % % \subsubsection{Immediate write} % % \begin{macro}[aux]{\@@_immediate_write_test:n} % Read the stream number. If it is one of the reserved streams, we % use the primitive. Otherwise, parse the text. % \begin{macrocode} \cs_new_protected:Npn \@@_immediate_write_test:n #1 { \int_gset:Nn \g_@@_stream_int {#1} \clist_if_in:NnTF \g_@@_reserved_iow_clist {#1} { \@@_tex_immediate:w \@@_tex_write:w \g_@@_stream_int } { \primargs_get_general_text:N \@@_immediate_write_aux:n } } % \end{macrocode} % \end{macro} % % \begin{macro}[aux]{\@@_immediate_write_aux:n} % Test whether the stream is allocated or not. % \begin{macrocode} \cs_new_protected_nopar:Npn \@@_immediate_write_aux:n { \prop_get:NVNTF \g_@@_iow_prop \g_@@_stream_int \l_@@_internal_tl { \@@_immediate_write_open:n } { \@@_immediate_write_closed:n } } % \end{macrocode} % \end{macro} % % \begin{macro}[aux]{\@@_immediate_write_closed:n} % If the stream \cs{g_@@_stream_int} is not allocated, then write % either to the terminal or only to the log file, depending on the % sign. % \begin{macrocode} \cs_new_protected:Npn \@@_immediate_write_closed:n #1 { \@@_tex_immediate:w \@@_tex_write:w \if_int_compare:w \g_@@_stream_int < \c_zero -1 \else: 16 \fi: {#1} } % \end{macrocode} % \end{macro} % % \begin{macro}[aux]{\@@_immediate_write_open:n} % \begin{macro}[aux]{\@@_immediate_write_readlines_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, 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 the % token list \cs{g_@@_iow_\meta{stream}_tl}. 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 \@@_immediate_write_open:n #1 { \@@_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_eq:NN \tex_endlinechar:D \c_minus_one \tex_openin:D \g_@@_ior \g_@@_tmp_file_tl \scan_stop: \@@_immediate_write_readlines_loop: \tex_closein:D \g_@@_ior \group_end: } \cs_new_protected_nopar:Npn \@@_immediate_write_readlines_loop: { \etex_readline:D \g_@@_ior to \l_@@_internal_tl \ior_if_eof:NF \g_@@_ior { \tl_gput_right:cx { g_@@_iow_ \int_use:N \g_@@_stream_int _tl } { \@@_tex_immediate:w \@@_tex_write:w \g_@@_iow { \l_@@_internal_tl } } \@@_immediate_write_readlines_loop: } } % \end{macrocode} % \end{macro} % \end{macro} % % % \subsection{Non-immediate writing} % % This is trickier, because the expansion of the text for a % non-immediate \tn{write} takes place immediately after the page % containing it is shipped out. We store each non-immediate % \tn{openout}, \tn{write}, or \tn{closeout} without expansion in % separate token lists \cs{g_@@_late_write_\meta{stream}_tl} to be used % later, and instead write |`(|\meta{stream}|)| to a file (including the % strange delimiters). After each shipout, we can read the file to see % which output operations we need to perform, and in what order. % % \subsubsection{Replacement for primitives} % % \begin{macro}[aux]{\@@_late:n} % Store the action to be done at shipout in a token list, and % non-immediately write the label \cs{g_@@_late_write_int} of the % output operation to the temporary file. Here, |#1| holds an % assignment similar to the lines above it, and |#2| holds the % relevant immediate action to be performed after shipout. % \begin{macrocode} \cs_new_protected:Npn \@@_late:n #1 { \int_gincr:N \g_@@_late_write_int \tl_const:cx { c_@@_late_write_ \int_use:N \g_@@_late_write_int _tl } { \int_gset:Nn \exp_not:N \g_@@_stream_int { \exp_not:V \g_@@_stream_int } \exp_not:n {#1} } \exp_args:NNx \@@_tex_write:w \g_@@_iow { `( \int_use:N \g_@@_late_write_int ) } } % \end{macrocode} % \end{macro} % % \begin{macro}[aux]{\@@_openout:w} % \begin{macro}[aux]{\@@_openout_test:n, \@@_openout_aux:n} % \tn{openout} tests if the number to come is among reserved streams. % If it is, use the primitive, otherwise, parse a file name. % \begin{macrocode} \cs_new_protected_nopar:Npn \@@_openout:w { \s_@@ \primargs_get_number:N \@@_openout_test:n } \cs_new_protected:Npn \@@_openout_test:n #1 { \int_gset:Nn \g_@@_stream_int {#1} \clist_if_in:NnTF \g_@@_reserved_iow_clist {#1} { \@@_tex_openout:w \g_@@_stream_int } { \@@_equals_file_name:N \@@_openout_aux:n } } \cs_new_protected:Npn \@@_openout_aux:n #1 { \@@_late:n { \@@_immediate_openout_aux:n {#1} } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[aux]{\@@_write:w} % \begin{macro}[aux]{\@@_write_test:n, \@@_write_aux:n} % Same idea for \tn{write}, except that we parse a text. % \begin{macrocode} \cs_new_protected_nopar:Npn \@@_write:w { \s_@@ \primargs_get_number:N \@@_write_test:n } \cs_new_protected:Npn \@@_write_test:n #1 { \int_gset:Nn \g_@@_stream_int {#1} \clist_if_in:NnTF \g_@@_reserved_iow_clist {#1} { \@@_tex_write:w \g_@@_stream_int } { \primargs_get_general_text:N \@@_write_aux:n } } \cs_new_protected:Npn \@@_write_aux:n #1 { \@@_late:n { \@@_immediate_write_aux:n {#1} } } % \end{macrocode} % \end{macro} % \end{macro} % % \begin{macro}[aux]{\@@_closeout:w} % \begin{macro}[aux]{\@@_closeout_test:n, \@@_closeout_aux:} % Same idea for \tn{closeout}, and we don't need to parse % anything else than the number. % \begin{macrocode} \cs_new_protected_nopar:Npn \@@_closeout:w { \s_@@ \primargs_get_number:N \@@_closeout_test:n } \cs_new_protected:Npn \@@_closeout_test:n #1 { \int_gset:Nn \g_@@_stream_int {#1} \clist_if_in:NnTF \g_@@_reserved_iow_clist {#1} { \@@_tex_closeout:w \g_@@_stream_int } { \@@_closeout_aux: } } \cs_new_protected_nopar:Npn \@@_closeout_aux: { \@@_late:n { \@@_immediate_closeout_aux: } } % \end{macrocode} % \end{macro} % \end{macro} % % \subsubsection{Shipout business} % % In this section, we hook into the \cs{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} % This is correct even if the values of the \tn{newlinechar} is changed % within the user code which builds the shipped out box, because the % value that \TeX{} uses is the value in effect immediately after % \tn{shipout}. % % \begin{macro}[aux]{\@@_before_shipout:} % Immediately before the shipout, we must open the writing stream % \cs{g_@@_iow}. 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{macrocode} \cs_new_protected_nopar:Npn \@@_before_shipout: { \@@_tex_immediate:w \@@_tex_openout:w \g_@@_iow \g_@@_tmp_file_tl \scan_stop: } % \end{macrocode} % \end{macro} % % \begin{macro}[aux]{\@@_after_shipout:} % \begin{macro}[aux, 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} extract 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_nopar: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} } \etex_everyeof:D { `() \exp_not:N } \tl_gset:Nx \g_@@_internal_tl { \exp_after:wN \@@_after_shipout_loop:ww \tex_input:D \g_@@_tmp_file_tl \c_space_tl } \group_end: \tl_map_inline:Nn \g_@@_internal_tl { \tl_use:c { c_@@_late_write_ ##1 _tl } } } \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}[aux]{\shipout} % \begin{macro}[aux]{\@@_shipout:w} % \begin{variable}{\g_@@_group_level_int, \g_@@_shipout_box} % If \pkg{atbegshi} is available, patch it by adding % \cs{@@_before_shipout:} and \cs{@@_after_shipout:} at the right % place: the two transformations are needed to cover several versions % of the package. Otherwise, redefine \tn{shipout} to add a hook (see % Heiko's \pkg{atbegshi} for details). % \begin{macrocode} \IfFileExists{atbegshi.sty} { \RequirePackage{atbegshi} \tl_replace_once:Nnn \AtBegShi@Output { \AtBegShi@OrgShipout \box \AtBeginShipoutBox } { \@@_before_shipout: \AtBegShi@OrgShipout \box \AtBeginShipoutBox \@@_after_shipout: } \tl_replace_once:Nnn \AtBegShi@Output { \AtBeginShipoutOriginalShipout \box \AtBeginShipoutBox } { \@@_before_shipout: \AtBeginShipoutOriginalShipout \box \AtBeginShipoutBox \@@_after_shipout: } } { \int_new:N \g_@@_group_level_int \box_new:N \g_@@_shipout_box \cs_new_protected_nopar:Npn \@@_shipout:w { \int_gset_eq:NN \g_@@_group_level_int \etex_currentgrouplevel:D \tex_afterassignment:D \@@_shipout_i: \tex_global:D \tex_setbox:D \g_@@_shipout_box } \cs_new_protected_nopar:Npn \@@_shipout_i: { \int_compare:nNnTF { \g_@@_group_level_int } = { \etex_currentgrouplevel:D } { \@@_shipout_ii: } { \tex_aftergroup:D \@@_shipout_ii: } } \cs_new_protected_nopar:Npn \@@_shipout_ii: { \@@_before_shipout: \@@_tex_shipout:w \tex_box:D \g_@@_shipout_box \@@_after_shipout: } \AtBeginDocument { \cs_gset_eq:NN \shipout \@@_shipout:w } } % \end{macrocode} % \end{variable} % \end{macro} % \end{macro} % % \subsection{Hook at the very end} % % \begin{variable}{\g_@@_at_end_int} % At the end of the run, we try very hard to put some material at the % |\@@end|. This integer controls how many times to call % \cs{@@_close_all_at_end:w}, to avoid infinite loops in case two % packages compete for that last place. % \begin{macrocode} \int_new:N \g_@@_at_end_int \int_gset:Nn \g_@@_at_end_int { 10 } % \end{macrocode} % \end{variable} % % \begin{macro}[aux]{\@@_close_all:} % At the end of the document, close all the files. % \begin{macrocode} \cs_new_protected_nopar:Npn \@@_close_all: { \prop_map_function:NN \g_@@_iow_prop \@@_immediate_write_and_close:nn \prop_gclear:N \g_@@_iow_prop } % \end{macrocode} % \end{macro} % % \begin{macro}[aux]{\@@_close_all_at_end:w} % This pushes its first argument to the very end of the \LaTeX{} run, % recursively (at most $10$ times, initial value of % \cs{g_@@_at_end_int}), just in case some other code adds things % there. % \begin{macrocode} \cs_set:Npn \@@_tmp:w #1 { \cs_new_protected:Npn \@@_close_all_at_end:w ##1 #1 { \int_gdecr:N \g_@@_at_end_int \int_compare:nNnTF \g_@@_at_end_int > \c_zero { \tl_if_empty:nTF {##1} { ##1 \@@_close_all: } { ##1 \@@_close_all_at_end:w } } { \@@_close_all: ##1 } #1 } } \exp_args:Nc \@@_tmp:w { @ @ end } \AtEndDocument { \@@_close_all_at_end:w } % \end{macrocode} % \end{macro} % % \subsection{Modified \cs{newwrite}} % % \begin{variable}{\g_@@_alloc_int} % The counter that \LaTeXe{} uses to allocate \tn{write} registers. % \begin{macrocode} \tex_countdef:D \g_@@_alloc_int 17 \scan_stop: % \end{macrocode} % \end{variable} % % \begin{macro}[aux]{\newwrite} % We need to allow \cs{newwrite} to allocate more than $16$ writes, % but beware that $18$ is reserved, and that packages might expect % $16$ or $17$ to write to the terminal. So instead skip until $20$, % to be on the safe side. This really ought to be \tn{protected}, but % none of the formats does that. % \begin{macrocode} \cs_new:Npn \@@_newwrite:N #1 { \int_gincr:N \g_@@_alloc_int \if_int_compare:w \g_@@_alloc_int = \c_sixteen \int_gset:Nn \g_@@_alloc_int { 20 } \fi: \int_set_eq:NN \allocationnumber \g_@@_alloc_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} % % \subsection{Redefining the ``normal'' control sequences} % % \begin{macro}[aux, updated = 2012-12-05]{\immediate} % \begin{macro}[aux]{\openout, \write, \closeout, \newwrite} % \tn{shipout} has been redefined earlier. % \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{macro} % % % % \end{implementation} % % \endinput