diff options
Diffstat (limited to 'macros/generic')
36 files changed, 21754 insertions, 0 deletions
diff --git a/macros/generic/advice/INSTALL.md b/macros/generic/advice/INSTALL.md new file mode 100644 index 0000000000..96844efa8f --- /dev/null +++ b/macros/generic/advice/INSTALL.md @@ -0,0 +1,69 @@ +# Installation from the TDS archive + +If Advice is not (yet) offered by your TeX distribution, the easiest way to +install it is by downloading the TDS archive `advice.tds.zip` from [Advice's +CTAN page](https://ctan.org/pkg/advice), and unpacking it into your `texmf` +directory. You will most likely also have to do the same for an auxiliary +package Advice depends on: [CollArgs](https://ctan.org/pkg/collargs). + +Read on only if you have an unstoppable urge to install from source and/or +compile the manual or the documented source code. + +# Installation from the source + +## Getting the sources + +There are several options: + +* Download and unpack the zip archive of the package from [Advice's CTAN + page](https://ctan.org/pkg/advice). + +* Download and unpack the TDS archive, or copy the files from your local + distribution. The sources reside in `<texmf>/source/generic/advice`. + +* Clone the [GitHub repository of + Memoize](https://github.com/sasozivanovic/memoize). + +## Generating the runtime files + +The easiest way to generate the runtime files is by running `make`. The +following command will generate runtime files for all supported formats +(currently: LaTeX, plain TeX and ConTeXt). + +``` +make advice.sty +``` + +Alternatively, you can generate these files manually. The source of this +package was written using [EasyDTX](https://ctan.org/pkg/easydtx). Therefore, +you first have to convert the `.edtx` file into a regular `.dtx`: + +``` +edtx2dtx advice.edtx > advice.dtx +``` + +The next step is standard. Produce the runtime files by compiling the +installation file: + +``` +tex advice.ins +``` + +Finally, if you require the ConTeXt runtime, replace all instances of +`\expanded` and `\unexpanded` in `t-advice.tex` by `\normalexpanded` and +`\normalunexpanded`, respectively. One way to do this is: + +``` +sed -i -s -e 's/\\\(un\)\?expanded/\\normal\1expanded/g;' t-advice.tex +``` + +## Installation + +It is recommended to install the files into a TDS-compliant `texmf` directory, +as usual. Inspect file `FILES` or the TDS archive `advice.tds.zip` to see what +goes where. + +# Compiling the documentation + +The documentation of this package is integrated into the documentation of +[Memoize](https://ctan.org/pkg/memoize), please continue there. diff --git a/macros/generic/advice/LICENCE b/macros/generic/advice/LICENCE new file mode 100644 index 0000000000..1b57559a83 --- /dev/null +++ b/macros/generic/advice/LICENCE @@ -0,0 +1,416 @@ +The LaTeX Project Public License +=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +LPPL Version 1.3c 2008-05-04 + +Copyright 1999 2002-2008 LaTeX3 Project + Everyone is allowed to distribute verbatim copies of this + license document, but modification of it is not allowed. + + +PREAMBLE +======== + +The LaTeX Project Public License (LPPL) is the primary license under +which the LaTeX kernel and the base LaTeX packages are distributed. + +You may use this license for any work of which you hold the copyright +and which you wish to distribute. This license may be particularly +suitable if your work is TeX-related (such as a LaTeX package), but +it is written in such a way that you can use it even if your work is +unrelated to TeX. + +The section `WHETHER AND HOW TO DISTRIBUTE WORKS UNDER THIS LICENSE', +below, gives instructions, examples, and recommendations for authors +who are considering distributing their works under this license. + +This license gives conditions under which a work may be distributed +and modified, as well as conditions under which modified versions of +that work may be distributed. + +We, the LaTeX3 Project, believe that the conditions below give you +the freedom to make and distribute modified versions of your work +that conform with whatever technical specifications you wish while +maintaining the availability, integrity, and reliability of +that work. If you do not see how to achieve your goal while +meeting these conditions, then read the document `cfgguide.tex' +and `modguide.tex' in the base LaTeX distribution for suggestions. + + +DEFINITIONS +=========== + +In this license document the following terms are used: + + `Work' + Any work being distributed under this License. + + `Derived Work' + Any work that under any applicable law is derived from the Work. + + `Modification' + Any procedure that produces a Derived Work under any applicable + law -- for example, the production of a file containing an + original file associated with the Work or a significant portion of + such a file, either verbatim or with modifications and/or + translated into another language. + + `Modify' + To apply any procedure that produces a Derived Work under any + applicable law. + + `Distribution' + Making copies of the Work available from one person to another, in + whole or in part. Distribution includes (but is not limited to) + making any electronic components of the Work accessible by + file transfer protocols such as FTP or HTTP or by shared file + systems such as Sun's Network File System (NFS). + + `Compiled Work' + A version of the Work that has been processed into a form where it + is directly usable on a computer system. This processing may + include using installation facilities provided by the Work, + transformations of the Work, copying of components of the Work, or + other activities. Note that modification of any installation + facilities provided by the Work constitutes modification of the Work. + + `Current Maintainer' + A person or persons nominated as such within the Work. If there is + no such explicit nomination then it is the `Copyright Holder' under + any applicable law. + + `Base Interpreter' + A program or process that is normally needed for running or + interpreting a part or the whole of the Work. + + A Base Interpreter may depend on external components but these + are not considered part of the Base Interpreter provided that each + external component clearly identifies itself whenever it is used + interactively. Unless explicitly specified when applying the + license to the Work, the only applicable Base Interpreter is a + `LaTeX-Format' or in the case of files belonging to the + `LaTeX-format' a program implementing the `TeX language'. + + + +CONDITIONS ON DISTRIBUTION AND MODIFICATION +=========================================== + +1. Activities other than distribution and/or modification of the Work +are not covered by this license; they are outside its scope. In +particular, the act of running the Work is not restricted and no +requirements are made concerning any offers of support for the Work. + +2. You may distribute a complete, unmodified copy of the Work as you +received it. Distribution of only part of the Work is considered +modification of the Work, and no right to distribute such a Derived +Work may be assumed under the terms of this clause. + +3. You may distribute a Compiled Work that has been generated from a +complete, unmodified copy of the Work as distributed under Clause 2 +above, as long as that Compiled Work is distributed in such a way that +the recipients may install the Compiled Work on their system exactly +as it would have been installed if they generated a Compiled Work +directly from the Work. + +4. If you are the Current Maintainer of the Work, you may, without +restriction, modify the Work, thus creating a Derived Work. You may +also distribute the Derived Work without restriction, including +Compiled Works generated from the Derived Work. Derived Works +distributed in this manner by the Current Maintainer are considered to +be updated versions of the Work. + +5. If you are not the Current Maintainer of the Work, you may modify +your copy of the Work, thus creating a Derived Work based on the Work, +and compile this Derived Work, thus creating a Compiled Work based on +the Derived Work. + +6. If you are not the Current Maintainer of the Work, you may +distribute a Derived Work provided the following conditions are met +for every component of the Work unless that component clearly states +in the copyright notice that it is exempt from that condition. Only +the Current Maintainer is allowed to add such statements of exemption +to a component of the Work. + + a. If a component of this Derived Work can be a direct replacement + for a component of the Work when that component is used with the + Base Interpreter, then, wherever this component of the Work + identifies itself to the user when used interactively with that + Base Interpreter, the replacement component of this Derived Work + clearly and unambiguously identifies itself as a modified version + of this component to the user when used interactively with that + Base Interpreter. + + b. Every component of the Derived Work contains prominent notices + detailing the nature of the changes to that component, or a + prominent reference to another file that is distributed as part + of the Derived Work and that contains a complete and accurate log + of the changes. + + c. No information in the Derived Work implies that any persons, + including (but not limited to) the authors of the original version + of the Work, provide any support, including (but not limited to) + the reporting and handling of errors, to recipients of the + Derived Work unless those persons have stated explicitly that + they do provide such support for the Derived Work. + + d. You distribute at least one of the following with the Derived Work: + + 1. A complete, unmodified copy of the Work; + if your distribution of a modified component is made by + offering access to copy the modified component from a + designated place, then offering equivalent access to copy + the Work from the same or some similar place meets this + condition, even though third parties are not compelled to + copy the Work along with the modified component; + + 2. Information that is sufficient to obtain a complete, + unmodified copy of the Work. + +7. If you are not the Current Maintainer of the Work, you may +distribute a Compiled Work generated from a Derived Work, as long as +the Derived Work is distributed to all recipients of the Compiled +Work, and as long as the conditions of Clause 6, above, are met with +regard to the Derived Work. + +8. The conditions above are not intended to prohibit, and hence do not +apply to, the modification, by any method, of any component so that it +becomes identical to an updated version of that component of the Work as +it is distributed by the Current Maintainer under Clause 4, above. + +9. Distribution of the Work or any Derived Work in an alternative +format, where the Work or that Derived Work (in whole or in part) is +then produced by applying some process to that format, does not relax or +nullify any sections of this license as they pertain to the results of +applying that process. + +10. a. A Derived Work may be distributed under a different license + provided that license itself honors the conditions listed in + Clause 6 above, in regard to the Work, though it does not have + to honor the rest of the conditions in this license. + + b. If a Derived Work is distributed under a different license, that + Derived Work must provide sufficient documentation as part of + itself to allow each recipient of that Derived Work to honor the + restrictions in Clause 6 above, concerning changes from the Work. + +11. This license places no restrictions on works that are unrelated to +the Work, nor does this license place any restrictions on aggregating +such works with the Work by any means. + +12. Nothing in this license is intended to, or may be used to, prevent +complete compliance by all parties with all applicable laws. + + +NO WARRANTY +=========== + +There is no warranty for the Work. Except when otherwise stated in +writing, the Copyright Holder provides the Work `as is', without +warranty of any kind, either expressed or implied, including, but not +limited to, the implied warranties of merchantability and fitness for a +particular purpose. The entire risk as to the quality and performance +of the Work is with you. Should the Work prove defective, you assume +the cost of all necessary servicing, repair, or correction. + +In no event unless required by applicable law or agreed to in writing +will The Copyright Holder, or any author named in the components of the +Work, or any other party who may distribute and/or modify the Work as +permitted above, be liable to you for damages, including any general, +special, incidental or consequential damages arising out of any use of +the Work or out of inability to use the Work (including, but not limited +to, loss of data, data being rendered inaccurate, or losses sustained by +anyone as a result of any failure of the Work to operate with any other +programs), even if the Copyright Holder or said author or said other +party has been advised of the possibility of such damages. + + +MAINTENANCE OF THE WORK +======================= + +The Work has the status `author-maintained' if the Copyright Holder +explicitly and prominently states near the primary copyright notice in +the Work that the Work can only be maintained by the Copyright Holder +or simply that it is `author-maintained'. + +The Work has the status `maintained' if there is a Current Maintainer +who has indicated in the Work that they are willing to receive error +reports for the Work (for example, by supplying a valid e-mail +address). It is not required for the Current Maintainer to acknowledge +or act upon these error reports. + +The Work changes from status `maintained' to `unmaintained' if there +is no Current Maintainer, or the person stated to be Current +Maintainer of the work cannot be reached through the indicated means +of communication for a period of six months, and there are no other +significant signs of active maintenance. + +You can become the Current Maintainer of the Work by agreement with +any existing Current Maintainer to take over this role. + +If the Work is unmaintained, you can become the Current Maintainer of +the Work through the following steps: + + 1. Make a reasonable attempt to trace the Current Maintainer (and + the Copyright Holder, if the two differ) through the means of + an Internet or similar search. + + 2. If this search is successful, then enquire whether the Work + is still maintained. + + a. If it is being maintained, then ask the Current Maintainer + to update their communication data within one month. + + b. If the search is unsuccessful or no action to resume active + maintenance is taken by the Current Maintainer, then announce + within the pertinent community your intention to take over + maintenance. (If the Work is a LaTeX work, this could be + done, for example, by posting to comp.text.tex.) + + 3a. If the Current Maintainer is reachable and agrees to pass + maintenance of the Work to you, then this takes effect + immediately upon announcement. + + b. If the Current Maintainer is not reachable and the Copyright + Holder agrees that maintenance of the Work be passed to you, + then this takes effect immediately upon announcement. + + 4. If you make an `intention announcement' as described in 2b. above + and after three months your intention is challenged neither by + the Current Maintainer nor by the Copyright Holder nor by other + people, then you may arrange for the Work to be changed so as + to name you as the (new) Current Maintainer. + + 5. If the previously unreachable Current Maintainer becomes + reachable once more within three months of a change completed + under the terms of 3b) or 4), then that Current Maintainer must + become or remain the Current Maintainer upon request provided + they then update their communication data within one month. + +A change in the Current Maintainer does not, of itself, alter the fact +that the Work is distributed under the LPPL license. + +If you become the Current Maintainer of the Work, you should +immediately provide, within the Work, a prominent and unambiguous +statement of your status as Current Maintainer. You should also +announce your new status to the same pertinent community as +in 2b) above. + + +WHETHER AND HOW TO DISTRIBUTE WORKS UNDER THIS LICENSE +====================================================== + +This section contains important instructions, examples, and +recommendations for authors who are considering distributing their +works under this license. These authors are addressed as `you' in +this section. + +Choosing This License or Another License +---------------------------------------- + +If for any part of your work you want or need to use *distribution* +conditions that differ significantly from those in this license, then +do not refer to this license anywhere in your work but, instead, +distribute your work under a different license. You may use the text +of this license as a model for your own license, but your license +should not refer to the LPPL or otherwise give the impression that +your work is distributed under the LPPL. + +The document `modguide.tex' in the base LaTeX distribution explains +the motivation behind the conditions of this license. It explains, +for example, why distributing LaTeX under the GNU General Public +License (GPL) was considered inappropriate. Even if your work is +unrelated to LaTeX, the discussion in `modguide.tex' may still be +relevant, and authors intending to distribute their works under any +license are encouraged to read it. + +A Recommendation on Modification Without Distribution +----------------------------------------------------- + +It is wise never to modify a component of the Work, even for your own +personal use, without also meeting the above conditions for +distributing the modified component. While you might intend that such +modifications will never be distributed, often this will happen by +accident -- you may forget that you have modified that component; or +it may not occur to you when allowing others to access the modified +version that you are thus distributing it and violating the conditions +of this license in ways that could have legal implications and, worse, +cause problems for the community. It is therefore usually in your +best interest to keep your copy of the Work identical with the public +one. Many works provide ways to control the behavior of that work +without altering any of its licensed components. + +How to Use This License +----------------------- + +To use this license, place in each of the components of your work both +an explicit copyright notice including your name and the year the work +was authored and/or last substantially modified. Include also a +statement that the distribution and/or modification of that +component is constrained by the conditions in this license. + +Here is an example of such a notice and statement: + + %% pig.dtx + %% Copyright 2008 M. Y. Name + % + % This work may be distributed and/or modified under the + % conditions of the LaTeX Project Public License, either version 1.3 + % of this license or (at your option) any later version. + % The latest version of this license is in + % https://www.latex-project.org/lppl.txt + % and version 1.3c or later is part of all distributions of LaTeX + % version 2008 or later. + % + % This work has the LPPL maintenance status `maintained'. + % + % The Current Maintainer of this work is M. Y. Name. + % + % This work consists of the files pig.dtx and pig.ins + % and the derived file pig.sty. + +Given such a notice and statement in a file, the conditions +given in this license document would apply, with the `Work' referring +to the three files `pig.dtx', `pig.ins', and `pig.sty' (the last being +generated from `pig.dtx' using `pig.ins'), the `Base Interpreter' +referring to any `LaTeX-Format', and both `Copyright Holder' and +`Current Maintainer' referring to the person `M. Y. Name'. + +If you do not want the Maintenance section of LPPL to apply to your +Work, change `maintained' above into `author-maintained'. +However, we recommend that you use `maintained', as the Maintenance +section was added in order to ensure that your Work remains useful to +the community even when you can no longer maintain and support it +yourself. + +Derived Works That Are Not Replacements +--------------------------------------- + +Several clauses of the LPPL specify means to provide reliability and +stability for the user community. They therefore concern themselves +with the case that a Derived Work is intended to be used as a +(compatible or incompatible) replacement of the original Work. If +this is not the case (e.g., if a few lines of code are reused for a +completely different task), then clauses 6b and 6d shall not apply. + + +Important Recommendations +------------------------- + + Defining What Constitutes the Work + + The LPPL requires that distributions of the Work contain all the + files of the Work. It is therefore important that you provide a + way for the licensee to determine which files constitute the Work. + This could, for example, be achieved by explicitly listing all the + files of the Work near the copyright notice of each file or by + using a line such as: + + % This work consists of all files listed in manifest.txt. + + in that place. In the absence of an unequivocal list it might be + impossible for the licensee to determine what is considered by you + to comprise the Work and, in such a case, the licensee would be + entitled to make reasonable conjectures as to which files comprise + the Work. + diff --git a/macros/generic/advice/Makefile b/macros/generic/advice/Makefile new file mode 100644 index 0000000000..421bcdbfc9 --- /dev/null +++ b/macros/generic/advice/Makefile @@ -0,0 +1,8 @@ +.PRECIOUS: %.dtx + +%.dtx: %.edtx + edtx2dtx $< > $@ + +%.sty %.tex t-%.tex &: %.ins %.dtx + tex $< + sed -i -s -e 's/\\\(un\)\?expanded/\\normal\1expanded/g;' t-$*.tex diff --git a/macros/generic/advice/README.md b/macros/generic/advice/README.md new file mode 100644 index 0000000000..0b001d9df8 --- /dev/null +++ b/macros/generic/advice/README.md @@ -0,0 +1,8 @@ +Like its namesake from the Emacs world, package Advice implements a generic +framework for extending the functionality of selected commands and +environments. + +This package was developed as an auxiliary package of +[Memoize](https://ctan.org/pkg/memoize). This is why it is, somewhat +unconventionally, documented alongside that package. This applies to both the +manual and the documented code listing. diff --git a/macros/generic/advice/advice.edtx b/macros/generic/advice/advice.edtx new file mode 100644 index 0000000000..ee9486226e --- /dev/null +++ b/macros/generic/advice/advice.edtx @@ -0,0 +1,1095 @@ +% \iffalse +% +% advice.edtx (this is not a .dtx file; to produce a .dtx, process it with edtx2dtx) +% +%% This file is a part of Advice, a TeX package implementing a generic +%% framework for extending the functionality of selected commands and +%% environments, available at https://ctan.org/pkg/advice and +%% https://github.com/sasozivanovic/advice. +%% +%% Copyright (c) 2023- Saso Zivanovic <saso.zivanovic@guest.arnes.si> +%% (Sa\v{s}o \v{Z}ivanovi\'{c}) +%% +%% This work may be distributed and/or modified under the conditions of the +%% LaTeX Project Public License, either version 1.3c of this license or (at +%% your option) any later version. The latest version of this license is in +%% https://www.latex-project.org/lppl.txt and version 1.3c or later is part of +%% all distributions of LaTeX version 2008 or later. +%% +%% This work has the LPPL maintenance status `maintained'. +%% The Current Maintainer of this work is Saso Zivanovic. +%% +%% The files belonging to this work and covered by LPPL are listed in `FILES`. +% +% \fi +% +% \begin{macrocode} +% +% \relax +% +%<*main> +%<latex>\ProvidesPackage{advice}[2023/10/10 v1.0.0 Extend commands and environments] +%<context>%D \module[ +%<context>%D file=t-advice.tex, +%<context>%D version=1.0.0, +%<context>%D title=Advice, +%<context>%D subtitle=Extend commands and environments, +%<context>%D author=Saso Zivanovic, +%<context>%D date=2023-10-10, +%<context>%D copyright=Saso Zivanovic, +%<context>%D license=LPPL, +%<context>%D ] +%<context>\writestatus{loading}{ConTeXt User Module / advice} +%<context>\unprotect +%<context>\startmodule[advice] +% \paragraph{Required packages} +%<latex>\RequirePackage{collargs} +%<plain>\input collargs +%<context>\input t-collargs +% +% \subsubsection{Installation into a keypath} +% +% \begin{handlerskey}{.install advice} +% This handler installs the advising mechanism into the handled path, which +% we shall henceforth also call the (advice) namespace. +\pgfkeys{ + /handlers/.install advice/.code={% + \edef\auto@install@namespace{\pgfkeyscurrentpath}% + \def\advice@install@setupkey{advice}% + \def\advice@install@activation{immediate}% + \pgfqkeys{/advice/install}{#1}% + \expanded{\noexpand\advice@install + {\auto@install@namespace}% + {\advice@install@setupkey}% + {\advice@install@activation}% + }% + }, +% \end{handlerskey} +% +% \begin{adviceinstallkey}{setup key,activation} +% These keys can be used in the argument of |.install advice| to configure +% the installation. By default, the setup key is |advice| and |activation| is +% |immediate|. + /advice/install/.cd, + setup key/.store in=\advice@install@setupkey, + activation/.is choice, + activation/.append code=\def\advice@install@activation{#1}, + activation/immediate/.code={}, + activation/deferred/.code={}, +} +% \end{adviceinstallkey} +% +% |#1| is the installation keypath (in Memoize, |/mmz|); |#2| is the setup key +% name (in Memoize, |auto|, and this is why we document it as such); |#3| is +% the initial activation regime. +\def\advice@install#1#2#3{% + % Switch to the installation keypath. + \pgfqkeys{#1}{% + % \begin{key}{auto, auto csname, auto key, auto', auto csname', auto key'} + % These keys submit a command or environment to advising. The + % namespace is hard-coded into these keys via |#1|; their arguments are + % the command/environment (cs)name, and setup keys belonging to path + % \meta{installation keypath}|/\meta{setup key name}|. + #2/.code 2 args={% + % Call the internal setup macro, wrapping the received keylist into a + % |pgfkeys| invocation. + \AdviceSetup{#1}{#2}{##1}{\pgfqkeys{#1/#2}{##2}}% + % Activate if not already activated (this can happen when updating the + % configuration). Note we don't call |\advice@activate| directly, but use + % the public keys; in this way, activation is automatically deferred if + % so requested. (We don't use |\pgfkeysalso| to allow |auto| being called + % from any path.) + \pgfqkeys{#1}{try activate, activate={##1}}% + }, + % A variant without activation. + #2'/.code 2 args={% + \AdviceSetup{#1}{#2}{##1}{\pgfqkeys{#1/#2}{##2}}% + }, + #2 csname/.style 2 args={ + #2/.expand once=\expandafter{\csname ##1\endcsname}{##2}, + }, + #2 csname'/.style 2 args={ + #2'/.expand once=\expandafter{\csname ##1\endcsname}{##2}, + }, + #2 key/.style 2 args={ + #2/.expand once=% + \expandafter{\csname pgfk@##1/.@cmd\endcsname}% + {collector=\advice@pgfkeys@collector,##2}, + }, + #2 key'/.style 2 args={ + #2'/.expand once=% + \expandafter{\csname pgfk@##1/.@cmd\endcsname}% + {collector=\advice@pgfkeys@collector,##2}, + }, + % \end{key} + % + % \begin{key}{activation} + % This key, residing in the installation keypath, forwards the request to + % the |/advice| path |activation| subkeys, which define |activate| and + % friends in the installation keypath. Initially, the activation regime + % is whatever the user has requested using the |.install advice| argument + % (here |#3|). + activation/.style={/advice/activation/##1={#1}}, + activation=#3, + % \end{key} + % + % \begin{key}{activate deferred} + % The deferred activations are collected in this style, see + % section~ref{sec:code:advice:activation} for details. + activate deferred/.code={}, + % \end{key} + % + % \begin{key}{activate csname, deactivate csname} + % For simplicity of implementation, the |csname| versions of |activate| + % and |deactivate| accept a single \meta{csname}. This way, they can be + % defined right away, as they don't change with the type of activation + % (immediate vs.\ deferred). + activate csname/.style={activate/.expand once={\csname##1\endcsname}}, + deactivate csname/.style={activate/.expand once={\csname##1\endcsname}}, + % \end{key} + % + % \begin{key}{activate key, deactivate key} + % (De)activation of |pgfkeys| keys. Accepts a list of key names, requires + % full key names. + activate key/.style={activate@key={#1/activate}{##1}}, + deactivate key/.style={activate@key={#1/deactivate}{##1}}, + activate@key/.code n args=2{% + \def\advice@temp{}% + \def\advice@do####1{% + \eappto\advice@temp{,\expandonce{\csname pgfk@####1/.@cmd\endcsname}}}% + \forcsvlist\advice@do{##2}% + \pgfkeysalso{##1/.expand once=\advice@temp}% + }, + % \end{key} + % + % The rest of the keys defined below reside in the |auto| subfolder of the + % installation keypath. + #2/.cd, + % + % \begin{mmzautokey}{run conditions, outer handler, bailout handler, collector, + % args, collector options, clear collector options, raw collector options, + % clear raw collector options, inner handler, options, clear options} + % These keys are used to setup the handling of the command or + % environment. The storage macros (|\AdviceRunConditions| etc.) have + % public names as they also play a crucial role in the handler + % definitions, see section~\ref{sec:code:advice:handle}. \indentmacrocode + run conditions/.store in=\AdviceRunConditions, + bailout handler/.store in=\AdviceBailoutHandler, + outer handler/.store in=\AdviceOuterHandler, + collector/.store in=\AdviceCollector, + collector options/.code={\appto\AdviceCollectorOptions{,##1}}, + clear collector options/.code={\def\AdviceCollectorOptions{}}, + raw collector options/.code={\appto\AdviceRawCollectorOptions{##1}}, + clear raw collector options/.code={\def\AdviceRawCollectorOptions{}}, + args/.store in=\AdviceArgs, + inner handler/.store in=\AdviceInnerHandler, + options/.code={\appto\AdviceOptions{,##1}}, + clear options/.code={\def\AdviceOptions{}}, + % \noindentmacrocode + % A user-friendly way to set |options|: any unknown key is an option. + .unknown/.code={% + \eappto{\AdviceOptions}{,\pgfkeyscurrentname={\unexpanded{##1}}}% + }, + % The default values of the keys, which equal the initial values for + % commands, as assigned by |\advice@setup@init@command|. + run conditions/.default=\AdviceRuntrue, + bailout handler/.default=\relax, + outer handler/.default=\advice@default@outer@handler, + collector/.default=\advice@CollectArgumentsRaw, + collector options/.value required, + raw collector options/.value required, + args/.default=\advice@noargs, + inner handler/.default=\advice@error@noinnerhandler, + options/.value required, + % \end{mmzautokey} + % + % \begin{mmzautokey}{reset} + % This key resets the advice settings to their initial values, which + % depend on whether we're handling a command or environment. + reset/.code={\csname\advice@setup@init@\AdviceType\endcsname}, + % \end{mmzautokey} + % + % \begin{mmzautokey} + % The code given here will be executed once we exit the setup group. + % |integrated driver| of Memoize uses it to declare a conditional. + after setup/.code={\appto\AdviceAfterSetup{##1}}, + % \end{mmzautokey} + % + % In \hologo{LaTeX}, we finish the installation by submitting |\begin|; the + % submission is funky, because the run conditions handler actually hacks + % the standard handling procedure. Note that if |\begin| is not + % activated, environments will not be handled, and that the automatic + % activation might be deffered. + %<latex>#1/#2=\begin{run conditions=\advice@begin@rc}, + }% +} +% +% +% \subsubsection{Submitting a command or environment} +% \label{sec:code:advice:setup} +% +% \begin{macro}{\AdviceSetup,\AdviceName,\AdviceType} +% Macro |\advice@setup| is called by key |auto| to submit a command or +% environment to advising. It receives four arguments: |#1| is the +% installation keypath / storage namespace: |#2| is the name of the setup +% key; |#3| is the submitted command or environment; |#4| is the setup code +% (which is only grabbed by |\advice@setup@i|). +% +% Executing this macro defines macros |\AdviceName|, holding the control +% sequence of the submitted command or the environment name, and |\AdviceType|, +% holding |command| or |environment|; they are used to set up some initial +% values, and may be used by user-defined keys in the |auto| path, as well +% (see |/mmz/auto/noop| for an example). The macro then performs internal +% initialization, and finally calls the second part, |\advice@setup@i|, with +% the command's \emph{storage} name as the first argument. +% +% This macro also serves as the programmer's interface to |auto|, the idea +% being that an advanced user may write code |#4| which defined the settings +% macros (|\AdviceOuterHandler| etc.) without deploying |pgfkeys|. (Also note +% that activation at the end only occurs through the |auto| interface.) +\def\AdviceSetup#1#2#3{% + % Open a group, so that we allow for embedded |auto| invocations. + \begingroup + \def\AdviceName{#3}% + % Command, complain, or environment? + \collargs@cs@cases{#3}{% + \def\AdviceType{command}% + \advice@setup@init@command + \advice@setup@i{#3}{#1}{#3}% + }{% + \advice@error@advice@notcs{#1/#2}{#3}% + }{% + \def\AdviceType{environment}% + \advice@setup@init@environment + %<latex>\advice@setup@i{#3}% + %<plain>\expandafter\advice@setup@i\expandafter{\csname #3\endcsname}% + %<context>\expandafter\advice@setup@i\expandafter{\csname start#3\endcsname}% + {#1}{#3}% + }% +} +% The arguments of |\advice@setup@i| are a bit different than for +% |\advice@setup|, because we have inserted the storage name as |#1| above, and +% we lost the setup key name |#2|. Here, |#2| is the installation keypath / +% storage namespace, |#3| is the submitted command or environment; and |#4| is +% the setup code. +% +% What is the difference between the storage name (|#1|) and the command\slash +% environment name (|#3|, and also the contents of |\AdviceName|), and why do we +% need both? For commands, there is actually no difference; for example, when +% submitting command |\foo|, we end up with |#1|=|#3|=|\foo|. And there is +% also no difference for \hologo{LaTeX} environments; when submitting +% environment |foo|, we get |#1|=|#3|=|foo|. But in \hologo{plainTeX}, +% |#1|=|\foo| and |#3|=|foo|, and in \hologo{ConTeXt}, |#1|=|\startfoo| and +% |#3|=|foo| --- which should explain the guards and |\expandafter|s above. +% +% And why both |#1| and |#3|? When a handled command is executed, it loads its +% configuration from a macro determined by the storage namespace and the +% (|\string|ified) storage name, e.g.\ |/mmz| and |\foo|. In \hologo{plainTeX} +% and \hologo{ConTeXt}, each environment is started by a dedicated command, +% |\foo| or |\startfoo|, so these control sequences (|\string|ified) must act +% as storage names. (Not so in \hologo{LaTeX}, where an environment +% configuration is loaded by |\begin|'s handler, which can easily work with +% storage name |foo|. Even more, having |\foo| as an environment storage +% name would conflict with the storage name for the (environment-internal) +% command |\foo| --- yes, we can submit either |foo| or |\foo|, or both, to +% advising.) +\def\advice@setup@i#1#2#3#4{% + % Load the current configuration of the handled command or environment --- if + % it exists. + \advice@setup@init@i{#2}{#1}% + \advice@setup@init@I{#2}{#1}% + \def\AdviceAfterSetup{}% + % Apply the setup code/keys. + #4% + % Save the resulting configuration. This closes the group, because the config + % is saved outside it. + \advice@setup@save{#2}{#1}% +} +% \end{macro} +% +% \paragraph{Initialize} the configuration of a command or environment. Note +% that the default values of the keys equal the initial values for commands. +% Nothing would go wrong if these were not the same, but it's nice that the +% end-user can easily revert to the initial values. +\def\advice@setup@init@common{% + \def\AdviceRunConditions{\AdviceRuntrue}% + \def\AdviceBailoutHandler{\relax}% + \def\AdviceOuterHandler{\advice@default@outer@handler}% + \def\AdviceCollector{\advice@CollectArgumentsRaw}% + \def\AdviceCollectorOptions{}% + \def\AdviceInnerHandler{\advice@error@noinnerhandler}% + \def\AdviceOptions{}% +} +\def\advice@setup@init@command{% + \advice@setup@init@common + \def\AdviceRawCollectorOptions{}% + \def\AdviceArgs{\advice@noargs}% +} +\def\advice@setup@init@environment{% + \advice@setup@init@common + \edef\AdviceRawCollectorOptions{% + \noexpand\collargsEnvironment{\AdviceName}% + % When grabbing an environment body, the end-tag will be included. This + % makes it possible to have the same inner handler for commands and + % environments. + \noexpand\collargsEndTagtrue + }% + \def\AdviceArgs{+b}% +} +% We need to initialize |\AdviceOuterHandler| etc.\ so that |\advice@setup@store| +% will work. +\advice@setup@init@command +% +% \paragraph.{The configuration storage} +% +% The remaining macros in this subsection deal with the configuration storage +% space, which is set up in a way to facilitate fast loading during the +% execution of handled commands and environments. +% +% The configuration of a command or environment is stored in two parts: the +% first stage settings comprise the run conditions, the bailout handler and the +% outer handler; the second stage settings contain the rest. When a handled +% command is invoked, only the first stage settings are immediately loaded, for +% speed; the second stage settings are only loaded if the run conditions are +% satisfied. +% +% \begin{macro}{\advice@init@i,\advice@init@I} +% The two-stage settings are stored in control sequences +% |\advice@i|\meta{namespace}|//|\meta{storage name} and +% |\advice@I|\meta{namespace}|//|\meta{storage name}, respectively, and +% accessed using macros |\advice@init@i| and |\advice@init@I|. +% +% Each setting storage macro contains a sequence of items, where each item is +% either of form |\def\AdviceSetting|\marg{value}. This allows us store +% multiple settings in a single macro (rather than define each +% control-sequence-valued setting separately, which would use more string +% memory), and also has the consequence that we don't require the handlers to +% be defined when submitting a command (whether that's good or bad could be +% debated: as things stand, any typos in handler declarations will only yield +% an error once the handled command is executed). +\def\advice@init@i#1#2{\csname advice@i#1//\string#2\endcsname} +\def\advice@init@I#1#2{\csname advice@I#1//\string#2\endcsname} +% We make a copy of these for setup; the originals might be swapped for +% tracing purposes. +\let\advice@setup@init@i\advice@init@i +\let\advice@setup@init@I\advice@init@I +% \end{macro} +% +% \begin{macro}{\advice@setup@save} +% To save the configuration at the end of the setup, we construct the storage +% macros out of |\AdviceRunConditions| and friends. Stage-one contains only +% |\AdviceRunConditions| and |\AdviceBailoutHandler|, so that +% |\advice@handle| can bail out as quickly as possible if the run conditions +% are not met. +\def\advice@setup@save#1#2{% + \expanded{% + % Close the group before saving. Note that |\expanded| has already expanded + % the settings macros. + \endgroup + \noexpand\csdef{advice@i#1//\string#2}{% + \def\noexpand\AdviceRunConditions{\expandonce\AdviceRunConditions}% + \def\noexpand\AdviceBailoutHandler{\expandonce\AdviceBailoutHandler}% + }% + \noexpand\csdef{advice@I#1//\string#2}{% + \def\noexpand\AdviceOuterHandler{\expandonce\AdviceOuterHandler}% + \def\noexpand\AdviceCollector{\expandonce\AdviceCollector}% + \def\noexpand\AdviceRawCollectorOptions{\expandonce\AdviceRawCollectorOptions}% + \def\noexpand\AdviceCollectorOptions{\expandonce\AdviceCollectorOptions}% + \def\noexpand\AdviceArgs{\expandonce\AdviceArgs}% + \def\noexpand\AdviceInnerHandler{\expandonce\AdviceInnerHandler}% + \def\noexpand\AdviceOptions{\expandonce\AdviceOptions}% + }% + \expandonce{\AdviceAfterSetup}% + }% +} +% \end{macro} +% \label{sec:code:advice:activation} +% +% \begin{advicekey}{activation/immediate, activation/deferred} +% The two subkeys of |/advice/activation| install the immediate and the +% deferred activation code into the installation keypath. They are invoked +% by \meta{installation keypath}|/activation=|\meta{type}. +% +% Under the deferred activation regime, the commands are not (de)activated +% right away. Rather, the (de)activation calls are collected in style +% |activate deferred|, which should be executed by the installation keypath +% owner, if and when they so desire. (Be sure to switch to +% |activation=immediate| before executing |activate deferred|, otherwise the +% activation will only be deferred once again.) +\pgfkeys{ + /advice/activation/deferred/.style={ + #1/activate/.style={% + activate deferred/.append style={#1/activate={##1}}}, + #1/deactivate/.style={% + activate deferred/.append style={#1/deactivate={##1}}}, + #1/force activate/.style={% + activate deferred/.append style={#1/force activate={##1}}}, + #1/try activate/.style={% + activate deferred/.append style={#1/try activate={##1}}}, + }, +% \end{advicekey} +% +% \begin{key}{activate, deactivate, force activate, try activate} +% The ``real,'' immediate |activate| and |deactivate| take a comma-separated +% list of commands or environments and (de)activate them. If |try activate| +% is in effect, no error is thrown upon failure. If |force activate| is in +% effect, activation proceeds even if we already had the original definition; +% it does not apply to deactivation. These conditionals are set to false +% after every invocation of key |(de)activate|, so that they only apply to +% the immediately following |(de)activate|. (|#1| below is the +% \meta{namespace}; |##1| is the list of commands to be (de)activated.) + /advice/activation/immediate/.style={ + #1/activate/.code={% + \forcsvlist{\advice@activate{#1}}{##1}% + \advice@activate@forcefalse + \advice@activate@tryfalse + }, + #1/deactivate/.code={% + \forcsvlist{\advice@deactivate{#1}}{##1}% + \advice@activate@forcefalse + \advice@activate@tryfalse + }, + #1/force activate/.is if=advice@activate@force, + #1/try activate/.is if=advice@activate@try, + }, +} +\newif\ifadvice@activate@force +\newif\ifadvice@activate@try +% \end{key} +% +% \begin{macro}{\advice@original@csname,\advice@original@cs,\AdviceGetOriginal} +% Activation replaces the original meaning of the handled command with our +% definition. We store the original definition into control sequence +% |\advice@o|\meta{namespace}|//|\meta{storage name} (with a |\string|ified +% \meta{storage name}). Internally, during (de)activation and handling, we +% access it using |\advice@original@csname| and |\advice@original@cs|. Publicly +% it should always be accessed by |\AdviceGetOriginal|, which returns the +% argument control sequence if that control sequence is not handled. +% +% Using the internal command outside the handling context, we could fall +% victim to scenario such as the following. When we memoize something +% containing a |\label|, the produced cc-memo contains code eventually +% executing the original |\label|. If we called the original |\label| via +% the internal macro there, and the user |deactivate|d |\label| on a +% subsequent compilation, the cc-memo would not call |\label| anymore, but +% |\relax|, resulting in a silent error. Using |\AdviceGetOriginal|, the +% original |\label| will be executed even when not activated. +% +% However, not all is bright with |\AdviceGetOriginal|. Given an activated +% control sequence (|#2|), a typo in the namespace argument (|#1|) will lead +% to an infinite loop upon the execution of |\AdviceGetOriginal|. In the +% manual, we recommend defining a namespace-specific macro to avoid such +% typos. +\def\advice@original@csname#1#2{advice@o#1//\string#2} +\def\advice@original@cs#1#2{\csname advice@o#1//\string#2\endcsname} +\def\AdviceGetOriginal#1#2{% + \ifcsname advice@o#1//\string#2\endcsname + \csname advice@o#1//\string#2\expandafter\endcsname + \else + \expandafter#2% + \fi +} +% \end{macro} +% +% \begin{macro}{\advice@activate,\advice@deactivate} +% These macros execute either the command, or the environment (de)activator. +\def\advice@activate#1#2{% + \collargs@cs@cases{#2}% + {\advice@activate@cmd{#1}{#2}}% + {\advice@error@activate@notcsorenv{}{#1}}% + {\advice@activate@env{#1}{#2}}% +} +\def\advice@deactivate#1#2{% + \collargs@cs@cases{#2}% + {\advice@deactivate@cmd{#1}{#2}}% + {\advice@error@activate@notcsorenv{de}{#1}}% + {\advice@deactivate@env{#1}{#2}}% +} +% \end{macro} +% +% \begin{macro}{\advice@activate@cmd} +% We are very careful when we're activating a command, because activating +% means rewriting its original definition. Configuration by |auto| did not +% touch the original command; activation will. So, the leitmotif of this +% macro: safety first. (|#1| is the namespace, and |#2| is the command to be +% activated.) +\def\advice@activate@cmd#1#2{% + % Is the command defined? + \ifdef{#2}{% + % Yes, the command is defined. Let's see if it's safe to activate it. + % We'll do this by checking whether we have its original definition in our + % storage. If we do, this means that we have already activated the + % command. Activating it twice would lead to the loss of the original + % definition (because the second activation would store our own + % redefinition as the original definition) and consequently an infinite + % loop (because once --- well, if --- the handler tries to invoke the + % original command, it will execute itself all over). + \ifcsdef{\advice@original@csname{#1}{#2}}{% + % Yes, we have the original definition, so the safety check failed, and + % we shouldn't activate again. Unless \dots\ how does its current + % definition look like? + \advice@if@our@definition{#1}{#2}{% + % Well, the current definition of the command matches what we would put + % there ourselves. The command is definitely activated, and we refuse + % to activate again, as that would destroy the original definition. + \advice@activate@error@activated{#1}{#2}{Command}{already}% + }{% + % We don't recognize the current definition as our own code (despite + % the fact that we have surely activated the commmand before, given the + % result of the first safety check). It appears that someone else was + % playing fast and loose with the same command, and redefined it after + % our activation. (In fact, if that someone else was another instance + % of Advice, from another namespace, forcing the activation will result + % in the loss of the original definition and the infinite loop.) So it + % \emph{should} be safe to activate it (again) \dots\ but we won't do + % it unless the user specifically requested this using |force + % activate|. Note that without |force activate|, we would be stuck in + % this branch, as we could neither activate (again) nor deactivate the + % command. + \ifadvice@activate@force + \advice@activate@cmd@do{#1}{#2}% + \else + \advice@activate@error@activated{#1}{#2}{Command}{already}% + \fi + }% + }{% + % No, we don't have the command's original definition, so it was not yet + % activated, and we may activate it. + \advice@activate@cmd@do{#1}{#2}% + }% + }{% + \advice@activate@error@undefined{#1}{#2}{Command}{}% + }% +} +% \end{macro} +% +% \begin{macro}{\advice@deactivate@cmd} +% The deactivation of a command follows the same template as activation, but +% with a different logic, and of course a different effect. In order to +% deactivate a command, both safety checks discussed above must be satisfied: +% we must have the command's original definition, \emph{and} our redefinition +% must still reside in the command's control sequence --- the latter +% condition prevents overwriting someone else's redefinition with the +% original command. As both conditions must be unavoidably fulfilled, |force +% activate| has no effect in deactivation (but |try activate| has). +\def\advice@deactivate@cmd#1#2{% + \ifdef{#2}{% + \ifcsdef{\advice@original@csname{#1}{#2}}{% + \advice@if@our@definition{#1}{#2}{% + \advice@deactivate@cmd@do{#1}{#2}% + }{% + \advice@deactivate@error@changed{#1}{#2}% + }% + }{% + \advice@activate@error@activated{#1}{#2}{Command}{not yet}% + }% + }{% + \advice@activate@error@undefined{#1}{#2}{Command}{de}% + }% +} +% \end{macro} +% +% \begin{macro}{\advice@if@our@definition} +% This macro checks whether control sequence |#2| was already activated (in +% namespace |#1|) in the sense that its current definition contains the code +% our activation would put there: |\advice@handle{#1}{#2}| (protected). +\def\advice@if@our@definition#1#2{% + \protected\def\advice@temp{\advice@handle{#1}{#2}}% + \ifx#2\advice@temp + \expandafter\@firstoftwo + \else + \expandafter\@secondoftwo + \fi +} +% \end{macro} +% +% \begin{macro}{\advice@activate@cmd@do} +% This macro saves the original command, and redefines its control sequence. +% Our redefinition must be |\protected| --- even if the original command +% wasn't fragile, our replacement certainly is. (Note that as we require +% \hologo{eTeX} anyway, we don't have to pay attention to \hologo{LaTeX}'s +% robust commands by redefining their ``inner'' command. Protecting our +% replacement suffices.) +\def\advice@activate@cmd@do#1#2{% + \cslet{\advice@original@csname{#1}{#2}}#2% + \protected\def#2{\advice@handle{#1}{#2}}% + \PackageInfo{advice (#1)}{Activated command "\string#2"}% +} +% \end{macro} +% +% \begin{macro}{\advice@deactivate@cmd@do} +% This macro restores the original command, and removes its definition from +% our storage --- this also serves as a signal that the command is not +% activated anymore. +\def\advice@deactivate@cmd@do#1#2{% + \letcs#2{\advice@original@csname{#1}{#2}}% + \csundef{\advice@original@csname{#1}{#2}}% + \PackageInfo{advice (#1)}{Deactivated command "\string#2"}% +} +% \end{macro} +% +% +% \subsubsection{Executing a handled command} +% \label{sec:code:advice:handle} +% +% \begin{macro}{\advice@handle} +% An invocation of this macro is what replaces the original command and runs +% the whole shebang. The system is designed to bail out as quickly as +% necessary if the run conditions are not met (plus \hologo{LaTeX}'s |\begin| +% will receive a very special treatment for this reason). +% +% We first check the run conditions, and bail out if they are not satisfied. +% Note that only the stage-one config is loaded at this point. It sets up +% the following macros (while they are public, neither the end user not the +% installation keypath owner should ever have to use them): +% +% \begin{itemize} +% \item \PrintMainMacro{\AdviceRunConditions} executes |\AdviceRuntrue| if the +% command should be handled; set by |run conditions|. +% \item \PrintMainMacro{\AdviceBailoutHandler} will be executed if the +% command will not be handled, after all; set by |bailout handler|. +% \end{itemize} +\def\advice@handle#1#2{% + \advice@init@i{#1}{#2}% + \AdviceRunfalse + \AdviceRunConditions + \advice@handle@rc{#1}{#2}% +} +% \end{macro} +% +% \begin{macro}{\advice@handle@rc} +% We continue the handling in a new macro, because this is the point where +% the handler for |\begin| will hack into the regular flow of events. +\def\advice@handle@rc#1#2{% + \ifAdviceRun + \expandafter\advice@handle@outer + \else + % Bailout is simple: we first execute the handler, and then the original command. + \AdviceBailoutHandler + \expandafter\advice@original@cs + \fi + {#1}{#2}% +} +% \end{macro} +% +% \begin{macro}{\advice@handle@outer} +% To actually handle the command, we first setup some macros: +% +% \begin{itemize} +% \item \PrintMainMacro{\AdviceNamespace} holds the installation keypath / +% storage name space. +% \item \PrintMainMacro{\AdviceName} holds the control sequence of the handled +% command, or the environment name. +% \item \PrintMainMacro{\AdviceReplaced} holds the ``substituted'' code. For +% commands, this is the same as |\AdviceName|. For environment |foo|, it +% equals |\begin{foo}| in \hologo{LaTeX}, |\foo| in \hologo{plainTeX} and +% |\startfoo| in \hologo{ConTeXt}. +% \item \PrintMainMacro{\AdviceOriginal} executes the original definition of +% the handled command or environment. +% \end{itemize} +\def\advice@handle@outer#1#2{% + \def\AdviceNamespace{#1}% + \def\AdviceName{#2}% + \let\AdviceReplaced\AdviceName + \def\AdviceOriginal{\AdviceGetOriginal{#1}{#2}}% + % We then load the stage-two settings. This defines the following macros: + % \begin{itemize} + % \item \PrintMainMacro{\AdviceOuterHandler} will effectively replace the + % command, if it will be handled; set by |outer handler|. + % \item \PrintMainMacro{\AdviceCollector} collects the arguments of the handled + % command, perhaps consulting |\AdviceArgs| to learn about its argument + % structure. + % \item \PrintMainMacro{\AdviceRawCollectorOptions} contains the options which + % will be passed to the argument collector, in the ``raw'' format. + % \item \PrintMainMacro{\AdviceCollectorOptions} contains the additional, + % user-specified options which will be passed to the argument collector. + % \item \PrintMainMacro{\AdviceArgs} contains the |xparse|-style argument + % specification of the command, or equals |\advice@noargs| to signal that + % command was defined using |xparse| and that the argument specification + % should be retrieved automatically. + % \item \PrintMainMacro{\AdviceInnerHandler} is called by the argument + % collector once it finishes its work. It receives all the collected + % arguments as a single (braced) argument. + % \item \PrintMainMacro{\AdviceOptions} holds options which may be used by the + % outer or the inner handler; Advice does not need or touch + % them. + % \end{itemize} + \advice@init@I{#1}{#2}% + % All prepared, we execute the outer handler. + \AdviceOuterHandler +} +% \end{macro} +% +% \begin{macro}{\ifAdviceRun} +% This conditional is set by the run conditions macro to signal whether we +% should run the outer (true) or the bailout (false) handler. +\newif\ifAdviceRun +% \end{macro} +% +% \begin{macro}{\advice@default@outer@handler} +% The default outer handler merely executes the argument collector. Note +% that it works for both commands and environments. +\def\advice@default@outer@handler{% + \AdviceCollector +} +% \end{macro} +% +% \begin{macro}{\advice@CollectArgumentsRaw} +% This is the default collector, which will collect the argument using +% CollArgs' command |\CollectArgumentsRaw|. It will provide that command +% with: +% \begin{itemize} +% \item the collector options, given in the raw format: the caller +% (|\collargsCaller|), the raw options (|\AdviceRawCollectorOptions|) and the +% user options (|\AdviceRawCollectorOptions|, wrapped in |\collargsSet|; +% \item the argument specification |\AdviceArgs| of the handled command; and +% \item the inner handler |\AdviceInnerHandler| to execute after collecting the +% arguments; the inner handler receives the collected arguments as a single +% braced argument. +% \end{itemize} +% If the argument specification is not defined (either the user did not set +% it, or has reset it by writing |args| without a value), it is assumed that +% the handled command was defined by |xparse| and |\AdviceArgs| will be +% retrieved by |\GetDocumentCommandArgSpec|. +% \begin{listingregion}{_advice-CollectArgumentsRaw.tex} +\def\advice@CollectArgumentsRaw{% + \AdviceIfArgs{}{% + \expandafter\GetDocumentCommandArgSpec\expandafter{\AdviceName}% + \let\AdviceArgs\ArgumentSpecification + }% + \expanded{% + \noexpand\CollectArgumentsRaw{% + \noexpand\collargsCaller{\expandonce\AdviceName}% + \expandonce\AdviceRawCollectorOptions + \ifdefempty\AdviceCollectorOptions{}{% + \noexpand\collargsSet{\expandonce\AdviceCollectorOptions}% + }% + }% + {\expandonce\AdviceArgs}% + {\expandonce\AdviceInnerHandler}% + }% +} +% \end{listingregion} +% \end{macro} +% +% \begin{macro}{\AdviceIfArgs} +% If the value of |args| is ``real'', i.e.\ an |xparse| argument +% specification, execute the first argument. If |args| was set to the +% special value |\advice@noargs|, signaling a command defined by +% |\NewDocumentCommand| or friends, execute the second argument. (Ok, in +% reality anything other than |\advice@noargs| counts as real ``real''.) +\def\advice@noargs@text{\advice@noargs} +\def\AdviceIfArgs{% + \ifx\AdviceArgs\advice@noargs@text + \expandafter\@secondoftwo + \else + \expandafter\@firstoftwo + \fi +} +% \end{macro} +% +% \begin{macro}{\advice@pgfkeys@collector} +% A |pgfkeys| collector is very simple: the sole argument of the any key +% macro, regardless of the argument structure of the key, is everything up to +% |\pgfeov|. +\def\advice@pgfkeys@collector#1\pgfeov{% + \AdviceInnerHandler{#1}% +} +% \end{macro} +% +% \subsubsection{Environments} +% \label{sec:code:advice:environments} +% +% \begin{macro}{\advice@activate@env,\advice@deactivate@env} +% Things are simple in \hologo{TeX} and \hologo{ConTeXt}, as their environments +% are really commands. So rather than activating environment name |#2|, we +% (de)activate command |\#2| or |\start#2|, depending on the format. +%<*plain,context> +\def\advice@activate@env#1#2{% + \expanded{% + \noexpand\advice@activate@cmd{#1}{\expandonce{\csname + %<context>start% + #2\endcsname}}% + }% +} +\def\advice@deactivate@env#1#2{% + \expanded{% + \noexpand\advice@deactivate@cmd{#1}{\expandonce{\csname + %<context>start% + #2\endcsname}}% + }% +} +%</plain,context> +% +% We activate commands by redefining them; that's the only way to do it. But +% we won't activate a \hologo{LaTeX} environment |foo| by redefining command +% |\foo|, where the user's definition for the start of the environment actually +% resides, as such a redefinition would be executed too late, deep within the +% group opened by |\begin|, following many internal operations and public +% hooks. We handle \hologo{LaTeX} environments by defining an outer handler +% for |\begin| (consequently, \hologo{LaTeX} environment support can be +% (de)activated by the user by saying (|de|)|activate=\begin|), and +% activating an environment will be nothing but setting a mark, by +% defining a dummy control sequence |\advice@original@csname{#1}{#2}|, +% which that handler will inspect. Note that |force activate| has no +% effect here. +%<*latex> +\def\advice@activate@env#1#2{% + \ifcsdef{\advice@original@csname{#1}{#2}}{% + \advice@activate@error@activated{#1}{#2}{Environment}{already}% + }{% + \csdef{\advice@original@csname{#1}{#2}}{}% + }% +} +\def\advice@deactivate@env#1#2{% + \ifcsdef{\advice@original@csname{#1}{#2}}{% + \csundef{\advice@original@csname{#1}{#2}}{}% + }{% + \advice@activate@error@activated{#1}{#2}{Environment}{not yet}% + }% +} +% \end{macro} +% +% \begin{macro}{\advice@begin@rc} +% This is the handler for |\begin|. It is very special, for speed. It is +% meant to be declared as the run conditions component, and it hacks into +% the normal flow of handling. It knows that after executing the run +% conditions macro, |\advice@handle| eventually (the tracing info may +% interrupt here as |#1|) continues by +% |\advice@handle@rc|\marg{namespace}\marg{handled control sequence}, so it +% grabs all these (|#2| is the \meta{namespace} and |#3| is the +% \meta{handled control sequence}, i.e.\ |\begin|) plus the environment +% name (|#4|). +\def\advice@begin@rc#1\advice@handle@rc#2#3#4{% + % We check whether environment |#4| is activated (in namespace |#2|) by + % inspecting whether activation dummy is defined. If it is not, we execute + % the original |\begin| (|\advice@original@cs{#2}{#3}|), followed by the + % environment name (|#4|). Note that we \emph{don't} execute the + % environment's bailout handler here: we haven't checked its run conditions + % yet, as the environment is simply not activated. + \ifcsname\advice@original@csname{#2}{#4}\endcsname + \expandafter\advice@begin@env@rc + \else + \expandafter\advice@original@cs + \fi + {#2}{#3}{#4}% +} +% \end{macro} +% +% \begin{macro}{\advice@begin@env@rc} +% Starting from this point, we essentially replicate the workings of +% |\advice@handle|, adapted to \hologo{LaTeX} environments. +\def\advice@begin@env@rc#1#2#3{% + % We first load the stage-one configuration for environment |#3| in namespace + % |#1|. + \advice@init@i{#1}{#3}% + % This defined |\AdviceRunConditions| for the environment. We can now check its + % run conditions. If they are not satisfied, we bail out by executing the + % environment's bailout handler followed by the original |\begin| + % (|\advice@original@cs{#1}{#2}|) plus the environment name (|#3|). + \AdviceRunConditions + \ifAdviceRun + \expandafter\advice@begin@env@outer + \else + \AdviceBailoutHandler + \expandafter\advice@original@cs + \fi + {#1}{#2}{#3}% +} +% \end{macro} +% +% \begin{macro}{\advice@begin@env@outer} +% We define the macros expected by the outer handler, see +% |\advice@handle@outer|, load the second-stage configuration, and execute the +% environment's outer handler. +\def\advice@begin@env@outer#1#2#3{% + \def\AdviceNamespace{#1}% + \def\AdviceName{#3}% + \def\AdviceReplaced{#2{#3}}% + \def\AdviceOriginal{\AdviceGetOriginal{#1}{#2}{#3}}% + \advice@init@I{#1}{#3}% + \AdviceOuterHandler +} +%</latex> +% \end{macro} +% +% +% \subsubsection{Error messages} +% +% Define error messages for the entire package. Note that +% |\advice@(de)activate@error@...| implement |try activate|. +% +\def\advice@activate@error@activated#1#2#3#4{% + \ifadvice@activate@try + \else + \PackageError{advice (#1)}{#3 "\string#2" is #4 activated}{}% + \fi +} +\def\advice@activate@error@undefined#1#2#3#4{% + \ifadvice@activate@try + \else + \PackageError{advice (#1)}{% + #3 "\string#2" you are trying to #4activate is not defined}{}% + \fi +} +\def\advice@deactivate@error@changed#1#2{% + \ifadvice@activate@try + \else + \PackageError{advice (#1)}{The definition of "\string#2" has changed since we + have activated it. Has somebody overridden our command?}{If you have tried + to deactivate so that you could immediately reactivate, you may want to try + "force activate".}% + \fi +} +\def\advice@error@advice@notcs#1#2{% + \PackageError{advice}{The first argument of key "#1" should be either a single + control sequence or an environment name, not "#2"}{}% +} +\def\advice@error@activate@notcsorenv#1#2{% + \PackageError{advice}{Each item in the value of key "#1activate" should be + either a control sequence or an environment name, not "#2".}{}% +} +\def\advice@error@storecs@notcs#1#2{% + \PackageError{advice}{The value of key "#1" should be a single control sequence, + not "\string#2"}{}% +} +\def\advice@error@noinnerhandler#1{% + \PackageError{advice (\AdviceNamespace)}{The inner handler for + "\expandafter\string\AdviceName" is not defined}{}% +} +% \subsubsection{Tracing} +% +% \begin{macro}{\AdviceTracingOn,\AdviceTracingOff} +% We implement tracing by adding the tracing information to the handlers +% after we load them. So it is the handlers themselves which, if and when +% they are executed, will print out that this is happening. +\def\AdviceTracingOn{% + \let\advice@init@i\advice@trace@init@i + \let\advice@init@I\advice@trace@init@I +} +\def\AdviceTracingOff{% + \let\advice@init@i\advice@setup@init@i + \let\advice@init@I\advice@setup@init@I +} +% \end{macro} +\def\advice@trace#1{\immediate\write16{[tracing advice] #1}} +\def\advice@trace@init@i#1#2{% + \advice@trace{Advising \detokenize\expandafter{\string#2} (\detokenize{#1})}% + \advice@setup@init@i{#1}{#2}% + \edef\AdviceRunConditions{% + % We first execute the original run conditions, so that we can show the + % result. + \expandonce\AdviceRunConditions + \noexpand\advice@trace{\space\space + Executing run conditions: + \detokenize\expandafter{\AdviceRunConditions} + --> + \noexpand\ifAdviceRun true\noexpand\else false\noexpand\fi + }% + }% + \edef\AdviceBailoutHandler{% + \noexpand\advice@trace{\space\space + Executing bailout handler: + \detokenize\expandafter{\AdviceBailoutHandler}}% + \expandonce\AdviceBailoutHandler + }% +} +\def\advice@trace@init@I#1#2{% + \advice@setup@init@I{#1}{#2}% + \edef\AdviceOuterHandler{% + \noexpand\advice@trace{\space\space + Executing outer handler: + \detokenize\expandafter{\AdviceOuterHandler}}% + \expandonce\AdviceOuterHandler + }% + \edef\AdviceCollector{% + \noexpand\advice@trace{\space\space + Executing collector: + \detokenize\expandafter{\AdviceCollector}}% + \noexpand\advice@trace{\space\space\space\space + argument specification: + \detokenize\expandafter{\AdviceArgs}}% + \noexpand\advice@trace{\space\space\space\space + options: + \detokenize\expandafter{\AdviceCollectorOptions}}% + \noexpand\advice@trace{\space\space\space\space + raw options: + \detokenize\expandafter{\AdviceRawCollectorOptions}}% + \expandonce\AdviceCollector + }% + % The tracing inner handler must grab the provided argument, if it's to show + % what it is. + \edef\advice@inner@handler@trace##1{% + \noexpand\advice@trace{\space\space + Executing inner handler: + \detokenize\expandafter{\AdviceInnerHandler}}% + \noexpand\advice@trace{\space\space\space\space + Received arguments: + \noexpand\detokenize{##1}}% + \noexpand\advice@trace{\space\space\space\space + options: + \detokenize\expandafter{\AdviceOptions}}% + \expandonce{\AdviceInnerHandler}{##1}% + }% + \def\AdviceInnerHandler{\advice@inner@handler@trace}% +} +%<plain>\resetatcatcode +%<context>\stopmodule +%<context>\protect +%</main> +% +% \subsubsection{The \TikZ; collector} +% \label{sec:code:advice:tikz} +% +% In this section, we implement the argument collector for command |\tikz|, +% which has idiosyncratic syntax, see \PGFmanual{12.2.2}: +% \begin{itemize} +% \item |\tikz|\meta{animation spec}|[|\meta{options}|]{|\meta{picture code}|}| +% \item |\tikz|\meta{animation spec}|[|\meta{options}|]|\meta{picture command}|;| +% \end{itemize} +% where \meta{animation spec} = +% (|:|\meta{key}|={|\meta{value}|}|)*. +% +% The \TikZ; code resides in a special file. It is meant to be |\input| at any +% time, so we need to temporarily assign |@| category code 11. +%<*tikz> +\edef\mmzresetatcatcode{\catcode`\noexpand\@\the\catcode`\@\relax}% +\catcode`\@=11 +\def\AdviceCollectTikZArguments{% +% We initialize the token register which will hold the collected arguments, and +% start the collection. Nothing of note happens until \dots + \mmz@temptoks={}% + \mmz@tikz@anim +} +\def\mmz@tikz@anim{% + \pgfutil@ifnextchar[{\mmz@tikz@opt}{% + \pgfutil@ifnextchar:{\mmz@tikz@anim@a}{% + \mmz@tikz@code}}%] +} +\def\mmz@tikz@anim@a#1=#2{% + \toksapp\mmz@temptoks{#1={#2}}% + \mmz@tikz@anim +} +\def\mmz@tikz@opt[#1]{% + \toksapp\mmz@temptoks{[#1]}% + \mmz@tikz@code +} +\def\mmz@tikz@code{% + \pgfutil@ifnextchar\bgroup\mmz@tikz@braced\mmz@tikz@single +} +\long\def\mmz@tikz@braced#1{\toksapp\mmz@temptoks{{#1}}\mmz@tikz@done} +\def\mmz@tikz@single#1;{\toksapp\mmz@temptoks{#1;}\mmz@tikz@done} +% \dots\ we finish collecting the arguments, when we execute the inner handler, +% with the (braced) collected arguments is its sole argument. +\def\mmz@tikz@done{% + \expandafter\AdviceInnerHandler\expandafter{\the\mmz@temptoks}% +} +\mmzresetatcatcode +%</tikz> +% \end{macrocode} +% +% Local Variables: +% TeX-engine: luatex +% TeX-master: "doc/memoize-code.tex" +% TeX-auto-save: nil +% End:
\ No newline at end of file diff --git a/macros/generic/advice/advice.ins b/macros/generic/advice/advice.ins new file mode 100644 index 0000000000..b1b1df68ce --- /dev/null +++ b/macros/generic/advice/advice.ins @@ -0,0 +1,33 @@ +%% advice.ins +%% +%% This file is a part of Advice, a TeX package implementing a generic +%% framework for extending the functionality of selected commands and +%% environments, available at https://ctan.org/pkg/advice and +%% https://github.com/sasozivanovic/advice. +%% +%% Copyright (c) 2023- Saso Zivanovic <saso.zivanovic@guest.arnes.si> +%% (Sa\v{s}o \v{Z}ivanovi\'{c}) +%% +%% This work may be distributed and/or modified under the conditions of the +%% LaTeX Project Public License, either version 1.3c of this license or (at +%% your option) any later version. The latest version of this license is in +%% https://www.latex-project.org/lppl.txt and version 1.3c or later is part of +%% all distributions of LaTeX version 2008 or later. +%% +%% This work has the LPPL maintenance status `maintained'. +%% The Current Maintainer of this work is Saso Zivanovic. +%% +%% The files belonging to this work and covered by LPPL are listed in `FILES`. +%% +\input docstrip.tex +\keepsilent +\preamble +\endpreamble +\askforoverwritefalse +\generate{% + \file{advice.sty}{\from{advice.dtx}{main,latex}}% + \file{advice.tex}{\from{advice.dtx}{main,plain}}% + \file{t-advice.tex}{\from{advice.dtx}{main,context}}% + \file{advice-tikz.code.tex}{\from{advice.dtx}{tikz}}% +} +\endbatchfile diff --git a/macros/generic/collargs/INSTALL.md b/macros/generic/collargs/INSTALL.md new file mode 100644 index 0000000000..df93bc8214 --- /dev/null +++ b/macros/generic/collargs/INSTALL.md @@ -0,0 +1,68 @@ +# Installation from the TDS archive + +If CollArgs is not (yet) offered by your TeX distribution, the easiest way to +install it is by downloading the TDS archive `collargs.tds.zip` from +[CollArgs's CTAN page](https://ctan.org/pkg/collargs), and unpacking it into +your `texmf` directory. + +Read on only if you have an unstoppable urge to install from source and/or +compile the manual or the documented source code. + +# Installation from the source + +## Getting the sources + +There are several options: + +* Download and unpack the zip archive of the package from [CollArgs's CTAN + page](https://ctan.org/pkg/collargs). + +* Download and unpack the TDS archive, or copy the files from your local + distribution. The sources reside in `<texmf>/source/generic/collargs`. + +* Clone the [GitHub repository of + Memoize](https://github.com/sasozivanovic/memoize). + +## Generating the runtime files + +The easiest way to generate the runtime files is by running `make`. The +following command will generate runtime files for all supported formats +(currently: LaTeX, plain TeX and ConTeXt). + +``` +make collargs.sty +``` + +Alternatively, you can generate these files manually. The source of this +package was written using [EasyDTX](https://ctan.org/pkg/easydtx). Therefore, +you first have to convert the `.edtx` file into a regular `.dtx`: + +``` +edtx2dtx collargs.edtx > collargs.dtx +``` + +The next step is standard. Produce the runtime files by compiling the +installation file: + +``` +tex collargs.ins +``` + +Finally, if you require the ConTeXt runtime, replace all instances of +`\expanded` and `\unexpanded` in `t-collargs.tex` by `\normalexpanded` and +`\normalunexpanded`, respectively. One way to do this is: + +``` +sed -i -s -e 's/\\\(un\)\?expanded/\\normal\1expanded/g;' t-collargs.tex +``` + +## Installation + +It is recommended to install the files into a TDS-compliant `texmf` directory, +as usual. Inspect file `FILES` or the TDS archive `collargs.tds.zip` to see +what goes where. + +# Compiling the documentation + +The documentation of this package is integrated into the documentation of +[Memoize](https://ctan.org/pkg/memoize), please continue there. diff --git a/macros/generic/collargs/LICENCE b/macros/generic/collargs/LICENCE new file mode 100644 index 0000000000..1b57559a83 --- /dev/null +++ b/macros/generic/collargs/LICENCE @@ -0,0 +1,416 @@ +The LaTeX Project Public License +=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +LPPL Version 1.3c 2008-05-04 + +Copyright 1999 2002-2008 LaTeX3 Project + Everyone is allowed to distribute verbatim copies of this + license document, but modification of it is not allowed. + + +PREAMBLE +======== + +The LaTeX Project Public License (LPPL) is the primary license under +which the LaTeX kernel and the base LaTeX packages are distributed. + +You may use this license for any work of which you hold the copyright +and which you wish to distribute. This license may be particularly +suitable if your work is TeX-related (such as a LaTeX package), but +it is written in such a way that you can use it even if your work is +unrelated to TeX. + +The section `WHETHER AND HOW TO DISTRIBUTE WORKS UNDER THIS LICENSE', +below, gives instructions, examples, and recommendations for authors +who are considering distributing their works under this license. + +This license gives conditions under which a work may be distributed +and modified, as well as conditions under which modified versions of +that work may be distributed. + +We, the LaTeX3 Project, believe that the conditions below give you +the freedom to make and distribute modified versions of your work +that conform with whatever technical specifications you wish while +maintaining the availability, integrity, and reliability of +that work. If you do not see how to achieve your goal while +meeting these conditions, then read the document `cfgguide.tex' +and `modguide.tex' in the base LaTeX distribution for suggestions. + + +DEFINITIONS +=========== + +In this license document the following terms are used: + + `Work' + Any work being distributed under this License. + + `Derived Work' + Any work that under any applicable law is derived from the Work. + + `Modification' + Any procedure that produces a Derived Work under any applicable + law -- for example, the production of a file containing an + original file associated with the Work or a significant portion of + such a file, either verbatim or with modifications and/or + translated into another language. + + `Modify' + To apply any procedure that produces a Derived Work under any + applicable law. + + `Distribution' + Making copies of the Work available from one person to another, in + whole or in part. Distribution includes (but is not limited to) + making any electronic components of the Work accessible by + file transfer protocols such as FTP or HTTP or by shared file + systems such as Sun's Network File System (NFS). + + `Compiled Work' + A version of the Work that has been processed into a form where it + is directly usable on a computer system. This processing may + include using installation facilities provided by the Work, + transformations of the Work, copying of components of the Work, or + other activities. Note that modification of any installation + facilities provided by the Work constitutes modification of the Work. + + `Current Maintainer' + A person or persons nominated as such within the Work. If there is + no such explicit nomination then it is the `Copyright Holder' under + any applicable law. + + `Base Interpreter' + A program or process that is normally needed for running or + interpreting a part or the whole of the Work. + + A Base Interpreter may depend on external components but these + are not considered part of the Base Interpreter provided that each + external component clearly identifies itself whenever it is used + interactively. Unless explicitly specified when applying the + license to the Work, the only applicable Base Interpreter is a + `LaTeX-Format' or in the case of files belonging to the + `LaTeX-format' a program implementing the `TeX language'. + + + +CONDITIONS ON DISTRIBUTION AND MODIFICATION +=========================================== + +1. Activities other than distribution and/or modification of the Work +are not covered by this license; they are outside its scope. In +particular, the act of running the Work is not restricted and no +requirements are made concerning any offers of support for the Work. + +2. You may distribute a complete, unmodified copy of the Work as you +received it. Distribution of only part of the Work is considered +modification of the Work, and no right to distribute such a Derived +Work may be assumed under the terms of this clause. + +3. You may distribute a Compiled Work that has been generated from a +complete, unmodified copy of the Work as distributed under Clause 2 +above, as long as that Compiled Work is distributed in such a way that +the recipients may install the Compiled Work on their system exactly +as it would have been installed if they generated a Compiled Work +directly from the Work. + +4. If you are the Current Maintainer of the Work, you may, without +restriction, modify the Work, thus creating a Derived Work. You may +also distribute the Derived Work without restriction, including +Compiled Works generated from the Derived Work. Derived Works +distributed in this manner by the Current Maintainer are considered to +be updated versions of the Work. + +5. If you are not the Current Maintainer of the Work, you may modify +your copy of the Work, thus creating a Derived Work based on the Work, +and compile this Derived Work, thus creating a Compiled Work based on +the Derived Work. + +6. If you are not the Current Maintainer of the Work, you may +distribute a Derived Work provided the following conditions are met +for every component of the Work unless that component clearly states +in the copyright notice that it is exempt from that condition. Only +the Current Maintainer is allowed to add such statements of exemption +to a component of the Work. + + a. If a component of this Derived Work can be a direct replacement + for a component of the Work when that component is used with the + Base Interpreter, then, wherever this component of the Work + identifies itself to the user when used interactively with that + Base Interpreter, the replacement component of this Derived Work + clearly and unambiguously identifies itself as a modified version + of this component to the user when used interactively with that + Base Interpreter. + + b. Every component of the Derived Work contains prominent notices + detailing the nature of the changes to that component, or a + prominent reference to another file that is distributed as part + of the Derived Work and that contains a complete and accurate log + of the changes. + + c. No information in the Derived Work implies that any persons, + including (but not limited to) the authors of the original version + of the Work, provide any support, including (but not limited to) + the reporting and handling of errors, to recipients of the + Derived Work unless those persons have stated explicitly that + they do provide such support for the Derived Work. + + d. You distribute at least one of the following with the Derived Work: + + 1. A complete, unmodified copy of the Work; + if your distribution of a modified component is made by + offering access to copy the modified component from a + designated place, then offering equivalent access to copy + the Work from the same or some similar place meets this + condition, even though third parties are not compelled to + copy the Work along with the modified component; + + 2. Information that is sufficient to obtain a complete, + unmodified copy of the Work. + +7. If you are not the Current Maintainer of the Work, you may +distribute a Compiled Work generated from a Derived Work, as long as +the Derived Work is distributed to all recipients of the Compiled +Work, and as long as the conditions of Clause 6, above, are met with +regard to the Derived Work. + +8. The conditions above are not intended to prohibit, and hence do not +apply to, the modification, by any method, of any component so that it +becomes identical to an updated version of that component of the Work as +it is distributed by the Current Maintainer under Clause 4, above. + +9. Distribution of the Work or any Derived Work in an alternative +format, where the Work or that Derived Work (in whole or in part) is +then produced by applying some process to that format, does not relax or +nullify any sections of this license as they pertain to the results of +applying that process. + +10. a. A Derived Work may be distributed under a different license + provided that license itself honors the conditions listed in + Clause 6 above, in regard to the Work, though it does not have + to honor the rest of the conditions in this license. + + b. If a Derived Work is distributed under a different license, that + Derived Work must provide sufficient documentation as part of + itself to allow each recipient of that Derived Work to honor the + restrictions in Clause 6 above, concerning changes from the Work. + +11. This license places no restrictions on works that are unrelated to +the Work, nor does this license place any restrictions on aggregating +such works with the Work by any means. + +12. Nothing in this license is intended to, or may be used to, prevent +complete compliance by all parties with all applicable laws. + + +NO WARRANTY +=========== + +There is no warranty for the Work. Except when otherwise stated in +writing, the Copyright Holder provides the Work `as is', without +warranty of any kind, either expressed or implied, including, but not +limited to, the implied warranties of merchantability and fitness for a +particular purpose. The entire risk as to the quality and performance +of the Work is with you. Should the Work prove defective, you assume +the cost of all necessary servicing, repair, or correction. + +In no event unless required by applicable law or agreed to in writing +will The Copyright Holder, or any author named in the components of the +Work, or any other party who may distribute and/or modify the Work as +permitted above, be liable to you for damages, including any general, +special, incidental or consequential damages arising out of any use of +the Work or out of inability to use the Work (including, but not limited +to, loss of data, data being rendered inaccurate, or losses sustained by +anyone as a result of any failure of the Work to operate with any other +programs), even if the Copyright Holder or said author or said other +party has been advised of the possibility of such damages. + + +MAINTENANCE OF THE WORK +======================= + +The Work has the status `author-maintained' if the Copyright Holder +explicitly and prominently states near the primary copyright notice in +the Work that the Work can only be maintained by the Copyright Holder +or simply that it is `author-maintained'. + +The Work has the status `maintained' if there is a Current Maintainer +who has indicated in the Work that they are willing to receive error +reports for the Work (for example, by supplying a valid e-mail +address). It is not required for the Current Maintainer to acknowledge +or act upon these error reports. + +The Work changes from status `maintained' to `unmaintained' if there +is no Current Maintainer, or the person stated to be Current +Maintainer of the work cannot be reached through the indicated means +of communication for a period of six months, and there are no other +significant signs of active maintenance. + +You can become the Current Maintainer of the Work by agreement with +any existing Current Maintainer to take over this role. + +If the Work is unmaintained, you can become the Current Maintainer of +the Work through the following steps: + + 1. Make a reasonable attempt to trace the Current Maintainer (and + the Copyright Holder, if the two differ) through the means of + an Internet or similar search. + + 2. If this search is successful, then enquire whether the Work + is still maintained. + + a. If it is being maintained, then ask the Current Maintainer + to update their communication data within one month. + + b. If the search is unsuccessful or no action to resume active + maintenance is taken by the Current Maintainer, then announce + within the pertinent community your intention to take over + maintenance. (If the Work is a LaTeX work, this could be + done, for example, by posting to comp.text.tex.) + + 3a. If the Current Maintainer is reachable and agrees to pass + maintenance of the Work to you, then this takes effect + immediately upon announcement. + + b. If the Current Maintainer is not reachable and the Copyright + Holder agrees that maintenance of the Work be passed to you, + then this takes effect immediately upon announcement. + + 4. If you make an `intention announcement' as described in 2b. above + and after three months your intention is challenged neither by + the Current Maintainer nor by the Copyright Holder nor by other + people, then you may arrange for the Work to be changed so as + to name you as the (new) Current Maintainer. + + 5. If the previously unreachable Current Maintainer becomes + reachable once more within three months of a change completed + under the terms of 3b) or 4), then that Current Maintainer must + become or remain the Current Maintainer upon request provided + they then update their communication data within one month. + +A change in the Current Maintainer does not, of itself, alter the fact +that the Work is distributed under the LPPL license. + +If you become the Current Maintainer of the Work, you should +immediately provide, within the Work, a prominent and unambiguous +statement of your status as Current Maintainer. You should also +announce your new status to the same pertinent community as +in 2b) above. + + +WHETHER AND HOW TO DISTRIBUTE WORKS UNDER THIS LICENSE +====================================================== + +This section contains important instructions, examples, and +recommendations for authors who are considering distributing their +works under this license. These authors are addressed as `you' in +this section. + +Choosing This License or Another License +---------------------------------------- + +If for any part of your work you want or need to use *distribution* +conditions that differ significantly from those in this license, then +do not refer to this license anywhere in your work but, instead, +distribute your work under a different license. You may use the text +of this license as a model for your own license, but your license +should not refer to the LPPL or otherwise give the impression that +your work is distributed under the LPPL. + +The document `modguide.tex' in the base LaTeX distribution explains +the motivation behind the conditions of this license. It explains, +for example, why distributing LaTeX under the GNU General Public +License (GPL) was considered inappropriate. Even if your work is +unrelated to LaTeX, the discussion in `modguide.tex' may still be +relevant, and authors intending to distribute their works under any +license are encouraged to read it. + +A Recommendation on Modification Without Distribution +----------------------------------------------------- + +It is wise never to modify a component of the Work, even for your own +personal use, without also meeting the above conditions for +distributing the modified component. While you might intend that such +modifications will never be distributed, often this will happen by +accident -- you may forget that you have modified that component; or +it may not occur to you when allowing others to access the modified +version that you are thus distributing it and violating the conditions +of this license in ways that could have legal implications and, worse, +cause problems for the community. It is therefore usually in your +best interest to keep your copy of the Work identical with the public +one. Many works provide ways to control the behavior of that work +without altering any of its licensed components. + +How to Use This License +----------------------- + +To use this license, place in each of the components of your work both +an explicit copyright notice including your name and the year the work +was authored and/or last substantially modified. Include also a +statement that the distribution and/or modification of that +component is constrained by the conditions in this license. + +Here is an example of such a notice and statement: + + %% pig.dtx + %% Copyright 2008 M. Y. Name + % + % This work may be distributed and/or modified under the + % conditions of the LaTeX Project Public License, either version 1.3 + % of this license or (at your option) any later version. + % The latest version of this license is in + % https://www.latex-project.org/lppl.txt + % and version 1.3c or later is part of all distributions of LaTeX + % version 2008 or later. + % + % This work has the LPPL maintenance status `maintained'. + % + % The Current Maintainer of this work is M. Y. Name. + % + % This work consists of the files pig.dtx and pig.ins + % and the derived file pig.sty. + +Given such a notice and statement in a file, the conditions +given in this license document would apply, with the `Work' referring +to the three files `pig.dtx', `pig.ins', and `pig.sty' (the last being +generated from `pig.dtx' using `pig.ins'), the `Base Interpreter' +referring to any `LaTeX-Format', and both `Copyright Holder' and +`Current Maintainer' referring to the person `M. Y. Name'. + +If you do not want the Maintenance section of LPPL to apply to your +Work, change `maintained' above into `author-maintained'. +However, we recommend that you use `maintained', as the Maintenance +section was added in order to ensure that your Work remains useful to +the community even when you can no longer maintain and support it +yourself. + +Derived Works That Are Not Replacements +--------------------------------------- + +Several clauses of the LPPL specify means to provide reliability and +stability for the user community. They therefore concern themselves +with the case that a Derived Work is intended to be used as a +(compatible or incompatible) replacement of the original Work. If +this is not the case (e.g., if a few lines of code are reused for a +completely different task), then clauses 6b and 6d shall not apply. + + +Important Recommendations +------------------------- + + Defining What Constitutes the Work + + The LPPL requires that distributions of the Work contain all the + files of the Work. It is therefore important that you provide a + way for the licensee to determine which files constitute the Work. + This could, for example, be achieved by explicitly listing all the + files of the Work near the copyright notice of each file or by + using a line such as: + + % This work consists of all files listed in manifest.txt. + + in that place. In the absence of an unequivocal list it might be + impossible for the licensee to determine what is considered by you + to comprise the Work and, in such a case, the licensee would be + entitled to make reasonable conjectures as to which files comprise + the Work. + diff --git a/macros/generic/collargs/Makefile b/macros/generic/collargs/Makefile new file mode 100644 index 0000000000..421bcdbfc9 --- /dev/null +++ b/macros/generic/collargs/Makefile @@ -0,0 +1,8 @@ +.PRECIOUS: %.dtx + +%.dtx: %.edtx + edtx2dtx $< > $@ + +%.sty %.tex t-%.tex &: %.ins %.dtx + tex $< + sed -i -s -e 's/\\\(un\)\?expanded/\\normal\1expanded/g;' t-$*.tex diff --git a/macros/generic/collargs/README.md b/macros/generic/collargs/README.md new file mode 100644 index 0000000000..f36abea78a --- /dev/null +++ b/macros/generic/collargs/README.md @@ -0,0 +1,8 @@ +Package CollArgs provides a command which can determine the argument scope of +any command whose argument structure conforms to +[xparse](https://ctan.org/pkg/xparse)'s argument specification. + +This package was developed as an auxiliary package of +[Memoize](https://ctan.org/pkg/memoize). This is why it is, somewhat +unconventionally, documented alongside that package. This applies to both the +manual and the documented code listing. diff --git a/macros/generic/collargs/collargs.edtx b/macros/generic/collargs/collargs.edtx new file mode 100644 index 0000000000..40a102c1a8 --- /dev/null +++ b/macros/generic/collargs/collargs.edtx @@ -0,0 +1,2938 @@ +% \iffalse +% collargs.edtx (this is not a .dtx file; to produce a .dtx, process it with edtx2dtx) +%% +%% This file is a part of CollArgs, a TeX package providing a command which can +%% determine the argument scope of any command whose argument structure +%% conforms to xparse's argument specification, available at +%% https://ctan.org/pkg/advice and https://github.com/sasozivanovic/advice. +%% +%% Copyright (c) 2023- Saso Zivanovic <saso.zivanovic@guest.arnes.si> +%% (Sa\v{s}o \v{Z}ivanovi\'{c}) +%% +%% This work may be distributed and/or modified under the conditions of the +%% LaTeX Project Public License, either version 1.3 of this license or (at your +%% option) any later version. The latest version of this license is in +%% http://www.latex-project.org/lppl.txt and version 1.3 or later is part of +%% all distributions of LaTeX version 2005/12/01 or later. +%% +%% This work has the LPPL maintenance status `maintained'. +%% The Current Maintainer of this work is Saso Zivanovic. +%% +%% The files belonging to this work and covered by LPPL are listed in +%% (<texmf>/doc/generic/collargs/)FILES. +% +% \fi +% +% \begin{macrocode} +% +% Package CollArgs provides commands |\CollectArguments| and +% |\CollectArgumentsRaw|, which (what a surprise!) collect the arguments +% conforming to the given (slightly extended) |xparse| argument specification. +% The package was developed to help out with automemoization (see +% section~\ref{sec:code:automemoization}). It started out as a few lines of +% code, but had grown once I realized I want automemoization to work for +% verbatim environments as well --- the environment-collecting code is based on +% Bruno Le Floch's package |cprotect| --- and had then grown some more once I +% decided to support the |xparse| argument specification in full detail, and to +% make the verbatim mode flexible enough to deal with a variety of situations. +% +% The implementation of this package does not depend on |xparse|. Perhaps this +% is a mistake, especially as the |xparse| code is now included in the base +% \hologo{LaTeX}, but the idea was to have a light-weight package (not sure +% this is the case anymore, given all the bells and whistles), to have its +% functionality available in plain \hologo{TeX} and \hologo{ConTeXt} as well +% (same as Memoize), and, perhaps most importantly, to have the ability to +% collect the arguments verbatim. +% +% +% \paragraph{Identification} +%<latex>\ProvidesPackage{collargs}[2023/10/10 v1.0.0 Collect arguments of any command] +%<context>%D \module[ +%<context>%D file=t-collargs.tex, +%<context>%D version=1.0.0, +%<context>%D title=CollArgs, +%<context>%D subtitle=Collect arguments of any command, +%<context>%D author=Saso Zivanovic, +%<context>%D date=2023-10-10, +%<context>%D copyright=Saso Zivanovic, +%<context>%D license=LPPL, +%<context>%D ] +%<context>\writestatus{loading}{ConTeXt User Module / collargs} +%<context>\unprotect +%<context>\startmodule[collargs] +% \paragraph{Required packages} +%<latex>\RequirePackage{pgfkeys} +%<plain>\input pgfkeys +%<context>\input t-pgfkey +%<latex>\RequirePackage{etoolbox} +%<plain,context>\input etoolbox-generic +%<plain>\edef\resetatcatcode{\catcode`\noexpand\@\the\catcode`\@\relax} +%<plain>\catcode`\@11\relax +% +% \begin{macro}{\toksapp,\gtoksapp,\etoksapp,\xtoksapp} +% Our macros for appending to a token register only accept a control sequence +% defined by |\toksdef| (like |\mytoks|) but not an explicit register +% designation like |\toks0|, so we only define them if noone else did; the +% names of the macros match the \hologo{LuaTeX} primitives, so they surely +% won't be defined there. +\ifdefined\toksapp\else + \long\def\toksapp#1#2{#1\expandafter{\the#1#2}}% +\fi +\ifdefined\etoksapp\else + \long\def\etoksapp#1#2{#1\expandafter{\expanded{\the#1#2}}}% +\fi +\ifdefined\gtoksapp\else + \long\def\gtoksapp#1#2{\global#1\expandafter{\the#1#2}}% +\fi +\ifdefined\xtoksapp\else + \long\def\xtoksapp#1#2{\global#1\expandafter{\expanded{\the#1#2}}}% +\fi +\ifdefined\toks@\else\toksdef\toks@=0 \fi +% \end{macro} +% +% \begin{macro}{\CollectArguments,\CollectArgumentsRaw} +% These are the only public commands provided by the +% package. |\CollectArguments| takes three arguments: the optional |#1| is +% the option list, processed by |pgfkeys| (given the grouping structure, +% these options will apply to all arguments); the mandatory |#2| is the +% |xparse|-style argument specification; the mandatory |#3| is the ``next'' +% command (or a sequence of commands). The argument list is expected to +% start immediately after the final argument; |\CollectArguments| parses it, +% effectively figuring out its extent, and then passes the entire argument +% list to the ``next'' command (as a single argument). +% +% |\CollectArgumentsRaw| differs only in how it takes and processes the +% options. For one, these should be given as a mandatory argument. +% Furthermore, they do not take the form of a keylist, but should deploy the +% ``programmer's interface.'' |#1| should thus be a sequence of invocations +% of the macro counterparts of the keys defined in +% section~\ref{sec:code:collargs:keys}, which can be recognized as starting +% with |\collargs| followed by a capital letter, e.g.\ |\collargsCaller|. +% Note that |\collargsSet| may also be used in |#1|. (The ``optional,'' +% i.e.\ bracketed, argument of |\CollectArgumentsRaw| is in fact mandatory.) +\protected\def\CollectArguments{% + \pgf@keys@utilifnextchar[\CollectArguments@i{\CollectArgumentsRaw{}}%] +} +\def\CollectArguments@i[#1]{\CollectArgumentsRaw{\collargsSet{#1}}} +\protected\def\CollectArgumentsRaw#1#2#3{% + % This group will be closed by |\collargs@.| once we grinded through the + % argument specification. + \begingroup + % Initialize category code fixing; see section~\ref{sec:code:collargs:fix} + % for details. We have to do this before applying the settings, so that + % |\collargsFixFromNoVerbatim| et al can take effect. + \global\let\ifcollargs@last@verbatim\ifcollargs@verbatim + \global\let\ifcollargs@last@verbatimbraces\ifcollargs@verbatimbraces + \global\collargs@double@fixfalse + % Apply the settings. + \collargs@verbatim@wrap{#1}% + % Initialize the space-grabber. + \collargs@init@grabspaces + % Remember the code to execute after collection. + \def\collargs@next{#3}% + % Initialize the token register holding the collected arguments. + \global\collargs@toks{}% + % Execute the central loop macro, which expects the argument specification + % |#2| to be delimited from the following argument tokens by a dot. + \collargs@#2.% +} +% \end{macro} +% +% \begin{macro}{\collargsSet} +% This macro processes the given keys in the |/collargs| keypath. When it is +% used to process options given by the end user (the optional argument to +% |\CollectArguments|, and the options given within the argument +% specification, using the new modifier |&|), its invocation should be +% wrapped in |\collargs@verbatim@wrap| to correctly deal with the changes of +% the verbatim mode. +\def\collargsSet#1{\pgfqkeys{/collargs}{#1}} +% \end{macro} +% +% +% \subsubsection{The keys} +% \label{sec:code:collargs:keys} +% +% \begin{macro}{\collargs@cs@cases} +% If the first argument of this auxiliary macro is a single control sequence, +% then the second argument is executed. If the first argument starts with a +% control sequence but this control sequence does not form the entire +% argument, the third argument is executed. Otherwise, the fourth argument +% is executed. +% +% This macro is defined in package CollArgs because we use it in key |caller| +% below, but it is really useful in package Auto, where having it we don't +% have to bother the end-user with a separate keys for commands and +% environments, but automatically detect whether the argument of |auto| and +% (|de|)|activate| is a command or an environment. +\def\collargs@cs@cases#1{\collargs@cs@cases@i#1\collargs@cs@cases@end} +\let\collargs@cs@cases@end\relax +\def\collargs@cs@cases@i{\futurelet\collargs@temp\collargs@cs@cases@ii} +\def\collargs@cs@cases@ii#1#2\collargs@cs@cases@end{% + \ifcat\noexpand\collargs@temp\relax + \ifx\relax#2\relax + \expandafter\expandafter\expandafter\@firstofthree + \else + \expandafter\expandafter\expandafter\@secondofthree + \fi + \else + \expandafter\@thirdofthree + \fi +} +\def\@firstofthree#1#2#3{#1} +\def\@secondofthree#1#2#3{#2} +\def\@thirdofthree#1#2#3{#3} +% \end{macro} +% +% \begin{collargskey}{caller} +% \begin{macro}{\collargsCaller} +% Every macro which grabs a part of the argument list will be accessed +% through the ``caller'' control sequence, so that \hologo{TeX}'s reports +% of any errors in the argument structure can contain a command name +% familiar to the author.\footnote{The idea is borrowed from package +% |environ|, which is in turn based on code from |amsmath|.} For example, +% if the argument list ``originally'' belonged to command |\foo| with +% argument structure |r()|, but no parentheses follow in the input, we want +% \hologo{TeX} to complain that |Use of \foo doesn't match its definition|. +% This can be achieved by setting |caller=\foo|; the default is +% |caller=\CollectArguments|, which is still better than seeing an error +% involving some random internal control sequence. It is also ok to set an +% environment name as the caller, see below. +% +% The key and macro defined below store the caller control sequence into +% |\collargs@caller|, e.g.\ when we say |caller=\foo|, we effectively +% execute |\def\collargs@caller{\foo}|. +\collargsSet{ + caller/.code={\collargsCaller{#1}}, +} +\def\collargsCaller#1{% + \collargs@cs@cases{#1}{% + \let\collargs@temp\collargs@caller@cs + }{% + \let\collargs@temp\collargs@caller@csandmore + }{% + \let\collargs@temp\collargs@caller@env + }% + \collargs@temp{#1}% +} +\def\collargs@caller@cs#1{% + % If |#1| is a single control sequence, just use that as the caller. + \def\collargs@caller{#1}% +} +\def\collargs@caller@csandmore#1{% + % If |#1| starts with a control sequence, we don't complain, but convert the + % entire |#1| into a control sequence. + \begingroup + \escapechar -1 + \expandafter\endgroup + \expandafter\def\expandafter\collargs@caller\expandafter{% + \csname\string#1\endcsname + }% +} +\def\collargs@caller@env#1{% + % If |#1| does not start with a control sequence, we assume that is an + % environment name, so we prepend |start| in \hologo{ConTeXt}, and dress it + % up into |\begin{#1}| in \hologo{LaTeX}. + \expandafter\def\expandafter\collargs@caller\expandafter{% + \csname + %<context>start% + %<latex>begin{% + #1% + %<latex>}% + \endcsname + }% +} +\collargsCaller\CollectArguments +% \end{macro} +% \end{collargskey} +% +% \begin{macro}{\ifcollargs@verbatim,\ifcollargs@verbatimbraces} +% The first of these conditional +% signals that we're collecting the arguments in one of the verbatim +% modes; the second one signals the |verb| mode in particular. +\newif\ifcollargs@verbatim +\newif\ifcollargs@verbatimbraces +% \end{macro} +% +% \begin{collargskey}{verbatim, verb, no verbatim} +% \begin{macro}{\collargs@verbatim@wrap} +% These keys set the verbatim mode macro which will be executed by +% |\collargsSet| after processing all keys. +% The verbatim mode macros |\collargsVerbatim|, |\collargsVerb| and +% |\collargsNoVerbatim| are somewhat complex; we postpone their definition +% until section~\ref{sec:code:collargs:verbatim}. Their main effect is to +% set conditionals |\ifcollargs@verbatim| and |\ifcollargs@verbatimbraces|, +% which are be inspected by the argument type handlers --- and to make +% the requested category code changes, of course. +% +% Here, note that the verbatim-selection code is not executed while the +% keylist is being processed. Rather, the verbatim keys simply set the macro +% which will be executed \emph{after} the keylist is processed, and this is +% why processing of a keylist given by the user must be always wrapped in +% |\collargs@verbatim@wrap|. +\collargsSet{ + verbatim/.code={\let\collargs@apply@verbatim\collargsVerbatim}, + verb/.code={\let\collargs@apply@verbatim\collargsVerb}, + no verbatim/.code={\let\collargs@apply@verbatim\collargsNoVerbatim}, +} +\def\collargs@verbatim@wrap#1{% + \let\collargs@apply@verbatim\relax + #1% + \collargs@apply@verbatim +} +% \end{macro} +% \end{collargskey} +% +% \begin{collargskey}{fix from verbatim, fix from verb, fix from no verbatim} +% \begin{macro}{\collargsFixFromVerbatim,\collargsFixFromVerb,\collargsFixFromNoVerbatim} +% These keys and macros should be used to request a category code fix, when +% the offending tokenization took place prior to invoking +% |\CollectArguments|; see section~\ref{sec:code:collargs:fix} for details. +% While I assume that only |\collargsFixFromNoVerbatim| will ever be used +% (and it is used by |\mmz|), we provide macros for all three transitions, +% for completeness.\indentmacrocode +\collargsSet{ + fix from verbatim/.code={\collargsFixFromVerbatim}, + fix from verb/.code={\collargsFixFromVerb}, + fix from no verbatim/.code={\collargsFixFromNoVerbatim}, +} +% \noindentmacrocode +\def\collargsFixFromNoVerbatim{% + \global\collargs@fix@requestedtrue + \global\let\ifcollargs@last@verbatim\iffalse +} +\def\collargsFixFromVerbatim{% + \global\collargs@fix@requestedtrue + \global\let\ifcollargs@last@verbatim\iftrue + \global\let\ifcollargs@last@verbatimbraces\iftrue +} +\def\collargsFixFromVerb{% + \global\collargs@fix@requestedtrue + \global\let\ifcollargs@last@verbatim\iftrue + \global\let\ifcollargs@last@verbatimbraces\iffalse +} +% \end{macro} +% \end{collargskey} +% +% \begin{collargskey}{braces} +% Set the characters which are used as the grouping characters in the full +% verbatim mode. The user is only required to do this when multiple +% character pairs serve as the grouping characters. The underlying macro, +% |\collargsBraces|, will be defined in +% section~\ref{sec:code:collargs:verbatim}. +\collargsSet{ + braces/.code={\collargsBraces{#1}}% +} +% \end{collargskey} +% +% \begin{collargskey}{environment} +% \begin{macro}{\collargsEnvironment} +% Set the environment name. +% \indentmacrocode +\collargsSet{ + environment/.estore in=\collargs@b@envname +} +\def\collargsEnvironment#1{\edef\collargs@b@envname{#1}} +\collargsEnvironment{} +% \end{macro} +% \end{collargskey} +% +% \begin{collargskey}{begin tag, end tag, tags} +% \begin{macro}{\ifcollargsBeginTag,\ifcollargsEndTag,\ifcollargsAddTags} +% When |begin tag|\slash|end tag| is in effect, the begin\slash end-tag will +% be will be prepended/appended to the environment body. |tags| is a +% shortcut for setting |begin tag| and |end tag| simultaneously. +% \indentmacrocode +\collargsSet{ + begin tag/.is if=collargsBeginTag, + end tag/.is if=collargsEndTag, + tags/.style={begin tag=#1, end tag=#1}, + tags/.default=true, +} +\newif\ifcollargsBeginTag +\newif\ifcollargsEndTag +% \end{macro} +% \end{collargskey} +% +% \begin{collargskey}{ignore nesting} +% \begin{macro}{\ifcollargsIgnoreNesting} +% When this key is in effect, we will +% ignore any \cs{begin}\marg{name}s and simply grab everything up to +% the first \cs{end}\marg{name} (again, the markers are automatically +% adapted to the format). +\collargsSet{ + ignore nesting/.is if=collargsIgnoreNesting, +} +\newif\ifcollargsIgnoreNesting +% \end{macro} +% \end{collargskey} +% +% \begin{collargskey}{ignore other tags} +% \begin{macro}{\ifcollargsIgnoreOtherTags} +% This key is only relevant in the +% non-verbatim and partial verbatim modes in \hologo{LaTeX}. When it +% is in effect, CollArgs checks the environment name following each +% |\begin| and |\end|, ignoring the tags with an environment name other +% than |\collargs@b@envname|. +\collargsSet{ + ignore other tags/.is if=collargsIgnoreOtherTags, +} +\newif\ifcollargsIgnoreOtherTags +% \end{macro} +% \end{collargskey} +% +% \begin{collargskey}[noindex]{(append/prepend) (pre/post)processor} +% \begin{macro}[noindex]{\collargs(Append/Prepend)(Pre/Post)processor} +% \begin{collargskey}[noprint]{append preprocessor, prepend preprocessor, +% append postprocessor, prepend postprocessor} +% \begin{macro}[noprint]{\collargsAppendPreprocessor,\collargsPrependPreprocessor,\collargsAppendPostprocessor,\collargsPrependPostprocessor} +% These keys and +% macros populate the list of preprocessors, +% |\collargs@preprocess@arg|, and the list of post-processors, +% |\collargs@postprocess@arg|, executed in |\collargs@appendarg|. +\collargsSet{ + append preprocessor/.code={\collargsAppendPreprocessor{#1}}, + prepend preprocessor/.code={\collargsPrependPreprocessor{#1}}, + append postprocessor/.code={\collargsAppendPostprocessor{#1}}, + prepend postprocessor/.code={\collargsPrependPostprocessor{#1}}, +} +\def\collargsAppendPreprocessor{% + \collargs@addprocessor\appto\collargs@preprocess@arg} +\def\collargsPrependPreprocessor{% + \collargs@addprocessor\preto\collargs@preprocess@arg} +\def\collargsAppendPostprocessor{% + \collargs@addprocessor\appto\collargs@postprocess@arg} +\def\collargsPrependPostprocessor{% + \collargs@addprocessor\preto\collargs@postprocess@arg} +% Here, |#1| will be either |\appto| or |\preto|, and |#2| will be either +% |\collargs@preprocess@arg| or |\collargs@postprocess@arg|. |#3| is the +% processor code. +\def\collargs@addprocessor#1#2#3{% + #1#2{% + \expanded{% + \unexpanded{#3}{\the\collargsArg}% + }% + }% +} +% \end{macro} +% \end{collargskey} +% \end{macro} +% \end{collargskey} +% +% \begin{collargskey}[noindex]{clear (pre/post)processors} +% \begin{macro}[noindex]{\collargsClear(Pre/Post)processors} +% \begin{collargskey}[noprint]{clear preprocessors, clear postprocessors} +% \begin{macro}[noprint]{\collargsClearPreprocessors,\collargsClearPostprocessors} +% These keys and macros +% clear the pre- and post-processor lists, which are initially empty as +% well. +\def\collargs@preprocess@arg{} +\def\collargs@postprocess@arg{} +\collargsSet{ + clear preprocessors/.code={\collargsClearPreprocessors}, + clear postprocessors/.code={\collargsClearPostprocessors}, +} +\def\collargsClearPreprocessors{\def\collargs@preprocess@arg{}}% +\def\collargsClearPostprocessors{\def\collargs@postprocess@arg{}}% +% \end{macro} +% \end{collargskey} +% \end{macro} +% \end{collargskey} +% +% \begin{collargskey}[noindex]{(append/prepend) expandable (pre/post)processor} +% \begin{macro}[noindex]{\collargs(Append/Prepend)Expandable(Pre/Post)processor} +% \begin{collargskey}[noprint]{append expandable preprocessor, prepend expandable preprocessor, append expandable postprocessor, prepend expandable postprocessor} +% \begin{macro}[noprint]{\collargsAppendExpandablePreprocessor,\collargsPrependExpandablePreprocessor,\collargsAppendExpandablePostprocessor,\collargsPrependExpandablePostprocessor} +% These keys +% and macros simplify the definition of fully expandable processors. Note +% that expandable processors are added to the same list as non-expandable +% processors. +\collargsSet{ + append expandable preprocessor/.code={% + \collargsAppendExpandablePreprocessor{#1}}, + prepend expandable preprocessor/.code={% + \collargsPrependExpandablePreprocessor{#1}}, + append expandable postprocessor/.code={% + \collargsAppendExpandablePostprocessor{#1}}, + prepend expandable postprocessor/.code={% + \collargsPrependExpandablePostprocessor{#1}}, +} +\def\collargsAppendExpandablePreprocessor{% + \collargs@addeprocessor\appto\collargs@preprocess@arg} +\def\collargsPrependExpandablePreprocessor{% + \collargs@addeprocessor\preto\collargs@preprocess@arg} +\def\collargsAppendExpandablePostprocessor{% + \collargs@addeprocessor\appto\collargs@postprocess@arg} +\def\collargsPrependExpandablePostprocessor{% + \collargs@addeprocessor\preto\collargs@postprocess@arg} +\def\collargs@addeprocessor#1#2#3{% + #1#2{% + \expanded{% + \edef\noexpand\collargs@temp{\unexpanded{#3}{\the\collargsArg}}% + \unexpanded{\expandafter\collargsArg\expandafter{\collargs@temp}}% + }% + }% +} +% \end{macro} +% \end{collargskey} +% \end{macro} +% \end{collargskey} +% +% \begin{collargskey}[noindex]{(append/prepend) (pre/post)wrap} +% \begin{macro}[noindex]{\collargs(Append/Prepend)(Pre/Post)wrap} +% \begin{collargskey}[noprint]{append prewrap, prepend prewrap, append postwrap, prepend postwrap} +% \begin{macro}[noprint]{\collargsAppendPrewrap,\collargsPrependPrewrap,\collargsAppendPostwrap,\collargsPrependPostwrap} +% These keys and macros +% simplify the definition of processors which yield the result after a +% single expansion. Again, they are added to the same list as other +% processors. +\collargsSet{ + append prewrap/.code={\collargsAppendPrewrap{#1}}, + prepend prewrap/.code={\collargsPrependPrewrap{#1}}, + append postwrap/.code={\collargsAppendPostwrap{#1}}, + prepend postwrap/.code={\collargsPrependPostwrap{#1}}, +} +\def\collargsAppendPrewrap{\collargs@addwrap\appto\collargs@preprocess@arg} +\def\collargsPrependPrewrap{\collargs@addwrap\preto\collargs@preprocess@arg} +\def\collargsAppendPostwrap{\collargs@addwrap\appto\collargs@postprocess@arg} +\def\collargsPrependPostwrap{\collargs@addwrap\preto\collargs@postprocess@arg} +\def\collargs@addwrap#1#2#3{% + #1#2{% + \long\def\collargs@temp##1{#3}% + \expandafter\expandafter\expandafter\collargsArg + \expandafter\expandafter\expandafter{% + \expandafter\collargs@temp\expandafter{\the\collargsArg}% + }% + }% +} +% \end{macro} +% \end{collargskey} +% \end{macro} +% \end{collargskey} +% +% +% \begin{collargskey}{no delimiters} +% \begin{macro}{\ifcollargsNoDelimiters} +% When this conditional is in effect, the +% delimiter wrappers set by |\collargs@wrap| are ignored by +% |\collargs@appendarg|. +\collargsSet{% + no delimiters/.is if=collargsNoDelimiters, +} +\newif\ifcollargsNoDelimiters +% \end{macro} +% \end{collargskey} +% +% \subsubsection{The central loop} +% \label{sec:code:collargs:central-loop} +% +% The central loop is where we grab the next \meta{token} from the argument +% specification and execute the corresponding argument type or modifier +% handler, |\collargs@|\meta{token}. The central loop consumes the argument +% type \meta{token}; the handler will see the remainder of the argument +% specification (which starts with the arguments to the argument type, if any, +% e.g.\ by |()| of |d()|), followed by a dot, and then the tokens list from +% which the arguments are to be collected. It is the responsibility of handler +% to preserve the rest of the argument specification and reexecute the central +% loop once it is finished. +% +% \begin{macro}{\collargs@} +% Each argument is processed in a group to allow for local settings. This +% group is closed by |\collargs@appendarg|. +\def\collargs@{% + \begingroup + \collargs@@@ +} +% \end{macro} +% +% \begin{macro}{\collargs@@@} +% This macro is where modifier handlers reenter the central loop --- we don't +% want modifers to open a group, because their settings should remain in +% effect until the next argument. Furthermore, modifiers do not trigger +% category code fixes. +\def\collargs@@@#1{% + \collargs@in@{#1}{&+!>.}% + \ifcollargs@in@ + \expandafter\collargs@@@iii + \else + \expandafter\collargs@@@i + \fi + #1% +} +\def\collargs@@@i#1.{% + % Fix the category code of the next argument token, if necessary, and then + % proceed with the main loop. + \collargs@fix{\collargs@@@ii#1.}% +} +% Reset the fix request and set the last verbatim conditionals to the current +% state. +\def\collargs@@@ii{% + \global\collargs@fix@requestedfalse + \global\let\ifcollargs@last@verbatim\ifcollargs@verbatim + \global\let\ifcollargs@last@verbatimbraces\ifcollargs@verbatimbraces + \collargs@@@iii +} +% Call the modifier or argument type handler denoted by the first token of the +% remainder of the argument specification. +\def\collargs@@@iii#1{% + \ifcsname collargs@#1\endcsname + \csname collargs@#1\expandafter\endcsname + \else + % We throw an error if the token refers to no argument type or modifier. + \collargs@error@badtype{#1}% + \fi +} +% Throwing an error stops the processing of the argument specification, and +% closes the group opened in |\collargs@i|. +\def\collargs@error@badtype#1#2.{% + \PackageError{collargs}{Unknown xparse argument type or modifier "#1" + for "\expandafter\string\collargs@caller\space"}{}% + \endgroup +} +% \end{macro} +% +% {\catcode`\&=11 +% \begin{macro}{\collargs@&} +% We extend the |xparse| syntax with modifier |&|, which applies the given +% options to the following (and only the following) argument. If |&| is +% followed by another |&|, the options are expected to occur in the raw +% format, like the options given to |\CollectArgumentsRaw|. Otherwise, the +% options should take the form of a keylist, which will be processed by +% |\collargsSet|. In any case, the options should be given within the +% argument specification, immediately following the (single or double) |&|. +\csdef{collargs@&}{% + \futurelet\collargs@temp\collargs@amp@i +} +\def\collargs@amp@i{% + % In \hologo{ConTeXt}, |&| has character code ``other'' in the text. + %<!context>\ifx\collargs@temp&% + %<context>\expandafter\ifx\detokenize{&}\collargs@temp + \expandafter\collargs@amp@raw + \else + \expandafter\collargs@amp@set + \fi +} +\def\collargs@amp@raw#1#2{% + \collargs@verbatim@wrap{#2}% + \collargs@@@ +} +\def\collargs@amp@set#1{% + \collargs@verbatim@wrap{\collargsSet{#1}}% + \collargs@@@ +} +% \end{macro}} +% +% \begin{macro}[noindex]{\collargs@+} +% \MyIndex{collargs@+}{\texttt{\textbackslash collargs@+}}{main} +% This modifier makes the next argument long, i.e.\ accept paragraph tokens. +\csdef{collargs@+}{% + \collargs@longtrue + \collargs@@@ +} +\newif\ifcollargs@long +% \end{macro} +% +% \begin{macro}{\collargs@>} +% We can simply ignore the processor modifier. (This, |xparse|'s processor, +% should not be confused with CollArgs's processors, which are set using +% keys |append preprocessor| etc.) +\csdef{collargs@>}#1{\collargs@@@} +% \end{macro} +% +% \begin{macro}[noindex]{\collargs@!} +% \MyIndex{collargs@!}{\texttt{\textbackslash collargs@\exclamationmark}}{main} +% Should we accept spaces before an optional argument following a mandatory +% argument (\pkg{xparse} manual, \S1.1)? By default, yes. This modifier is +% only applicable to types |d| and |t|, and derived types, but, unlike +% |xparse|, we don't bother to enforce this; when used with other types, |!| +% simply has no effect. +\csdef{collargs@!}{% + \collargs@grabspacesfalse + \collargs@@@ +} +% \end{macro} +% +% \begin{macro}{\collargs@toks} +% This token register is where we store the collected argument tokens. All +% assignments to this register are global, because it needs to survive the +% groups opened for individual arguments. +\newtoks\collargs@toks +% \end{macro} +% +% \begin{macro}{\collargsArg} +% An auxiliary, but publicly available token register, used for processing +% the argument, and by some argument type handlers. +\newtoks\collargsArg +% \end{macro} +% +% \begin{macro}{\collargs@.} +% This fake argument type is used to signal the end of the argument list. +% Note that this really counts as an extension of the |xparse| argument +% specification. +\csdef{collargs@.}{% + % Close the group opened in |\collargs@|. + \endgroup + % Close the main |\CollectArguments| group, fix the category code of the next + % token if necessary, and execute the next-code, followed by the collected + % arguments in braces. Any over-grabbed spaces are reinserted into the input + % stream, non-verbatim. + \expanded{% + \endgroup + \noexpand\collargs@fix{% + \expandonce\collargs@next{\the\collargs@toks}% + \collargs@spaces + }% + }% +} +% \end{macro} +% +% \subsubsection{Auxiliary macros} +% \label{sec:code:collargs:aux} +% +% \begin{macro}{\collargs@appendarg} +% This macro is used by the argument type +% handlers to append the collected argument to the storage +% (|\collargs@toks|). +\long\def\collargs@appendarg#1{% + % Temporarily store the collected argument into a token register. The + % processors will manipulate the contents of this register. + \collargsArg={#1}% + % This will clear the double-fix conditional, and potentially request a + % normal, single fix. We can do this here because this macro is only called + % when something is actually collected. For details, see + % section~\ref{sec:code:collargs:fix}. + \ifcollargs@double@fix + \collargs@cancel@double@fix + \fi + % Process the argument with user-definable preprocessors, the wrapper defined + % by the argument type, and user-definable postprocessors. + \collargs@preprocess@arg + \ifcollargsNoDelimiters + \else + \collargs@process@arg + \fi + \collargs@postprocess@arg + % Append the processed argument, preceded by any grabbed spaces (in the + % correct mode), to the storage. + \xtoksapp\collargs@toks{\collargs@grabbed@spaces\the\collargsArg}% + % Initialize the space-grabber. + \collargs@init@grabspaces + % Once the argument was appended to the list, we can close its group, opened + % by |\collargs@|. + \endgroup +} +% \end{macro} +% +% \begin{macro}{\collargs@wrap} +% This macro is used by argument type handlers to declare their delimiter +% wrap, like square brackets around the optional argument of type |o|. It +% uses |\collargs@addwrap|, defined in section~\ref{sec:code:collargs:keys}, +% but adds to |\collargs@process@arg|, which holds the delimiter wrapper +% defined by the argument type handler. Note that this macro \emph{appends} +% a wrapper, so multiple wrappers are allowed --- this is used by type |e| +% handler. +\def\collargs@wrap{\collargs@addwrap\appto\collargs@process@arg} +\def\collargs@process@arg{} +% \end{macro} +% +% \begin{macro}{\collargs@defcollector,\collargs@defusecollector,\collargs@letusecollector} +% These macros streamline the usage of +% the ``caller'' control sequence. They are like a |\def|, but should not +% be given the control sequence to define, as they will automatically +% define the control sequence residing in |\collargs@caller|; the usage is +% thus |\collargs@defcollector<parameters>{<definition>}|. For example, if +% |\collargs@caller| holds |\foo|, |\collargs@defcollector#1{(#1)}| is +% equivalent to |\def\foo#1{(#1)}|. Macro |\collargs@defcollector| will +% only define the caller control sequence to be the collector, while +% |\collargs@defusecollector| will also immediately execute it. +\def\collargs@defcollector#1#{% + \ifcollargs@long\long\fi + \expandafter\def\collargs@caller#1% +} +\def\collargs@defusecollector#1#{% + \afterassignment\collargs@caller + \ifcollargs@long\long\fi + \expandafter\def\collargs@caller#1% +} +\def\collargs@letusecollector#1{% + \expandafter\let\collargs@caller#1% + \collargs@caller +} +\newif\ifcollargs@grabspaces +\collargs@grabspacestrue +% \end{macro} +% +% \begin{macro}{\collargs@init@grabspaces} +% The space-grabber macro +% |\collargs@grabspaces| should be initialized by executing this macro. If +% |\collargs@grabspaces| is called twice without an intermediate +% initialization, it will assume it is in the same position in the input +% stream and simply bail out. +\def\collargs@init@grabspaces{% + \gdef\collargs@gs@state{0}% + \gdef\collargs@spaces{}% + \gdef\collargs@otherspaces{}% +} +% \end{macro} +% +% \begin{macro}{\collargs@grabspaces} +% This auxiliary macro grabs any following +% spaces, and then executes the next-code given as the sole argument. The +% spaces will be stored into two macros, |\collargs@spaces| and +% |\collargs@otherspaces|, which store the spaces in the non-verbatim and the +% verbatim form. With the double storage, we can grab the spaces in the +% verbatim mode and use them non-verbatim, or vice versa. The macro takes a +% single argument, the code to execute after maybe grabbing the spaces. +% +\def\collargs@grabspaces#1{% + \edef\collargs@gs@next{\unexpanded{#1}}% + \ifnum\collargs@gs@state=0 + \gdef\collargs@gs@state{1}% + \expandafter\collargs@gs@i + \else + \expandafter\collargs@gs@next + \fi +} +\def\collargs@gs@i{% + \futurelet\collargs@temp\collargs@gs@g +} +% We check for grouping characters even in the verbatim mode, because we might +% be in the partial verbatim. +\def\collargs@gs@g{% + \ifcat\noexpand\collargs@temp\bgroup + \expandafter\collargs@gs@next + \else + \ifcat\noexpand\collargs@temp\egroup + \expandafter\expandafter\expandafter\collargs@gs@next + \else + \expandafter\expandafter\expandafter\collargs@gs@ii + \fi + \fi +} +\def\collargs@gs@ii{% + \ifcollargs@verbatim + \expandafter\collargs@gos@iii + \else + \expandafter\collargs@gs@iii + \fi +} +% This works because the character code of a space token is always 32. +\def\collargs@gs@iii{% + \expandafter\ifx\space\collargs@temp + \expandafter\collargs@gs@iv + \else + \expandafter\collargs@gs@next + \fi +} +\expandafter\def\expandafter\collargs@gs@iv\space{% + \gappto\collargs@spaces{ }% + \xappto\collargs@otherspaces{\collargs@otherspace}% + \collargs@gs@i +} +% We need the space of category 12 above. +\begingroup\catcode`\ =12\relax\gdef\collargs@otherspace{ }\endgroup +\def\collargs@gos@iii#1{% + % Macro |\collargs@cc| recalls the ``outside'' category code of character + % |#1|; see section~\ref{sec:code:collargs:verbatim}. + \ifnum\collargs@cc{#1}=10 + % We have a space. + \expandafter\collargs@gos@iv + \else + \ifnum\collargs@cc{#1}=5 + % We have a newline. + \expandafter\expandafter\expandafter\collargs@gos@v + \else + \expandafter\expandafter\expandafter\collargs@gs@next + \fi + \fi + #1% +} +\def\collargs@gos@iv#1{% + \gappto\collargs@otherspaces{#1}% + % No matter how many verbatim spaces we collect, they equal a single + % non-verbatim space. + \gdef\collargs@spaces{ }% + \collargs@gs@i +} +\def\collargs@gos@v{% + % Only add the first newline. + \ifnum\collargs@gs@state=2 + \expandafter\collargs@gs@next + \else + \expandafter\collargs@gs@vi + \fi +} +\def\collargs@gs@vi#1{% + \gdef\collargs@gs@state{2}% + \gappto\collargs@otherspaces{#1}% + \gdef\collargs@spaces{ }% + \collargs@gs@i +} +% \end{macro} +% +% \begin{macro}{\collargs@maybegrabspaces} +% This macro grabs any following spaces, but it will do so only when +% conditional |\ifcollargs@grabspaces|, which can be \emph{un}set by modifier +% |!|, is in effect. The macro is used by handlers for types |d| and |t|. +\def\collargs@maybegrabspaces{% + \ifcollargs@grabspaces + \expandafter\collargs@grabspaces + \else + \expandafter\@firstofone + \fi +} +% \end{macro} +% +% \begin{macro}{\collargs@grabbed@spaces} +% This macro expands to either the verbatim +% or the non-verbatim variant of the grabbed spaces, depending on the +% verbatim mode in effect at the time of expansion. +\def\collargs@grabbed@spaces{% + \ifcollargs@verbatim + \collargs@otherspaces + \else + \collargs@spaces + \fi +} +% \end{macro} +% +% \begin{macro}{\collargs@reinsert@spaces} +% Inserts the grabbed spaces back into the +% input stream, but with the category code appropriate for the verbatim mode +% then in effect. After the insertion, the space-grabber is initialized and +% the given next-code is executed in front of the inserted spaces. +\def\collargs@reinsert@spaces#1{% + \expanded{% + \unexpanded{% + \collargs@init@grabspaces + #1% + }% + \collargs@grabbed@spaces + }% +} +% \end{macro} +% +% \begin{macro}{\collargs@ifnextcat} +% An adaptation of |\pgf@keys@utilifnextchar| +% which checks whether the \emph{category} code of the next non-space +% character matches the category code of |#1|. +\long\def\collargs@ifnextcat#1#2#3{% + \let\pgf@keys@utilreserved@d=#1% + \def\pgf@keys@utilreserved@a{#2}% + \def\pgf@keys@utilreserved@b{#3}% + \futurelet\pgf@keys@utillet@token\collargs@ifncat} +\def\collargs@ifncat{% + \ifx\pgf@keys@utillet@token\pgf@keys@utilsptoken + \let\pgf@keys@utilreserved@c\collargsxifnch + \else + \ifcat\noexpand\pgf@keys@utillet@token\pgf@keys@utilreserved@d + \let\pgf@keys@utilreserved@c\pgf@keys@utilreserved@a + \else + \let\pgf@keys@utilreserved@c\pgf@keys@utilreserved@b + \fi + \fi + \pgf@keys@utilreserved@c} +{% + \def\:{\collargs@xifncat} + \expandafter\gdef\: {\futurelet\pgf@keys@utillet@token\collargs@ifncat} +} +% \end{macro} +% +% \begin{macro}{\collargs@forrange} +% This macro executes macro |\collargs@do| for every integer from |#1| and +% |#2|, both inclusive. |\collargs@do| should take a single parameter, the +% current number. +\def\collargs@forrange#1#2{% + \expanded{% + \noexpand\collargs@forrange@i{\number#1}{\number#2}% + }% + } +\def\collargs@forrange@i#1#2{% + \ifnum#1>#2 % + \expandafter\@gobble + \else + \expandafter\@firstofone + \fi + {% + \collargs@do{#1}% + \expandafter\collargs@forrange@i\expandafter{\number\numexpr#1+1\relax}{#2}% + }% +} +% \end{macro} +% +% \begin{macro}{\collargs@forranges} +% This macro executes macro |\collargs@do| for every integer falling into the +% ranges specified in |#1|. The ranges should be given as a comma-separated +% list of |from-to| items, e.g.\ |1-5,10-11|. +\def\collargs@forranges{\forcsvlist\collarg@forrange@i} +\def\collarg@forrange@i#1{\collarg@forrange@ii#1-} +\def\collarg@forrange@ii#1-#2-{\collargs@forrange{#1}{#2}} +% \end{macro} +% +% \begin{macro}{\collargs@percentchar} +% This macro holds the percent character of category 12. +\begingroup +\catcode`\%=12 +\gdef\collargs@percentchar{%} +\endgroup +% \end{macro} +% +% +% \subsubsection{The handlers} +% \label{sec:code:collargs:handlers} +% +% \begin{macro}{\collargs@l} +% We will first define the handler for the very funky argument type |l|, +% which corresponds to \hologo{TeX}'s |\def\foo#1#{...}|, which grabs (into +% |#1|) everything up to the first opening brace --- not because this type is +% important or even recommended to use, but because the definition of the +% handler is very simple, at least for the non-verbatim case. +% +\def\collargs@l#1.{% + % Any pre-grabbed spaces in fact belong into the argument. + \collargs@reinsert@spaces{\collargs@l@i#1.}% +} +\def\collargs@l@i{% + % We request a correction of the category code of the delimiting brace if the + % verbatim mode changes for the next argument; for details, see + % section~\ref{sec:code:collargs:fix}. + \global\collargs@fix@requestedtrue + % Most handlers will branch into the verbatim and the non-verbatim part using + % conditional |\ifcollargs@verbatim|. This handler is a bit special, because + % it needs to distinguish verbatim and non-verbatim \emph{braces}, and + % braces are verbatim only in the full verbatim mode, i.e.\ when + % |\ifcollargs@verbatimbraces| is true. + \ifcollargs@verbatimbraces + \expandafter\collargs@l@verb + \else + \expandafter\collargs@l@ii + \fi +} +% We grab the rest of the argument specification (|#1|), to be reinserted into +% the token stream when we reexecute the central loop. +\def\collargs@l@ii#1.{% + % In the non-verbatim mode, we merely have to define and execute the + % collector macro. The parameter text |##1##| (note the doubled hashes), + % which will put everything up to the first opening brace into the first + % argument, looks funky, but that's all. + \collargs@defusecollector##1##{% + % We append the collected argument, |##1|, to |\collargs@toks|, the token + % register holding the collected argument tokens. + \collargs@appendarg{##1}% + % Back to the central loop, with the rest of the argument specification + % reinserted. + \collargs@#1.% + }% +} +\def\collargs@l@verb#1.{% + % In the verbatim branch, we need to grab everything up to the first opening + % brace of category code 12, so we want to define the collector with + % parameter text |##1{|, with the opening brace of category 12. We have + % stored this token in macro |\collargs@other@bgroup|, which we now need to + % expand. + \expandafter\collargs@defusecollector + \expandafter##\expandafter1\collargs@other@bgroup{% + % Appending the argument works the same as in the non-verbatim case. + \collargs@appendarg{##1}% + % Reexecuting the central loop macro is a bit more involved, as we need to + % reinsert the verbatim opening brace (contrary to the regular brace above, + % the verbatim brace is consumed by the collector macro) back into the + % token stream, behind the reinserted argument specification. + \expanded{% + \noexpand\collargs@\unexpanded{#1.}% + \collargs@other@bgroup + }% + }% +} +% \end{macro} +% +% \begin{macro}{\collargs@u} +% Another weird type --- |u|\meta{tokens} reads everything up to the given +% \meta{tokens}, i.e.\ this is \hologo{TeX}'s +% |\def\foo#1|\meta{tokens}|{...}| --- but again, simple enough to allow us +% to showcase solutions to two recurring problems. +% +% We start by branching into the verbatim mode (full or partial) or the +% non-verbatim mode. +\def\collargs@u{% + \ifcollargs@verbatim + \expandafter\collargs@u@verb + \else + \expandafter\collargs@u@i + \fi +} +% To deal with the verbatim mode, we only need to convert the above +% \meta{tokens} (i.e.\ the argument of |u| in the argument specification) to +% category 12, i.e.\ we have to |\detokenize| them. Then, we may proceed as in +% the non-verbatim branch, |\collargs@u@ii|. +\def\collargs@u@verb#1{% + % The |\string| here is a temporary solution to a problem with spaces. Our + % verbatim mode has them of category ``other'', but |\detokenize| produces a + % space of category ``space'' behind control words. ^^A todo + \expandafter\collargs@u@i\expandafter{\detokenize\expandafter{\string#1}}% +} +% We then reinsert any pre-grabbed spaces into the stream, but we take care not +% to destroy the braces around our delimiter in the argument specification. +\def\collargs@u@i#1#2.{% + \collargs@reinsert@spaces{\collargs@u@ii{#1}#2.}% +} +\def\collargs@u@ii#1#2.{% + % |#1| contains the delimiter tokens, so |##1| below will receive everything + % in the token stream up to these. But we have a problem: if we defined the + % collector as for the non-verbatim |l|, and the delimiter happened to be + % preceded by a single brace group, we would lose the braces. For example, + % if the delimiter was |-| and we received |{foo}-|, we would collect |foo-|. + % We solve this problem by inserting |\collargs@empty| (with an empty + % definition) into the input stream (at the end of this macro) --- this way, + % the delimiter can never be preceded by a single brace group --- and then + % expanding it away before appending to storage (within the argument of + % |\collargs@defusecollector|). + \collargs@defusecollector##1#1{% + % Define the wrapper which will add the delimiter tokens (|#1|) after the + % collected argument. The wrapper will be applied during argument + % processing in |\collargs@appendarg| (sandwiched between used-definable + % pre- and post-processors). + \collargs@wrap{####1#1}% + % Expand the first token in |##1|, which we know to be |\collargs@empty|, + % with empty expansion. + \expandafter\collargs@appendarg\expandafter{##1}% + \collargs@#2.% + }% + % Insert |\collargs@empty| into the input stream, in front of the ``real'' + % argument tokens. + \collargs@empty +} +\def\collargs@empty{} +% \end{macro} +% +% \begin{macro}{\collargs@r} +% Finally, a real argument type: required delimited argument. +\def\collargs@r{% + \ifcollargs@verbatim + \expandafter\collargs@r@verb + \else + \expandafter\collargs@r@i + \fi +} +\def\collargs@r@verb#1#2{% + \expandafter\collargs@r@i\detokenize{#1#2}% +} +\def\collargs@r@i#1#2#3.{% + % We will need to use the |\collargs@empty| trick from type |u|, but with an + % additional twist: we need to insert it \emph{after} the opening delimiter + % |#1|. To do this, we consume the opening delimiter by the ``outer'' + % collector below --- we need to use the collector so that we get a nice + % error message when the opening delimiter is not present --- and have this + % collector define the ``inner'' collector in the spirit of type |u|. + % + % The outer collector has no parameters, it just requires the presence of the + % opening delimiter. + \collargs@defcollector#1{% + % The inner collector will grab everything up to the closing delimiter. + \collargs@defusecollector####1#2{% + % Append the collected argument |####1| to the list, wrapping it into the + % delimiters (|#1| and |#2|), but not before expanding its first token, + % which we know to be |\collargs@empty|. + \collargs@wrap{#1########1#2}% + \expandafter\collargs@appendarg\expandafter{####1}% + \collargs@#3.% + }% + \collargs@empty + }% + % Another complication: our delimited argument may be preceded by spaces. To + % replicate the argument tokens faithfully, we need to collect them before + % trying to grab the argument itself. + \collargs@grabspaces\collargs@caller +} +% \end{macro} +% +% \begin{macro}{\collargs@R} +% Discard the default and execute |r|. +\def\collargs@R#1#2#3{\collargs@r#1#2} +% \end{macro} +% +% \begin{macro}{\collargs@d} +% Optional delimited argument. Very similar to |r|. +\def\collargs@d{% + \ifcollargs@verbatim + \expandafter\collargs@d@verb + \else + \expandafter\collargs@d@i + \fi +} +\def\collargs@d@verb#1#2{% + \expandafter\collargs@d@i\detokenize{#1#2}% +} +\def\collargs@d@i#1#2#3.{% + % This macro will be executed when the optional argument is not present. It + % simply closes the argument's group and reexecutes the central loop. + \def\collargs@d@noopt{% + \global\collargs@fix@requestedtrue + \endgroup + \collargs@#3.% + }% + % The collector(s) are exactly as for |r|. + \collargs@defcollector#1{% + \collargs@defusecollector####1#2{% + \collargs@wrap{#1########1#2}% + \expandafter\collargs@appendarg\expandafter{####1}% + \collargs@#3.% + }% + \collargs@empty + }% + % This macro will check, in conjunction with |\futurelet| below, whether the + % optional argument is present or not. + \def\collargs@d@ii{% + \ifx#1\collargs@temp + \expandafter\collargs@caller + \else + \expandafter\collargs@d@noopt + \fi + }% + % Whether spaces are allowed in front of this type of argument depends on the + % presence of modifier |!|. + \collargs@maybegrabspaces{\futurelet\collargs@temp\collargs@d@ii}% +} +% \end{macro} +% +% \begin{macro}{\collargs@D} +% Discard the default and execute |d|. +\def\collargs@D#1#2#3{\collargs@d#1#2} +% \end{macro} +% +% \begin{macro}{\collargs@o} +% |o| is just |d| with delimiters |[| and |]|. +\def\collargs@o{\collargs@d[]} +% \end{macro} +% +% \begin{macro}{\collargs@O} +% |O| is just |d| with delimiters |[| and |]| and the discarded default. +\def\collargs@O#1{\collargs@d[]} +% \end{macro} +% +% \begin{macro}{\collargs@t} +% An optional token. Similar to |d|. +\def\collargs@t{% + \ifcollargs@verbatim + \expandafter\collargs@t@verb + \else + \expandafter\collargs@t@i + \fi +} +\def\collargs@t@space{ } +\def\collargs@t@verb#1{% + \let\collargs@t@space\collargs@otherspace + \expandafter\collargs@t@i\expandafter{\detokenize{#1}}% +} +\def\collargs@t@i#1{% + \expandafter\ifx\space#1% + \expandafter\collargs@t@s + \else + \expandafter\collargs@t@I\expandafter#1% + \fi +} +\def\collargs@t@s#1.{% + \collargs@grabspaces{% + \ifcollargs@grabspaces + \collargs@appendarg{}% + \else + \expanded{% + \noexpand\collargs@init@grabspaces + \noexpand\collargs@appendarg{\collargs@grabbed@spaces}% + }% + \fi + \collargs@#1.% + }% +} +\def\collargs@t@I#1#2.{% + \def\collargs@t@noopt{% + \global\collargs@fix@requestedtrue + \endgroup + \collargs@#2.% + }% + \def\collargs@t@opt##1{% + \collargs@appendarg{#1}% + \collargs@#2.% + }% + \def\collargs@t@ii{% + \ifx#1\collargs@temp + \expandafter\collargs@t@opt + \else + \expandafter\collargs@t@noopt + \fi + }% + \collargs@maybegrabspaces{\futurelet\collargs@temp\collargs@t@ii}% +} +\def\collargs@t@opt@space{% + \expanded{\noexpand\collargs@t@opt{\space}\expandafter}\romannumeral-0% +}% +% \end{macro} +% +% \begin{macro}{\collargs@s} +% The optional star is just a special case of |t|. +\def\collargs@s{\collargs@t*} +% \end{macro} +% +% \begin{macro}{\collargs@m} +% Mandatory argument. Interestingly, here's where things get complicated, +% because we have to take care of several \hologo{TeX} quirks. +\def\collargs@m{% + \ifcollargs@verbatim + \expandafter\collargs@m@verb + \else + \expandafter\collargs@m@i + \fi +} +% The non-verbatim mode. First, collect any spaces in front of the argument. +\def\collargs@m@i#1.{% + \collargs@grabspaces{\collargs@m@checkforgroup#1.}% +} +% Is the argument in braces or not? +\def\collargs@m@checkforgroup#1.{% + \edef\collargs@action{\unexpanded{\collargs@m@checkforgroup@i#1.}}% + \futurelet\collargs@token\collargs@action +} +\def\collargs@m@checkforgroup@i{% + \ifcat\noexpand\collargs@token\bgroup + \expandafter\collargs@m@group + \else + \expandafter\collargs@m@token + \fi +} +% The argument is given in braces, so we put them back around it +% (|\collargs@wrap|) when appending to the storage. +\def\collargs@m@group#1.{% + \collargs@defusecollector##1{% + \collargs@wrap{{####1}}% + \collargs@appendarg{##1}% + \collargs@#1.% + }% +} +% The argument is a single token, we append it to the storage as is. +\def\collargs@m@token#1.{% + \collargs@defusecollector##1{% + \collargs@appendarg{##1}% + \collargs@#1.% + }% +} +% The verbatim mode. Again, we first collect any spaces in front of the +% argument. +\def\collargs@m@verb#1.{% + \collargs@grabspaces{\collargs@m@verb@checkforgroup#1.}% +} +% We want to check whether we're dealing with a braced argument. We're in the +% verbatim mode, but are braces verbatim as well? In other words, are we in +% |verbatim| or |verb| mode? In the latter case, braces are regular, so we +% redirect to the regular mode. +\def\collargs@m@verb@checkforgroup{% + \ifcollargs@verbatimbraces + \expandafter\collargs@m@verb@checkforgroup@i + \else + \expandafter\collargs@m@checkforgroup + \fi +} +% Is the argument in verbatim braces? +\def\collargs@m@verb@checkforgroup@i#1.{% + \def\collargs@m@verb@checkforgroup@ii{\collargs@m@verb@checkforgroup@iii#1.}% + \futurelet\collargs@temp\collargs@m@verb@checkforgroup@ii +} +\def\collargs@m@verb@checkforgroup@iii#1.{% + \expandafter\ifx\collargs@other@bgroup\collargs@temp + % Yes, the argument is in (verbatim) braces. + \expandafter\collargs@m@verb@group + \else + % We need to manually check whether the following token is a (verbatim) + % closing brace, and throw an error if it is. + \expandafter\ifx\collargs@other@egroup\collargs@temp + \expandafter\expandafter\expandafter\collargs@m@verb@egrouperror + \else + % The argument is a single token. + \expandafter\expandafter\expandafter\collargs@m@v@token + \fi + \fi + #1.% +} +\def\collargs@m@verb@egrouperror#1.{% + \PackageError{collargs}{% + Argument of \expandafter\string\collargs@caller\space has an extra + \iffalse{\else\string}}{}% +} +% A single-token verbatim argument. +\def\collargs@m@v@token#1.#2{% + % Is it a control sequence? (Macro |\collargs@cc| recalls the ``outside'' + % category code of character |#1|; see + % section~\ref{sec:code:collargs:verbatim}.) + \ifnum\collargs@cc{#2}=0 + \expandafter\collargs@m@v@token@cs + \else + \expandafter\collargs@m@token + \fi + #1.#2% +} +% Is it a one-character control sequence? +\def\collargs@m@v@token@cs#1.#2#3{% + \ifnum\collargs@cc{#3}=11 + \expandafter\collargs@m@v@token@cs@letter + \else + \expandafter\collargs@m@v@token@cs@nonletter + \fi + #1.#2#3% +} +% Store |\<token>|. +\def\collargs@m@v@token@cs@nonletter#1.#2#3{% + \collargs@appendarg{#2#3}% + \collargs@#1.% +} +% Store |\| to a temporary register, we'll parse the control sequence name now. +\def\collargs@m@v@token@cs@letter#1.#2{% + \collargsArg{#2}% + \def\collargs@tempa{#1}% + \collargs@m@v@token@cs@letter@i +} +% Append a letter to the control sequence. +\def\collargs@m@v@token@cs@letter@i#1{% + \ifnum\collargs@cc{#1}=11 + \toksapp\collargsArg{#1}% + \expandafter\collargs@m@v@token@cs@letter@i + \else + % Finish, returning the non-letter to the input stream. + \expandafter\collargs@m@v@token@cs@letter@ii\expandafter#1% + \fi +} +% Store the verbatim control sequence. +\def\collargs@m@v@token@cs@letter@ii{% + \expanded{% + \unexpanded{% + \expandafter\collargs@appendarg\expandafter{\the\collargsArg}% + }% + \noexpand\collargs@\expandonce\collargs@tempa.% + }% +} +% The verbatim mandatory argument is delimited by verbatim braces. We have to +% use the heavy machinery adapted from |cprotect|. +\def\collargs@m@verb@group#1.#2{% + \let\collargs@begintag\collargs@other@bgroup + \let\collargs@endtag\collargs@other@egroup + \def\collargs@tagarg{}% + \def\collargs@commandatend{\collargs@m@verb@group@i#1.}% + \collargs@readContent +} +% This macro appends the result given by the heavy machinery, waiting for us in +% macro |\collargsArg|, to |\collargs@toks|, but not before dressing +% it up (via |\collargs@wrap|) in a pair of verbatim braces. +\def\collargs@m@verb@group@i{% + \edef\collargs@temp{% + \collargs@other@bgroup\unexpanded{##1}\collargs@other@egroup}% + \expandafter\collargs@wrap\expandafter{\collargs@temp}% + \expandafter\collargs@appendarg\expandafter{\the\collargsArg}% + \collargs@ +} +% \end{macro} +% +% \begin{macro}{\collargs@g} +% An optional group: same as |m|, but we simply bail out if we don't find the +% group character. +\def\collargs@g{% + \def\collargs@m@token{% + \global\collargs@fix@requestedtrue + \endgroup + \collargs@ + }% + \let\collargs@m@v@token\collargs@m@token + \collargs@m +} +% \end{macro} +% +% \begin{macro}{\collargs@G} +% Discard the default and execute |g|. +\def\collargs@G#1{\collargs@g} +% \end{macro} +% +% \begin{macro}{\collargs@v} +% Verbatim argument. The code is executed in the group, deploying +% |\collargsVerbatim|. The grouping characters are always set to braces, to +% mimick |xparse| perfectly. +\def\collargs@v#1.{% + \begingroup + \collargsBraces{{}}% + \collargsVerbatim + \collargs@grabspaces{\collargs@v@i#1.}% +} +\def\collargs@v@i#1.#2{% + \expandafter\ifx\collargs@other@bgroup#2% + % If the first token we see is an opening brace, use the |cprotect| + % adaptation to grab the group. + \let\collargs@begintag\collargs@other@bgroup + \let\collargs@endtag\collargs@other@egroup + \def\collargs@tagarg{}% + \def\collargs@commandatend{% + \edef\collargs@temp{% + \collargs@other@bgroup\unexpanded{####1}\collargs@other@egroup}% + \expandafter\collargs@wrap\expandafter{\collargs@temp}% + \expandafter\collargs@appendarg\expandafter{\the\collargsArg}% + \endgroup + \collargs@#1.% + }% + \expandafter\collargs@readContent + \else + % Otherwise, the verbatim argument is delimited by two identical characters + % (|#2|). + \collargs@defcollector##1#2{% + \collargs@wrap{#2####1#2}% + \collargs@appendarg{##1}% + \endgroup + \collargs@#1.% + }% + \expandafter\collargs@caller + \fi +} +% \end{macro} +% +% \begin{macro}{\collargs@b} +% Environments. Here's where all hell breaks loose. We survive by adapting +% some code from Bruno Le Floch's |cprotect|. We first define the +% environment-related keys, then provide the handler code, and finish with +% the adaptation of |cprotect|'s environment-grabbing code. +% +% The argument type |b| token may be followed by a braced environment name +% (in the argument specification). +\def\collargs@b{% + \collargs@ifnextcat\bgroup\collargs@bg\collargs@bi +} +\def\collargs@bg#1{% + \edef\collargs@b@envname{#1}% + \collargs@bi +} +\def\collargs@bi#1.{% + % Convert the environment name to verbatim if necessary. + \ifcollargs@verbatim + \edef\collargs@b@envname{\detokenize\expandafter{\collargs@b@envname}}% + \fi + % This is a format-specific macro which sets up |\collargs@begintag| and + % |\collargs@endtag|. + \collargs@bi@defCPTbeginend + \edef\collargs@tagarg{% + \ifcollargs@verbatimbraces + \else + \ifcollargsIgnoreOtherTags + \collargs@b@envname + \fi + \fi + }% + % Run this after collecting the body. + \def\collargs@commandatend{% + % In \hologo{LaTeX}, we might, depending on the verbatim mode, need to + % check whether the environment name is correct. + %<latex>\collargs@bii + % In \hologo{plainTeX} and \hologo{ConTeXt}, we can skip directly to + % |\collargs@biii|. + %<plain,context>\collargs@biii + #1.% + }% + % Collect the environment body, but first, put any grabbed spaces back into + % the input stream. + \collargs@reinsert@spaces\collargs@readContent +} +%<*latex> +% In \hologo{LaTeX} in the regular and the partial verbatim mode, we search for +% |\begin|\slash|\end| --- as we cannot search for braces --- either as control +% sequences in the regular mode, or as strings in the partial verbatim +% mode. (After search, we will have to check whether the argument of +% |\begin|\slash|\end| matches our environment name.) In the full verbatim +% mode, we can search for the entire string |\begin|\slash|\end|\marg{name}. +\def\collargs@bi@defCPTbeginend{% + \edef\collargs@begintag{% + \ifcollargs@verbatim + \expandafter\string + \else + \expandafter\noexpand + \fi + \begin + \ifcollargs@verbatimbraces + \collargs@other@bgroup\collargs@b@envname\collargs@other@egroup + \fi + }% + \edef\collargs@endtag{% + \ifcollargs@verbatim + \expandafter\string + \else + \expandafter\noexpand + \fi + \end + \ifcollargs@verbatimbraces + \collargs@other@bgroup\collargs@b@envname\collargs@other@egroup + \fi + }% +} +%</latex> +%<*plain,context> +% We can search for the entire |\|\meta{name}\slash|\end|\meta{name} (in +% \hologo{TeX}) or |\start|\meta{name}\slash|\stop|\meta{name} (in +% \hologo{ConTeXt}), either as a control sequence (in the regular mode), or as +% a string (in the verbatim modes). +\def\collargs@bi@defCPTbeginend{% + \edef\collargs@begintag{% + \ifcollargs@verbatim + \expandafter\expandafter\expandafter\string + \else + \expandafter\expandafter\expandafter\noexpand + \fi + \csname + %<context>start% + \collargs@b@envname + \endcsname + }% + \edef\collargs@endtag{% + \ifcollargs@verbatim + \expandafter\expandafter\expandafter\string + \else + \expandafter\expandafter\expandafter\noexpand + \fi + \csname + %<plain>end% + %<context>stop% + \collargs@b@envname + \endcsname + }% +} +%</plain,context> +%<*latex> +% Check whether we're in front of the (braced) environment name (in +% \hologo{LaTeX}), and consume it. +\def\collargs@bii{% + \ifcollargs@verbatimbraces + \expandafter\collargs@biii + \else + \ifcollargsIgnoreOtherTags + % We shouldn't check the name in this case, because it was already + % checked, and consumed. + \expandafter\expandafter\expandafter\collargs@biii + \else + \expandafter\expandafter\expandafter\collargs@b@checkend + \fi + \fi +} +\def\collargs@b@checkend#1.{% + \collargs@grabspaces{\collargs@b@checkend@i#1.}% +} +\def\collargs@b@checkend@i#1.#2{% + \def\collargs@temp{#2}% + \ifx\collargs@temp\collargs@b@envname + \else + \collargs@b@checkend@error + \fi + \collargs@biii#1.% +} +\def\collargs@b@checkend@error{% + \PackageError{collargs}{Environment "\collargs@b@envname" ended as + "\collargs@temp"}{}% +} +%</latex> +% This macro stores the collected body. +\def\collargs@biii{% + % Define the wrapper macro (|\collargs@temp|). + \collargs@b@def@wrapper + % Execute |\collargs@appendarg| to append the body to the list. Expand the + % wrapper in |\collargs@temp| first and the body in |\collargsArg| next. + \expandafter\collargs@appendarg\expandafter{\the\collargsArg}% + % Reexecute the central loop. + \collargs@ +} +\def\collargs@b@def@wrapper{% + %<latex>\edef\collargs@temp{{\collargs@b@envname}}% + \edef\collargs@temp{% + % Was the begin-tag requested? + \ifcollargsBeginTag + % |\collargs@begintag| is already adapted to the format and the verbatim mode. + \expandonce\collargs@begintag + % Add the braced environment name in \hologo{LaTeX} in the regular and + % partial verbatim mode. + % + %<*latex> + \ifcollargs@verbatimbraces\else\collargs@temp\fi + %</latex> + \fi + % This is the body. + ####1% + % Rinse and repeat for the end-tag. + \ifcollargsEndTag + \expandonce\collargs@endtag + %<*latex> + \ifcollargs@verbatimbraces\else\collargs@temp\fi + %</latex> + \fi + }% + \expandafter\collargs@wrap\expandafter{\collargs@temp}% +} +% \end{macro} +% +% \begin{macro}{\collargs@readContent} +% This macro, which is an adaptation of +% \texttt{cprotect}'s environment-grabbing code, collects some delimited +% text, leaving the result in |\collargsArg|. Before calling it, one must +% define the following macros: |\collargs@begintag| and |\collargs@endtag| +% are the content delimiters; |\collargs@tagarg|, if non-empty, is the token +% or grouped text which must follow a delimiter to be taken into account; +% |\collargs@commandatend| is the command that will be executed once the +% content is collected. +\def\collargs@readContent{% + % Define macro which will search for the first begin-tag. + \ifcollargs@long\long\fi + \collargs@CPT@def\collargs@gobbleOneB\collargs@begintag{% + % Assign the collected tokens into a register. The first token in |##1| + % will be |\collargs@empty|, so we expand to get rid of it. + \expandafter\toks@\expandafter{##1}% + % |cprotect| simply grabs the token following the |\collargs@begintag| with + % a parameter. We can't do this, because we need the code to work in the + % non-verbatim mode, as well, and we might stumble upon a brace there. So + % we take a peek. + \futurelet\collargs@temp\collargs@gobbleOneB@i + }% + % Define macro which will search for the first end-tag. We make it long if + % so required (by |+|). + \ifcollargs@long\long\fi + \collargs@CPT@def\collargs@gobbleUntilE\collargs@endtag{% + % Expand |\collargs@empty| at the start of |##1|. + \expandafter\toksapp\expandafter\toks@\expandafter{##1}% + \collargs@gobbleUntilE@i + }% + % Initialize. + \collargs@begins=0\relax + \collargsArg{}% + \toks@{}% + % We will call |\collargs@gobbleUntilE| via the caller control sequence. + \collargs@letusecollector\collargs@gobbleUntilE + % We insert |\collargs@empty| to avoid the potential debracing problem. + \collargs@empty +} +% How many begin-tags do we have opened? +\newcount\collargs@begins +% An auxiliary macro which |\def|s |#1| so that it will grab everything up +% until |#2|. Additional parameters may be present before the definition. +\def\collargs@CPT@def#1#2{% + \expandafter\def\expandafter#1% + \expandafter##\expandafter1#2% +} +% A quark quard. +\def\collargs@qend{\collargs@qend} +% This macro will collect the ``environment'', leaving the result in +% |\collargsArg|. It expects |\collargs@begintag|, |\collargs@endtag| and +% |\collargs@commandatend| to be set. +\def\collargs@gobbleOneB@i{% + \def\collargs@begins@increment{1}% + \ifx\collargs@qend\collargs@temp + % We have reached the fake begin-tag. Note that we found the end-tag. + \def\collargs@begins@increment{-1}% + % Gobble the quark guard. + \expandafter\collargs@gobbleOneB@v + \else + % Append the real begin-tag to the temporary tokens. + \etoksapp\toks@{\expandonce\collargs@begintag}% + \expandafter\collargs@gobbleOneB@ii + \fi +}% +% Do we have to check the tag argument (i.e.\ the environment name after |\begin|)? +\def\collargs@gobbleOneB@ii{% + \expandafter\ifx\expandafter\relax\collargs@tagarg\relax + \expandafter\collargs@gobbleOneB@vi + \else + % Yup, so let's (carefully) collect the tag argument. + \expandafter\collargs@gobbleOneB@iii + \fi +} +\def\collargs@gobbleOneB@iii{% + \collargs@grabspaces{% + \collargs@letusecollector\collargs@gobbleOneB@iv + }% +} +\def\collargs@gobbleOneB@iv#1{% + \def\collargs@temp{#1}% + \ifx\collargs@temp\collargs@tagarg + % This is the tag argument we've been waiting for! + \else + % Nope, this |\begin| belongs to someone else. + \def\collargs@begins@increment{0}% + \fi + % Whatever the result was, we have to append the gobbled group to the + % temporary toks. + \etoksapp\toks@{\collargs@grabbed@spaces\unexpanded{{#1}}}% + \collargs@init@grabspaces + \collargs@gobbleOneB@vi +} +\def\collargs@gobbleOneB@v#1{\collargs@gobbleOneB@vi} +\def\collargs@gobbleOneB@vi{% + % Store. + \etoksapp\collargsArg{\the\toks@}% + % Advance the begin-tag counter. + \advance\collargs@begins\collargs@begins@increment\relax + % Find more begin-tags, unless this was the final one. + \ifnum\collargs@begins@increment=-1 + \else + \expandafter\collargs@gobbleOneB\expandafter\collargs@empty + \fi +} +\def\collargs@gobbleUntilE@i{% + % Do we have to check the tag argument (i.e.\ the environment name after |\end|)? + \expandafter\ifx\expandafter\relax\collargs@tagarg\relax + \expandafter\collargs@gobbleUntilE@iv + \else + % Yup, so let's (carefully) collect the tag argument. + \expandafter\collargs@gobbleUntilE@ii + \fi +} +\def\collargs@gobbleUntilE@ii{% + \collargs@grabspaces{% + \collargs@letusecollector\collargs@gobbleUntilE@iii + }% +} +\def\collargs@gobbleUntilE@iii#1{% + \etoksapp\toks@{\collargs@grabbed@spaces}% + \collargs@init@grabspaces + \def\collargs@tempa{#1}% + \ifx\collargs@tempa\collargs@tagarg + % This is the tag argument we've been waiting for! + \expandafter\collargs@gobbleUntilE@iv + \else + % Nope, this |\end| belongs to someone else. Insert the end tag plus the + % tag argument, and collect until the next |\end|. + \expandafter\toksapp\expandafter\toks@\expandafter{\collargs@endtag{#1}}% + \expandafter\collargs@letusecollector\expandafter\collargs@gobbleUntilE + \fi +} +\def\collargs@gobbleUntilE@iv{% + % Invoke |\collargs@gobbleOneB| with the collected material, plus a fake + % begin-tag and a quark guard. + \ifcollargsIgnoreNesting + \expandafter\collargsArg\expandafter{\the\toks@}% + \expandafter\collargs@commandatend + \else + \expandafter\collargs@gobbleUntilE@v + \fi +} +\def\collargs@gobbleUntilE@v{% + \expanded{% + \noexpand\collargs@letusecollector\noexpand\collargs@gobbleOneB + \noexpand\collargs@empty + \the\toks@ + % Add a fake begin-tag and a quark guard. + \expandonce\collargs@begintag + \noexpand\collargs@qend + }% + \ifnum\collargs@begins<0 + \expandafter\collargs@commandatend + \else + \etoksapp\collargsArg{% + \expandonce\collargs@endtag + \expandafter\ifx\expandafter\relax\collargs@tagarg\relax\else{% + \expandonce\collargs@tagarg}\fi + }% + \toks@={}% + \expandafter\collargs@letusecollector\expandafter\collargs@gobbleUntilE + \expandafter\collargs@empty + \fi +} +% \end{macro} +% +% \begin{macro}{\collargs@e} +% Embellishments. Each embellishment counts as an argument, in the sense +% that we will execute |\collargs@appendarg|, with all the processors, for +% each embellishment separately. +\def\collargs@e{% + % We open an extra group, because |\collargs@appendarg| will close a group + % for each embellishment. + \global\collargs@fix@requestedtrue + \begingroup + \ifcollargs@verbatim + \expandafter\collargs@e@verbatim + \else + \expandafter\collargs@e@i + \fi +} +% Detokenize the embellishment tokens in the verbatim mode. +\def\collargs@e@verbatim#1{% + \expandafter\collargs@e@i\expandafter{\detokenize{#1}}% +} +% Ungroup the embellishment tokens, separating them from the rest of the +% argument specification by a dot. +\def\collargs@e@i#1{\collargs@e@ii#1.} +% We now have embellishment tokens in |#1| and the rest of the argument +% specification in |#2|. Let's grab spaces first. +\def\collargs@e@ii#1.#2.{% + \collargs@grabspaces{\collargs@e@iii#1.#2.}% +} +% What's the argument token? +\def\collargs@e@iii#1.#2.{% + \def\collargs@e@iv{\collargs@e@v#1.#2.}% + \futurelet\collargs@temp\collargs@e@iv +} +% If it is a open or close group character, we surely don't have an +% embellishment. +\def\collargs@e@v{% + \ifcat\noexpand\collargs@temp\bgroup\relax + \let\collargs@marshal\collargs@e@z + \else + \ifcat\noexpand\collargs@temp\egroup\relax + \let\collargs@marshal\collargs@e@z + \else + \let\collargs@marshal\collargs@e@vi + \fi + \fi + \collargs@marshal +} +% We borrow the ``Does |#1| occur within |#2|?'' macro from |pgfutil-common|, +% but we fix it by executing |\collargs@in@@| in a braced group. This will +% prevent an |&| in an argument to function as an alignment character; the +% minor price to pay is that we assign the conditional globally. +\newif\ifcollargs@in@ +\def\collargs@in@#1#2{% + \def\collargs@in@@##1#1##2##3\collargs@in@@{% + \ifx\collargs@in@##2\global\collargs@in@false\else\global\collargs@in@true\fi + }% + {\collargs@in@@#2#1\collargs@in@\collargs@in@@}% +} +% Let' see whether the following token, now |#3|, is an embellishment token. +\def\collargs@e@vi#1.#2.#3{% + \collargs@in@{#3}{#1}% + \ifcollargs@in@ + \expandafter\collargs@e@vii + \else + \expandafter\collargs@e@z + \fi + #1.#2.#3% +} +% |#3| is the current embellishment token. We'll collect its argument using +% |\collargs@m|, but to do that, we have to (locally) redefine +% |\collargs@appendarg| and |\collargs@|, which get called by |\collargs@m|. +\def\collargs@e@vii#1.#2.#3{% + % We'll have to execute the original |\collargs@appendarg| later, so let's + % remember it. The temporary |\collargs@appendarg| simply stores the + % collected argument into |\collargsArg| --- we'll do the processing etc.\ + % later. + \let\collargs@real@appendarg\collargs@appendarg + \def\collargs@appendarg##1{\collargsArg{##1}}% + % Once |\collargs@m| is done, it will call the redefined |\collargs@| and + % thereby get us back into this handler. + \def\collargs@{\collargs@e@viii#1.#3}% + \collargs@m#2.% +} +% The parameters here are as follows. |#1| are the embellishment tokens, and +% |#2| is the current embellishment token; these get here via our local +% redefinition of |\collargs@| in |\collargs@e@vii|. |#3| are the rest of the +% argument specification, which is put behind control sequence |\collargs@| by +% the |m| handler. +\def\collargs@e@viii#1.#2#3.{% + % Our wrapper puts the current embellishment token in front of the collected + % embellishment argument. Note that if the embellishment argument was in + % braces, |\collargs@m| has already set one wrapper (which will apply first). + \collargs@wrap{#2##1}% + % We need to get rid of the current embellishment from embellishments, not to + % catch the same embellishment twice. + \def\collargs@e@ix##1#2{\collargs@e@x##1}% + \collargs@e@ix#1.#3.% +} +% When this is executed, the input stream starts with the (remaining) +% embellishment tokens, followed by a dot, then the rest of the argument +% specification, also followed by a dot. +\def\collargs@e@x{% + % Process the argument and append it to the storage. + \expandafter\collargs@real@appendarg\expandafter{\the\collargsArg}% + % |\collargs@real@appendarg| has closed a group, so we open it again, and + % start looking for another embellishment token in the input stream. + \begingroup + \collargs@e@ii +} +% The first argument token in not an embellishment token. We finish by +% consuming the list of embellishment tokens, closing the two groups opened by +% this handler, and reexecuting the central loop. +\def\collargs@e@z#1.{\endgroup\endgroup\collargs@} +% \end{macro} +% +% \begin{macro}{\collargs@E} +% Discard the defaults and execute |e|. +\def\collargs@E#1#2{\collargs@e{#1}} +% \end{macro} +% +% +% \subsubsection{The verbatim modes} +% \label{sec:code:collargs:verbatim} +% +% \begin{macro}{\collargsVerbatim,\collargsVerb,\collargsNoVerbatim} +% These macros set the two verbatim-related +% conditionals, |\ifcollargs@verbatim| and |\ifcollargs@verbatimbraces|, +% and then call |\collargs@make@verbatim| to effect the requested +% category code changes (among other things). A group should be opened +% prior to executing either of them. After execution, they are +% redefined to minimize the effort needed to enter into another mode in +% an embedded group. Below, we first define all the possible transitions. +\let\collargs@NoVerbatimAfterNoVerbatim\relax +\def\collargs@VerbAfterNoVerbatim{% + \collargs@verbatimtrue + \collargs@verbatimbracesfalse + \collargs@make@verbatim + \collargs@after{Verb}% +} +\def\collargs@VerbatimAfterNoVerbatim{% + \collargs@verbatimtrue + \collargs@verbatimbracestrue + \collargs@make@verbatim + \collargs@after{Verbatim}% +} +\def\collargs@NoVerbatimAfterVerb{% + \collargs@verbatimfalse + \collargs@verbatimbracesfalse + \collargs@make@other@groups + \collargs@make@no@verbatim + \collargs@after{NoVerbatim}% +} +\def\collargs@VerbAfterVerb{% + \collargs@make@other@groups +} +\def\collargs@VerbatimAfterVerb{% + \collargs@verbatimbracestrue + \collargs@make@other@groups + % Process the lists of grouping characters, created by + % |\collargs@make@verbatim|, making these characters of category ``other''. + \def\collargs@do##1{\catcode##1=12 }% + \collargs@bgroups + \collargs@egroups + \collargs@after{Verbatim}% +}% +\let\collargs@NoVerbatimAfterVerbatim\collargs@NoVerbatimAfterVerb +\def\collargs@VerbAfterVerbatim{% + \collargs@verbatimbracesfalse + \collargs@make@other@groups + % Process the lists of grouping characters, created by + % |\collargs@make@verbatim|, making these characters be of their normal + % category. + \def\collargs@do##1{\catcode##1=1 }% + \collargs@bgroups + \def\collargs@do##1{\catcode##1=2 }% + \collargs@egroups + \collargs@after{Verb}% +}% +\let\collargs@VerbatimAfterVerbatim\collargs@VerbAfterVerb +% This macro expects |#1| to be the mode just entered (|Verbatim|, |Verb| or +% |NoVerbatim|), and points macros |\collargsVerbatim|, |\collargsVerb| and +% |\collargsNoVerbatim| to the appropriate transition macro. +\def\collargs@after#1{% + \letcs\collargsVerbatim{collargs@VerbatimAfter#1}% + \letcs\collargsVerb{collargs@VerbAfter#1}% + \letcs\collargsNoVerbatim{collargs@NoVerbatimAfter#1}% +} +% The first transition is always from the non-verbatim mode. +\collargs@after{NoVerbatim} +% \end{macro} +% +% \begin{macro}{\collargs@bgroups,\collargs@egroups} +% Initialize the lists of the current grouping characters used in the +% redefinitions of macros |\collargsVerbatim| and |\collargsVerb| above. +% Each entry is of form |\collargs@do|\marg{character code}. These lists +% will be populated by |\collargs@make@verbatim|. They may be local, as they +% only used within the group opened for a verbatim environment. +\def\collargs@bgroups{}% +\def\collargs@egroups{}% +% \end{macro} +% +% \begin{macro}{\collargs@cc} +% This macro recalls the category code of character |#1|. In +% \hologo{LuaTeX}, we simply look up the category code in the original +% category code table; in other engines, we have stored the original category +% code into |\collargs@cc@|\meta{character code} by +% |\collargs@make@verbatim|. (Note that |#1| is a character, not a number.) +\ifdefined\luatexversion + \def\collargs@cc#1{% + \directlua{tex.sprint(tex.getcatcode(\collargs@catcodetable@original, + \the\numexpr\expandafter`\csname#1\endcsname\relax))}% + } +\else + \def\collargs@cc#1{% + \ifcsname collargs@cc@\the\numexpr\expandafter`\csname#1\endcsname\endcsname + \csname collargs@cc@\the\numexpr\expandafter`\csname#1\endcsname\endcsname + \else + 12% + \fi + } +\fi +% \end{macro} +% +% +% \begin{macro}{\collargs@other@bgroup,\collargs@other@egroup,\collargsBraces} +% Macros |\collargs@other@bgroup| and +% |\collargs@other@egroup| hold the characters of category code ``other'' +% which will play the role of grouping characters in the full verbatim mode. +% They are usually defined when entering a verbatim mode in +% |\collargs@make@verbatim|, but may be also set by the user via +% |\collargsBraces| (it is not even necessary to select characters which +% indeed have the grouping function in the outside category code regime). +% The setting process is indirect: executing |\collargsBraces| merely sets +% |\collargs@make@other@groups|, which gets executed by the subsequent +% |\collargsVerbatim|, |\collargsVerb| or |\collargsNoVerbatim| (either +% directly or via |\collargs@make@verbatim|). +\def\collargsBraces#1{% + \expandafter\collargs@braces@i\detokenize{#1}\relax +} +\def\collargs@braces@i#1#2#3\relax{% + \def\collargs@make@other@groups{% + \def\collargs@other@bgroup{#1}% + \def\collargs@other@egroup{#2}% + }% +} +\def\collargs@make@other@groups{} +% \end{macro} +% +% \begin{macro}{\collargs@catcodetable@verbatim,\catcodetable@atletter,\collargs@catcodetable@initex} +% We declare several new catcode tables in \hologo{LuaTeX}, the most +% important one being |\collargs@catcodetable@verbatim|, where all characters +% have category code 12. We only need the other two tables in some formats: +% |\collargs@catcodetable@atletter| holds the catcode in effect at the time +% of loading the package, and |\collargs@catcodetable@initex| is the +% \hologo{iniTeX} table. +\ifdefined\luatexversion + %<*latex,context> + \newcatcodetable\collargs@catcodetable@verbatim + %<latex>\let\collargs@catcodetable@atletter\catcodetable@atletter + %<context>\newcatcodetable\collargs@catcodetable@atletter + %</latex,context> + %<*plain> + \ifdefined\collargs@catcodetable@verbatim\else + \chardef\collargs@catcodetable@verbatim=4242 + \fi + \chardef\collargs@catcodetable@atletter=% + \number\numexpr\collargs@catcodetable@verbatim+1\relax + \chardef\collargs@catcodetable@initex=% + \number\numexpr\collargs@catcodetable@verbatim+2\relax + \initcatcodetable\collargs@catcodetable@initex + %</plain> + %<plain,context>\savecatcodetable\collargs@catcodetable@atletter + \begingroup + \@firstofone{% + %<latex>\catcodetable\catcodetable@initex + %<plain>\catcodetable\collargs@catcodetable@initex + %<context>\catcodetable\inicatcodes + \catcode`\\=12 + \catcode13=12 + \catcode0=12 + \catcode32=12 + \catcode`\%=12 + \catcode127=12 + \def\collargs@do#1{\catcode#1=12 }% + \collargs@forrange{`\a}{`\z}% + \collargs@forrange{`\A}{`\Z}% + \savecatcodetable\collargs@catcodetable@verbatim + \endgroup + }% +\fi +% \end{macro} +% +% \begin{collargskey}{verbatim ranges} +% \begin{macro}{\collargsVerbatimRanges,\collargs@verbatim@ranges} +% This key and macro set the character ranges to which the verbatim mode +% will apply (in \hologo{pdfTeX} and \hologo{XeTeX}), or which will be +% inspected for grouping and comment characters (in \hologo{LuaTeX}). In +% \hologo{pdfTeX}, the default value |0-255| should really remain +% unchanged. +\collargsSet{ + verbatim ranges/.store in=\collargs@verbatim@ranges, +} +\def\collargsVerbatimRanges#1{\def\collargs@verbatim@ranges{#1}} +\def\collargs@verbatim@ranges{0-255} +% \end{macro} +% \end{collargskey} +% +% \begin{macro}{\collargs@make@verbatim} +% This macro changes the category code of all characters to ``other'' --- +% except the grouping characters in the partial verbatim mode. While doing +% that, it also stores (unless we're in \hologo{LuaTeX}) the current category +% codes into |\collargs@cc@|\meta{character code} (easily recallable by +% |\collargs@cc|), redefines the ``primary'' grouping characters +% |\collargs@make@other@bgroup| and |\collargs@make@other@egroup| if +% necessary, and ``remembers'' the grouping characters (storing them into +% |\collargs@bgroups| and |\collargs@egroups|) and the comment characters +% (storing them into |\collargs@comments|). +% +% In \hologo{LuaTeX}, we can use catcode tables, so we change the category +% codes by switching to category code table +% |\collargs@catcodetable@verbatim|. In other engines, we have to change the +% codes manually. In order to offer some flexibility in \hologo{XeTeX}, we +% perform the change for characters in |verbatim ranges|. +\ifdefined\luatexversion + \def\collargs@make@verbatim{% + \directlua{% + for from, to in string.gmatch( + "\luaescapestring{\collargs@verbatim@ranges}", + "(\collargs@percentchar d+)-(\collargs@percentchar d+)" + ) do + for char = tex.round(from), tex.round(to) do + catcode = tex.catcode[char] + % For category codes 1, 2 and 14, we have to call macros + % |\collargs@make@verbatim@bgroup|, |\collargs@make@verbatim@egroup| + % and |\collargs@make@verbatim@comment|, same as for engines other + % than \hologo{LuaTeX}. + if catcode == 1 then + tex.sprint( + \number\collargs@catcodetable@atletter, + "\noexpand\\collargs@make@verbatim@bgroup{" .. char .. "}") + elseif catcode == 2 then + tex.sprint( + \number\collargs@catcodetable@atletter, + "\noexpand\\collargs@make@verbatim@egroup{" .. char .. "}") + elseif catcode == 14 then + tex.sprint( + \number\collargs@catcodetable@atletter, + "\noexpand\\collargs@make@verbatim@comment{" .. char .. "}") + end + end + end + }% + \edef\collargs@catcodetable@original{\the\catcodetable}% + \catcodetable\collargs@catcodetable@verbatim + % Even in \hologo{LuaTeX}, we switch between the verbatim braces regimes by + % hand. + \ifcollargs@verbatimbraces + \else + \def\collargs@do##1{\catcode##1=1\relax}% + \collargs@bgroups + \def\collargs@do##1{\catcode##1=2\relax}% + \collargs@egroups + \fi + } +\else + % The non-\hologo{LuaTeX} version: + \def\collargs@make@verbatim{% + \ifdefempty\collargs@make@other@groups{}{% + % The user has executed |\collargsBraces|. We first apply that setting + % by executing macro |\collargs@make@other@groups|, and then disable our + % automatic setting of the primary grouping characters. + \collargs@make@other@groups + \def\collargs@make@other@groups{}% + \let\collargs@make@other@bgroup\@gobble + \let\collargs@make@other@egroup\@gobble + }% + % Initialize the list of current comment characters. Each entry is of + % form |\collargs@do|\marg{character code}. The definition must be global, + % because the macro will be used only once we exit the current group (by + % |\collargs@fix@cc@from@other@comment|, if at all). + \gdef\collargs@comments{}% + \let\collargs@do\collargs@make@verbatim@char + \expandafter\collargs@forranges\expandafter{\collargs@verbatim@ranges}% + } + \def\collargs@make@verbatim@char#1{% + % Store the current category code of the current character. + \ifnum\catcode#1=12 + \else + \csedef{collargs@cc@#1}{\the\catcode#1}% + \fi + \ifnum\catcode#1=1 + \collargs@make@verbatim@bgroup{#1}% + \else + \ifnum\catcode#1=2 + \collargs@make@verbatim@egroup{#1}% + \else + \ifnum\catcode#1=14 + \collargs@make@verbatim@comment{#1}% + \fi + % Change the category code of the current character (including the + % comment characters). + \ifnum\catcode#1=12 + \else + \catcode#1=12\relax + \fi + \fi + \fi + } +\fi +% \end{macro} +% +% \begin{macro}{\collargs@make@verbatim@bgroup} +% This macro changes the category of +% the opening group character to ``other'', but only in the full verbatim +% mode. Next, it populates |\collargs@bgroups|, to facilitate the potential +% transition into the other verbatim mode. Finally, it executes +% |\collargs@make@other@bgroup|, which stores the ``other'' variant of the +% current character into |\collargs@other@bgroup|, and automatically disables +% itself, so that it is only executed for the first encountered opening group +% character --- unless it was already |\relax|ed at the top of +% |\collargs@make@verbatim| as a consequence of the user executing +% |\collargsBraces|. +\def\collargs@make@verbatim@bgroup#1{% + \ifcollargs@verbatimbraces + \catcode#1=12\relax + \fi + \appto\collargs@bgroups{\collargs@do{#1}}% + \collargs@make@other@bgroup{#1}% +} +\def\collargs@make@other@bgroup#1{% + \collargs@make@char\collargs@other@bgroup{#1}{12}% + \let\collargs@make@other@bgroup\@gobble +} +% \end{macro} +% +% \begin{macro}{\collargs@make@verbatim@egroup} +% Ditto for the closing group character. +\def\collargs@make@verbatim@egroup#1{% + \ifcollargs@verbatimbraces + \catcode#1=12\relax + \fi + \appto\collargs@egroups{\collargs@do{#1}}% + \collargs@make@other@egroup{#1}% +} +\def\collargs@make@other@egroup#1{% + \collargs@make@char\collargs@other@egroup{#1}{12}% + \let\collargs@make@other@egroup\@gobble +} +% \end{macro} +% +% \begin{macro}{\collargs@make@verbatim@comment} +% This macro populates +% |\collargs@make@comments@other|. +\def\collargs@make@verbatim@comment#1{% + \gappto\collargs@comments{\collargs@do{#1}}% +} +% \end{macro} +% +% \begin{macro}{\collargs@make@no@verbatim} +% This macro switches back to the non-verbatim mode: in \hologo{LuaTeX}, by +% switching to the original catcode table; in other engines, by recalling the +% stored category codes. +\ifdefined\luatexversion + \def\collargs@make@no@verbatim{% + \catcodetable\collargs@catcodetable@original\relax + }% +\else +\def\collargs@make@no@verbatim{% + \let\collargs@do\collargs@make@no@verbatim@char + \expandafter\collargs@forranges\expandafter{\collargs@verbatim@ranges}% +} +\fi +\def\collargs@make@no@verbatim@char#1{% + % The original category code of a characted was stored into + % |\collargs@cc@|\meta{character code} by |\collargs@make@verbatim|. (We don't + % use |\collargs@cc|, because we have a number.) + \ifcsname collargs@cc@#1\endcsname + \catcode#1=\csname collargs@cc@#1\endcsname\relax + % We don't have to restore category code 12. + \fi +} +% \end{macro} +% +% +% \subsubsection{Transition between the verbatim and the non-verbatim mode} +% \label{sec:code:collargs:fix} +% +% At the transition from verbatim to non-verbatim mode, and vice versa, we +% sometimes have to fix the category code of the next argument token. This +% happens when we have an optional argument type in one mode followed by an +% argument type in another mode, but the optional argument is absent, or when +% an optional, but absent, verbatim argument is the last argument in the +% specification. The problem arises because the presence of optional arguments +% is determined by looking ahead in the input stream; when the argument is +% absent, this means that we have fixed the category code of the next token. +% CollArgs addresses this issue by noting the situations where a token receives +% the wrong category code, and then does its best to replace that token with +% the same character of the appropriate category code. +% +% \begin{macro}{\ifcollargs@fix@requested} +% This conditional is set, globally, +% by the optional argument handlers when the argument is in fact absent, +% and reset in the central loop after applying the fix if necessary. +\newif\ifcollargs@fix@requested +% \end{macro} +% +% \begin{macro}{\collargs@fix} +% This macro selects the fixer appropriate to the transition between the +% previous verbatim mode (determined by |\ifcollargs@last@verbatim| and +% |\ifcollargs@last@verbatimbraces|) and the current verbatim mode (which +% is determined by macros |\ifcollargs@verbatim| and +% |\ifcollargs@verbatimbraces|); if the category code fix was not +% requested (for this, we check |\ifcollargs@fix@requested|), the +% macro simply executes the next-code given as the sole argument. +% The name of the fixer macro has the form +% |\collargs@fix@|\meta{last mode}|to|\meta{current mode}, where +% the modes are given by mnemonic codes: |V| = full verbatim, |v| = +% partial verbatim, and |N| = non-verbatim. +\long\def\collargs@fix#1{% + % Going through |\edef| + |\unexpanded| avoids doubling the hashes. + \edef\collargs@fix@next{\unexpanded{#1}}% + \ifcollargs@fix@requested + \letcs\collargs@action{collargs@fix@% + \ifcollargs@last@verbatim + \ifcollargs@last@verbatimbraces V\else v\fi + \else + N% + \fi + to% + \ifcollargs@verbatim + \ifcollargs@verbatimbraces V\else v\fi + \else + N% + \fi + }% + \else + \let\collargs@action\collargs@fix@next + \fi + \collargs@action +} +% \end{macro} +% +% \begin{macro}{\collargs@fix@NtoN,\collargs@fix@vtov,\collargs@fix@VtoV} +% \indentmacrocode +% Nothing to do, continue with the next-code. +\def\collargs@fix@NtoN{\collargs@fix@next} +\let\collargs@fix@vtov\collargs@fix@NtoN +\let\collargs@fix@VtoV\collargs@fix@NtoN +% \end{macro} +% +% \begin{macro}{\collargs@fix@Ntov} +% We do nothing for the group tokens; for other tokens, we redirect to +% |\collargs@fix@NtoV|. +\def\collargs@fix@Ntov{% + \futurelet\collargs@temp\collargs@fix@cc@to@other@ii +} +\def\collargs@fix@cc@to@other@ii{% + \ifcat\noexpand\collargs@temp\bgroup + \let\collargs@action\collargs@fix@next + \else + \ifcat\noexpand\collargs@temp\egroup + \let\collargs@action\collargs@fix@next + \else + \let\collargs@action\collargs@fix@NtoV + \fi + \fi + \collargs@action +} +% \end{macro} +% +% \begin{macro}{\collargs@fix@NtoV} +% The only complication here is that we might be in front of a control +% sequence that was a result of a previous fix in the other direction. +\def\collargs@fix@NtoV{% + \ifcollargs@double@fix + \ifcollargs@in@second@fix + \expandafter\expandafter\expandafter\collargs@fix@NtoV@secondfix + \else + \expandafter\expandafter\expandafter\collargs@fix@NtoV@onemore + \fi + \else + \expandafter\collargs@fix@NtoV@singlefix + \fi +} +% This is the usual situation of a single fix. We just use |\string| on +% the next token here (but note that some situations can't be saved: noone +% can bring a comment back to life, or distinguish a newline and a space) +\def\collargs@fix@NtoV@singlefix{% + \expandafter\collargs@fix@next\string +} +% If this is the first fix of two, we know |#1| is a control sequence, so it is +% safe to grab it. +\def\collargs@fix@NtoV@onemore#1{% + \collargs@do@one@more@fix{% + \expandafter\collargs@fix@next\string#1% + }% +} +% If this is the second fix of the two, we have to check whether the next token +% is a control sequence, and if it is, we need to remember it. Afterwards, we +% redirect to the single-fix. +\def\collargs@fix@NtoV@secondfix{% + \if\noexpand\collargs@temp\relax + \expandafter\collargs@fix@NtoV@secondfix@i + \else + \expandafter\collargs@fix@NtoV@singlefix + \fi +} +\def\collargs@fix@NtoV@secondfix@i#1{% + \gdef\collargs@double@fix@cs@ii{#1}% + \collargs@fix@NtoV@singlefix#1% +} +% \end{macro} +% +% \begin{macro}{\collargs@fix@vtoN} +% Do nothing for the grouping tokens, redirect to |\collargs@fix@VtoN| for +% other tokens. +\def\collargs@fix@vtoN{% + \futurelet\collargs@token\collargs@fix@vtoN@i +} +\def\collargs@fix@vtoN@i{% + \ifcat\noexpand\collargs@token\bgroup + \expandafter\collargs@fix@next + \else + \ifcat\noexpand\collargs@token\egroup + \expandafter\expandafter\expandafter\collargs@fix@next + \else + \expandafter\expandafter\expandafter\collargs@fix@VtoN + \fi + \fi +} +% \end{macro} +% +% \begin{macro}{\collargs@fix@vtoV} +% Redirect group tokens to |\collargs@fix@NtoV|, and do nothing for other +% tokens. +\def\collargs@fix@vtoV{% + \futurelet\collargs@token\collargs@fix@vtoV@i +} +\def\collargs@fix@vtoV@i{% + \ifcat\noexpand\collargs@token\bgroup + \expandafter\collargs@fix@NtoV + \else + \ifcat\noexpand\collargs@token\egroup + \expandafter\expandafter\expandafter\collargs@fix@NtoV + \else + \expandafter\expandafter\expandafter\collargs@fix@next + \fi + \fi +} +% \end{macro} +% +% \begin{macro}{\collargs@fix@Vtov} +% Redirect group tokens to |\collargs@fix@VtoN|, and do nothing for other +% tokens. |#1| is surely of category 12, so we can safely grab it. +\def\collargs@fix@catcode@of@braces@fromverbatim#1{% + \ifnum\catcode`#1=1 + \expandafter\collargs@fix@VtoN + \expandafter#1% + \else + \ifnum\catcode`#1=2 + \expandafter\expandafter\expandafter\collargs@fix@cc@VtoN + \expandafter\expandafter\expandafter#1% + \else + \expandafter\expandafter\expandafter\collargs@fix@next + \fi + \fi +} +% \end{macro} +% +% \begin{macro}{\collargs@fix@VtoN} +% This is the only complicated part. Control sequences and comments (but not +% grouping characters!) require special attention. We're fine to grab the +% token right away, as we know it is of category 12. +\def\collargs@fix@VtoN#1{% + \ifnum\catcode`#1=0 + \expandafter\collargs@fix@VtoN@escape + \else + \ifnum\catcode`#1=14 + \expandafter\expandafter\expandafter\collargs@fix@VtoN@comment + \else + \expandafter\expandafter\expandafter\collargs@fix@VtoN@token + \fi + \fi + #1% +} +% \end{macro} +% +% \begin{macro}{\collargs@fix@VtoN@token} +% We create a new character with the current +% category code behing the next-code. This works even for grouping +% characters. +\def\collargs@fix@VtoN@token#1{% + \collargs@insert@char\collargs@fix@next{`#1}{\the\catcode`#1}% +} +% \end{macro} +% +% \begin{macro}{\collargs@fix@VtoN@comment} +% This macro defines a macro which will, +% when placed at a comment character, remove the tokens until the end of the +% line. The code is adapted from the TeX.SE answer at +% \url(https://){tex.stackexchange.com/a/10454/16819} by Bruno Le Floch. +\def\collargs@defcommentstripper#1#2{% + % We chuck a parameter into the following definition, to grab the (verbatim) + % comment character. This is why this macro must be executed precisely + % before the (verbatim) comment character. + \def#1##1{% + \begingroup% + \escapechar=`\\% + \catcode\endlinechar=\active% + % We assign the ``other'' category code to comment characters. Without + % this, comment characters behind the first one make trouble: there would + % be no |^||^M| at the end of the line, so the comment stripper would + % gobble the following line as well; in fact, it would gobble all + % subsequent lines containing a comment character. We also make sure to + % change the category code of \emph{all} comment characters, even if there + % is usually just one. + \def\collargs@do####1{\catcode####1=12 }% + \collargs@comments + \csname\string#1\endcsname% + }% + \begingroup% + \escapechar=`\\% + \lccode`\~=\endlinechar% + \lowercase{% + \expandafter\endgroup + \expandafter\def\csname\string#1\endcsname##1~% + }{% + % I have removed |\space| from the end of the following line. We don't + % want it for our application. + \endgroup#2% + }% +} +\collargs@defcommentstripper\collargs@fix@VtoN@comment{% + \collargs@fix@next +} +% We don't need the generator any more. +\let\collargs@defcommentstripper\relax +% \end{macro} +% +% \begin{macro}{\collargs@fix@VtoN@escape} +% An escape character of category code 12 +% is the most challenging --- and we won't get things completely right --- as +% we have swim further down the input stream to create a control sequence. +% This macro will throw away the verbatim escape character |#1|. +\def\collargs@fix@VtoN@escape#1{% + \ifcollargs@double@fix + % We need to do things in a special way if we're in the double-fix + % situation triggered by the previous fixing of a control sequence + % (probably this very one). In that case, we can't collect it in the usual + % way because the entire control sequence is spelled out in verbatim. + \expandafter\collargs@fix@VtoN@escape@d + \else + % This here is the usual situation where the escape character was tokenized + % verbatim, but the control sequence name itself will be collected (right + % away) in the non-verbatim regime. + \expandafter\collargs@fix@VtoN@escape@i + \fi +} +\def\collargs@fix@VtoN@escape@i{% + % The sole character forming a control symbol name may be of any category. + % Temporarily redefining the category codes of the craziest characters allows + % |\collargs@fix@VtoN@escape@ii| to simply grab the following + % character. + \begingroup + \catcode`\\=12 + \catcode`\{=12 + \catcode`\}=12 + \catcode`\ =12 + \collargs@fix@VtoN@escape@ii +} +% The argument is the first character of the control sequence name. +\def\collargs@fix@VtoN@escape@ii#1{% + \endgroup + \def\collargs@csname{#1}% + % Only if |#1| is a letter may the control sequence name continue. + \ifnum\catcode`#1=11 + \expandafter\collargs@fix@VtoN@escape@iii + \else + % In the case of a control space, we have to throw away the following + % spaces. + \ifnum\catcode`#1=10 + \expandafter\expandafter\expandafter\collargs@fix@VtoN@escape@s + \else + % We have a control symbol. That means that we haven't peeked ahead and + % can thus skip |\collargs@fix@VtoN@escape@z|. + \expandafter\expandafter\expandafter\collargs@fix@VtoN@escape@z@i + \fi + \fi +} +% We still have to collect the rest of the control sequence name. Braces have +% their usual meaning again, so we have to check for them explicitly (and bail +% out if we stumble upon them). +\def\collargs@fix@VtoN@escape@iii{% + \futurelet\collargs@temp\collargs@fix@VtoN@escape@iv +} +\def\collargs@fix@VtoN@escape@iv{% + \ifcat\noexpand\collargs@temp\bgroup + \let\collargs@action\collargs@fix@VtoN@escape@z + \else + \ifcat\noexpand\collargs@temp\egroup + \let\collargs@action\collargs@fix@VtoN@escape@z + \else + \expandafter\ifx\space\collargs@temp + \let\collargs@action\collargs@fix@VtoN@escape@s + \else + \let\collargs@action\collargs@fix@VtoN@escape@v + \fi + \fi + \fi + \collargs@action +} +% If we have a letter, store it and loop back, otherwise finish. +\def\collargs@fix@VtoN@escape@v#1{% + \ifcat\noexpand#1a% + \appto\collargs@csname{#1}% + \expandafter\collargs@fix@VtoN@escape@iii + \else + \expandafter\collargs@fix@VtoN@escape@z\expandafter#1% + \fi +} +% Throw away the following spaces. +\def\collargs@fix@VtoN@escape@s{% + \futurelet\collargs@temp\collargs@fix@VtoN@escape@s@i +} +\def\collargs@fix@VtoN@escape@s@i{% + \expandafter\ifx\space\collargs@temp + \expandafter\collargs@fix@VtoN@escape@s@ii + \else + \expandafter\collargs@fix@VtoN@escape@z + \fi +} +\def\collargs@fix@VtoN@escape@s@ii{% + \expandafter\collargs@fix@VtoN@escape@z\romannumeral-0% +} +% Once we have collected the control sequence name into |\collargs@csname|, we +% will create the control sequence behind the next-code. However, we have two +% complications. The minor one is that |\csname| defines an unexisting control +% sequence to mean |\relax|, so we have to check whether the control sequence +% we will create is defined, and if not, ``undefine'' it in advance. +% ^^A todo: do nicer +\def\collargs@fix@VtoN@escape@z@i{% + \collargs@fix@VtoN@escape@z@maybe@undefine@cs@begin + \collargs@fix@VtoN@escape@z@ii +}% +\def\collargs@fix@VtoN@escape@z@maybe@undefine@cs@begin{% + \ifcsname\collargs@csname\endcsname + \@tempswatrue + \else + \@tempswafalse + \fi +} +\def\collargs@fix@VtoN@escape@z@maybe@undefine@cs@end{% + \if@tempswa + \else + \cslet{\collargs@csname}\collargs@undefined + \fi +} +\def\collargs@fix@VtoN@escape@z@ii{% + \expandafter\collargs@fix@VtoN@escape@z@maybe@undefine@cs@end + \expandafter\collargs@fix@next\csname\collargs@csname\endcsname +} +% The second complication is much greater, but it only applies to control words +% and spaces, and that's why control symbols went directly to the macro above. +% Control words and spaces will only get there via a detour through the +% following macro. +% +% The problem is that collecting the control word\slash space name peeked ahead +% in the stream, so the character following the control sequence (name) is +% already tokenized. We will (at least partially) address this by requesting a +% ``double-fix'': until the control sequence we're about to create is consumed +% into some argument, each category code fix will fix two ``tokens'' rather +% than one. +\def\collargs@fix@VtoN@escape@z{% + \collargs@if@one@more@fix{% + % Some previous fixing has requested a double fix, so let's do it. + % Afterwards, redirect to the control symbol code + % |\collargs@fix@VtoN@escape@z@i|. It will surely use the correct + % |\collargs@csname| because we do the second fix in a group. + \collargs@do@one@more@fix\collargs@fix@VtoN@escape@z@i + }{% + % Remember the collected control sequence. It will be used in + % |\collargs@cancel@double@fix|. + \collargs@fix@VtoN@escape@z@maybe@undefine@cs@begin + \xdef\collargs@double@fix@cs@i{\expandonce{\csname\collargs@csname\endcsname}}% + \collargs@fix@VtoN@escape@z@maybe@undefine@cs@end + % Request the double-fix. + \global\collargs@double@fixtrue + % The complication is addressed, redirect to the control symbol finish. + \collargs@fix@VtoN@escape@z@ii + }% +} +% When we have to ``redo'' a control sequence, because it was ping-ponged back +% into the verbatim mode, we cannot collect it by +% |\collargs@fix@VtoN@escape@i|, because it is spelled out entirely in +% verbatim. However, we have seen this control sequence before, and remembered +% it, so we'll simply grab it. Another complication is that we might be either +% at the ``first'' control sequence, whose fixing created all these double-fix +% trouble, or at the ``second'' control sequence, if the first one was +% immediately followed by another one. But we have remembered both of them: +% the first one in |\collargs@fix@VtoN@escape@z|, the second one in +% |\collargs@fix@NtoV@secondfix|. +\def\collargs@fix@VtoN@escape@d{% + \ifcollargs@in@second@fix + \expandafter\collargs@fix@VtoN@escape@d@i + \expandafter\collargs@double@fix@cs@ii + \else + \expandafter\collargs@fix@VtoN@escape@d@i + \expandafter\collargs@double@fix@cs@i + \fi +} +% We have the contents of either |\collargs@double@fix@cs@i| or +% |\collargs@double@fix@cs@ii| here, a control sequence in both cases. +\def\collargs@fix@VtoN@escape@d@i#1{% + \expandafter\expandafter\expandafter\collargs@fix@VtoN@escape@d@ii + \expandafter\string#1\relax +} +% We have the verbatimized control sequence name in |#2| (|#1| is the escape +% character). By storing it into |\collargs@csname|, we pretend we have +% collected it. By defining and executing |\collargs@fix@VtoN@escape@d@iii|, +% we actually gobble it from the input stream. Finally, we reroute to +% |\collargs@fix@VtoN@escape@z|. +\def\collargs@fix@VtoN@escape@d@ii#1#2\relax{% + \def\collargs@csname{#2}% + \def\collargs@fix@VtoN@escape@d@iii#2{% + \collargs@fix@VtoN@escape@z + }% + \collargs@fix@VtoN@escape@d@iii +} +% This conditional signals a double-fix request. It should be always set +% globally, because it is cleared by |\collargs@double@fixfalse| in a group. +\newif\ifcollargs@double@fix +% This conditional signals that we're currently performing the second fix. +\newif\ifcollargs@in@second@fix +% Inspect the two conditionals above to decide whether we have to perform +% another fix: if so, execute the first argument, otherwise the second one. +% This macro is called only from |\collargs@fix@VtoN@escape@z| and +% |\collargs@fix@NtoV|, because these are the only two places where we might +% need the second fix, ping-ponging a control sequence between the verbatim and +% the non-verbatim mode. +\def\collargs@if@one@more@fix{% + \ifcollargs@double@fix + \ifcollargs@in@second@fix + \expandafter\expandafter\expandafter\@secondoftwo + \else + \expandafter\expandafter\expandafter\@firstoftwo + \fi + \else + \expandafter\@secondoftwo + \fi +} +\def\collargs@do@one@more@fix#1{% + % We perform the second fix in a group, signalling that we're performing + % it. + \begingroup + \collargs@in@second@fixtrue + % Reexecute the fixing routine, at the end, close the group and execute + % the given code afterwards. + \collargs@fix{% + \endgroup + #1% + }% +} +% This macro is called from |\collargs@appendarg| to cancel the double-fix +% request. +\def\collargs@cancel@double@fix{% + % |\collargs@appendarg| is only executed when something was actually + % consumed. We thus know that at least one of the problematic ``tokens'' is + % gone, so the double fix is not necessary anymore. + \global\collargs@double@fixfalse + % What we have to figure out, still, is whether both problematic ``tokens'' + % we consumed. If so, no more fixing is required. But if only one of them + % was consumed, we need to request the normal, single, fix for the remaining + % ``token''. + \begingroup + % This will attach the delimiters directly to the argument, so we'll see what + % was actually consumed. + \collargs@process@arg + % We compare what was consumed when collecting the current argument with the + % control word that triggered double-fixing. If they match, only the + % offending control word was consumed, so we need to set the fix request to + % true for the following token. + \edef\collargs@temp{\the\collargsArg}% + \edef\collargs@tempa{\expandafter\string\collargs@double@fix@cs@i}% + \ifx\collargs@temp\collargs@tempa + \global\collargs@fix@requestedtrue + \fi + \endgroup +} +% \end{macro} +% +% +% \begin{macro}{\collargs@insert@char,\collargs@make@char} +% These macros create a character of character code |#2| and category code +% |#3|. The first macro inserts it into the stream behind the code in |#1|; +% the second one defines the control sequence in |#1| to hold the created +% character (clearly, it should not be used for categories 1 and 2). +% +% We use the facilities of \hologo{LuaTeX}, \hologo{XeTeX} and \hologo{LaTeX} +% where possible. In the end, we only have to implement our own macros for +% plain \hologo{pdfTeX}. +%<!context>\ifdefined\luatexversion + \def\collargs@insert@char#1#2#3{% + \edef\collargs@temp{\unexpanded{#1}}% + \expandafter\collargs@temp\directlua{% + tex.cprint(\number#3,string.char(\number#2))}% + }% + \def\collargs@make@char#1#2#3{% + \edef#1{\directlua{tex.cprint(\number#3,string.char(\number#2))}}% + }% +%<*!context> +\else + \ifdefined\XeTeXversion + \def\collargs@insert@char#1#2#3{% + \edef\collargs@temp{\unexpanded{#1}}% + \expandafter\collargs@temp\Ucharcat #2 #3 + }% + \def\collargs@make@char#1#2#3{% + \edef#1{\Ucharcat#2 #3}% + }% + \else + %<*latex> + \ExplSyntaxOn + \def\collargs@insert@char#1#2#3{% + \edef\collargs@temp{\unexpanded{#1}}% + \expandafter\expandafter\expandafter\collargs@temp\char_generate:nn{#2}{#3}% + }% + \def\collargs@make@char#1#2#3{% + \edef#1{\char_generate:nn{#2}{#3}}% + }% + \ExplSyntaxOff + %</latex> + %<*plain> + % The implementation is inspired by |expl3|'s implementation of + % |\char_generate:nn|, but our implementation is not expandable, for + % simplicity. We first store an (arbitrary) character |^||^@| of category + % code $n$ into control sequence |\collargs@charofcat@|$n$, for every + % (implementable) category code. + \begingroup + \catcode`\^^@=1 \csgdef{collargs@charofcat@1}{% + \noexpand\expandafter^^@\iffalse}\fi} + \catcode`\^^@=2 \csgdef{collargs@charofcat@2}{\iffalse{\fi^^@} + \catcode`\^^@=3 \csgdef{collargs@charofcat@3}{^^@} + \catcode`\^^@=4 \csgdef{collargs@charofcat@4}{^^@} + % As we have grabbed the spaces already, a remaining newline should surely be + % fixed into a |\par|. + \csgdef{collargs@charofcat@5}{\par} + \catcode`\^^@=6 \csxdef{collargs@charofcat@6}{\unexpanded{^^@}} + \catcode`\^^@=7 \csgdef{collargs@charofcat@7}{^^@} + \catcode`\^^@=8 \csgdef{collargs@charofcat@8}{^^@} + \csgdef{collargs@charofcat@10}{\noexpand\space} + \catcode`\^^@=11 \csgdef{collargs@charofcat@11}{^^@} + \catcode`\^^@=12 \csgdef{collargs@charofcat@12}{^^@} + \catcode`\^^@=13 \csgdef{collargs@charofcat@13}{^^@} + \endgroup + \def\collargs@insert@char#1#2#3{% + % Temporarily change the lowercase code of |^||^@| to the requested character + % |#2|. + \begingroup + \lccode`\^^@=#2\relax + % We'll have to close the group before executing the next-code. + \def\collargs@temp{\endgroup#1}% + % |\collargs@charofcat@|\meta{requested category code} is f-expanded first, + % leaving us to lowercase |\expandafter\collargs@temp^||^@|. Clearly, + % lowercasing |\expandafter\collargs@temp| is a no-op, but lowercasing + % |^||^@| gets us the requested character of the requested category. + % |\expandafter| is executed next, and this gets rid of the conditional for + % category codes 1 and 2. + \expandafter\lowercase\expandafter{% + \expandafter\expandafter\expandafter\collargs@temp + \romannumeral-`0\csname collargs@charofcat@\the\numexpr#3\relax\endcsname + }% + } + % This macro cannot not work for category code 6 (because we assign the + % result to a macro), but no matter, we only use it for category code 12 + % anyway. + \def\collargs@make@char#1#2#3{% + \begingroup + \lccode`\^^@=#2\relax + % Define |\collargs@temp| to hold |^||^@| of the appropriate category. + \edef\collargs@temp{% + \csname collargs@charofcat@\the\numexpr#3\relax\endcsname}% + % Preexpand the second |\collargs@temp| so that we lowercase + % |\def\collargs@temp{^||^@}|, with |^||^@| of the appropriate category. + \expandafter\lowercase\expandafter{% + \expandafter\def\expandafter\collargs@temp\expandafter{\collargs@temp}% + }% + \expandafter\endgroup + \expandafter\def\expandafter#1\expandafter{\collargs@temp}% + } + %</plain> + \fi +\fi +%</!context> +% \end{macro} +% +%<plain>\resetatcatcode +%<context>\stopmodule +%<context>\protect +% \end{macrocode} +% +% Local Variables: +% TeX-engine: luatex +% TeX-master: "doc/memoize-code.tex" +% TeX-auto-save: nil +% End:
\ No newline at end of file diff --git a/macros/generic/collargs/collargs.ins b/macros/generic/collargs/collargs.ins new file mode 100644 index 0000000000..a8673b6edc --- /dev/null +++ b/macros/generic/collargs/collargs.ins @@ -0,0 +1,33 @@ +%% collargs.ins +%% +%% This file is a part of CollArgs, a TeX package providing a command which can +%% determine the argument scope of any command whose argument structure +%% conforms to xparse's argument specification, available at +%% https://ctan.org/pkg/advice and https://github.com/sasozivanovic/advice. +%% +%% Copyright (c) 2023- Saso Zivanovic <saso.zivanovic@guest.arnes.si> +%% (Sa\v{s}o \v{Z}ivanovi\'{c}) +%% +%% This work may be distributed and/or modified under the conditions of the +%% LaTeX Project Public License, either version 1.3 of this license or (at your +%% option) any later version. The latest version of this license is in +%% http://www.latex-project.org/lppl.txt and version 1.3 or later is part of +%% all distributions of LaTeX version 2005/12/01 or later. +%% +%% This work has the LPPL maintenance status `maintained'. +%% The Current Maintainer of this work is Saso Zivanovic. +%% +%% The files belonging to this work and covered by LPPL are listed in +%% (<texmf>/doc/generic/collargs/)FILES. +%% +\input docstrip.tex +\keepsilent +\preamble +\endpreamble +\askforoverwritefalse +\generate{% + \file{collargs.sty}{\from{collargs.dtx}{latex}}% + \file{collargs.tex}{\from{collargs.dtx}{plain}}% + \file{t-collargs.tex}{\from{collargs.dtx}{context}}% +} +\endbatchfile diff --git a/macros/generic/etoolbox-generic/LICENCE b/macros/generic/etoolbox-generic/LICENCE new file mode 100644 index 0000000000..1b57559a83 --- /dev/null +++ b/macros/generic/etoolbox-generic/LICENCE @@ -0,0 +1,416 @@ +The LaTeX Project Public License +=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +LPPL Version 1.3c 2008-05-04 + +Copyright 1999 2002-2008 LaTeX3 Project + Everyone is allowed to distribute verbatim copies of this + license document, but modification of it is not allowed. + + +PREAMBLE +======== + +The LaTeX Project Public License (LPPL) is the primary license under +which the LaTeX kernel and the base LaTeX packages are distributed. + +You may use this license for any work of which you hold the copyright +and which you wish to distribute. This license may be particularly +suitable if your work is TeX-related (such as a LaTeX package), but +it is written in such a way that you can use it even if your work is +unrelated to TeX. + +The section `WHETHER AND HOW TO DISTRIBUTE WORKS UNDER THIS LICENSE', +below, gives instructions, examples, and recommendations for authors +who are considering distributing their works under this license. + +This license gives conditions under which a work may be distributed +and modified, as well as conditions under which modified versions of +that work may be distributed. + +We, the LaTeX3 Project, believe that the conditions below give you +the freedom to make and distribute modified versions of your work +that conform with whatever technical specifications you wish while +maintaining the availability, integrity, and reliability of +that work. If you do not see how to achieve your goal while +meeting these conditions, then read the document `cfgguide.tex' +and `modguide.tex' in the base LaTeX distribution for suggestions. + + +DEFINITIONS +=========== + +In this license document the following terms are used: + + `Work' + Any work being distributed under this License. + + `Derived Work' + Any work that under any applicable law is derived from the Work. + + `Modification' + Any procedure that produces a Derived Work under any applicable + law -- for example, the production of a file containing an + original file associated with the Work or a significant portion of + such a file, either verbatim or with modifications and/or + translated into another language. + + `Modify' + To apply any procedure that produces a Derived Work under any + applicable law. + + `Distribution' + Making copies of the Work available from one person to another, in + whole or in part. Distribution includes (but is not limited to) + making any electronic components of the Work accessible by + file transfer protocols such as FTP or HTTP or by shared file + systems such as Sun's Network File System (NFS). + + `Compiled Work' + A version of the Work that has been processed into a form where it + is directly usable on a computer system. This processing may + include using installation facilities provided by the Work, + transformations of the Work, copying of components of the Work, or + other activities. Note that modification of any installation + facilities provided by the Work constitutes modification of the Work. + + `Current Maintainer' + A person or persons nominated as such within the Work. If there is + no such explicit nomination then it is the `Copyright Holder' under + any applicable law. + + `Base Interpreter' + A program or process that is normally needed for running or + interpreting a part or the whole of the Work. + + A Base Interpreter may depend on external components but these + are not considered part of the Base Interpreter provided that each + external component clearly identifies itself whenever it is used + interactively. Unless explicitly specified when applying the + license to the Work, the only applicable Base Interpreter is a + `LaTeX-Format' or in the case of files belonging to the + `LaTeX-format' a program implementing the `TeX language'. + + + +CONDITIONS ON DISTRIBUTION AND MODIFICATION +=========================================== + +1. Activities other than distribution and/or modification of the Work +are not covered by this license; they are outside its scope. In +particular, the act of running the Work is not restricted and no +requirements are made concerning any offers of support for the Work. + +2. You may distribute a complete, unmodified copy of the Work as you +received it. Distribution of only part of the Work is considered +modification of the Work, and no right to distribute such a Derived +Work may be assumed under the terms of this clause. + +3. You may distribute a Compiled Work that has been generated from a +complete, unmodified copy of the Work as distributed under Clause 2 +above, as long as that Compiled Work is distributed in such a way that +the recipients may install the Compiled Work on their system exactly +as it would have been installed if they generated a Compiled Work +directly from the Work. + +4. If you are the Current Maintainer of the Work, you may, without +restriction, modify the Work, thus creating a Derived Work. You may +also distribute the Derived Work without restriction, including +Compiled Works generated from the Derived Work. Derived Works +distributed in this manner by the Current Maintainer are considered to +be updated versions of the Work. + +5. If you are not the Current Maintainer of the Work, you may modify +your copy of the Work, thus creating a Derived Work based on the Work, +and compile this Derived Work, thus creating a Compiled Work based on +the Derived Work. + +6. If you are not the Current Maintainer of the Work, you may +distribute a Derived Work provided the following conditions are met +for every component of the Work unless that component clearly states +in the copyright notice that it is exempt from that condition. Only +the Current Maintainer is allowed to add such statements of exemption +to a component of the Work. + + a. If a component of this Derived Work can be a direct replacement + for a component of the Work when that component is used with the + Base Interpreter, then, wherever this component of the Work + identifies itself to the user when used interactively with that + Base Interpreter, the replacement component of this Derived Work + clearly and unambiguously identifies itself as a modified version + of this component to the user when used interactively with that + Base Interpreter. + + b. Every component of the Derived Work contains prominent notices + detailing the nature of the changes to that component, or a + prominent reference to another file that is distributed as part + of the Derived Work and that contains a complete and accurate log + of the changes. + + c. No information in the Derived Work implies that any persons, + including (but not limited to) the authors of the original version + of the Work, provide any support, including (but not limited to) + the reporting and handling of errors, to recipients of the + Derived Work unless those persons have stated explicitly that + they do provide such support for the Derived Work. + + d. You distribute at least one of the following with the Derived Work: + + 1. A complete, unmodified copy of the Work; + if your distribution of a modified component is made by + offering access to copy the modified component from a + designated place, then offering equivalent access to copy + the Work from the same or some similar place meets this + condition, even though third parties are not compelled to + copy the Work along with the modified component; + + 2. Information that is sufficient to obtain a complete, + unmodified copy of the Work. + +7. If you are not the Current Maintainer of the Work, you may +distribute a Compiled Work generated from a Derived Work, as long as +the Derived Work is distributed to all recipients of the Compiled +Work, and as long as the conditions of Clause 6, above, are met with +regard to the Derived Work. + +8. The conditions above are not intended to prohibit, and hence do not +apply to, the modification, by any method, of any component so that it +becomes identical to an updated version of that component of the Work as +it is distributed by the Current Maintainer under Clause 4, above. + +9. Distribution of the Work or any Derived Work in an alternative +format, where the Work or that Derived Work (in whole or in part) is +then produced by applying some process to that format, does not relax or +nullify any sections of this license as they pertain to the results of +applying that process. + +10. a. A Derived Work may be distributed under a different license + provided that license itself honors the conditions listed in + Clause 6 above, in regard to the Work, though it does not have + to honor the rest of the conditions in this license. + + b. If a Derived Work is distributed under a different license, that + Derived Work must provide sufficient documentation as part of + itself to allow each recipient of that Derived Work to honor the + restrictions in Clause 6 above, concerning changes from the Work. + +11. This license places no restrictions on works that are unrelated to +the Work, nor does this license place any restrictions on aggregating +such works with the Work by any means. + +12. Nothing in this license is intended to, or may be used to, prevent +complete compliance by all parties with all applicable laws. + + +NO WARRANTY +=========== + +There is no warranty for the Work. Except when otherwise stated in +writing, the Copyright Holder provides the Work `as is', without +warranty of any kind, either expressed or implied, including, but not +limited to, the implied warranties of merchantability and fitness for a +particular purpose. The entire risk as to the quality and performance +of the Work is with you. Should the Work prove defective, you assume +the cost of all necessary servicing, repair, or correction. + +In no event unless required by applicable law or agreed to in writing +will The Copyright Holder, or any author named in the components of the +Work, or any other party who may distribute and/or modify the Work as +permitted above, be liable to you for damages, including any general, +special, incidental or consequential damages arising out of any use of +the Work or out of inability to use the Work (including, but not limited +to, loss of data, data being rendered inaccurate, or losses sustained by +anyone as a result of any failure of the Work to operate with any other +programs), even if the Copyright Holder or said author or said other +party has been advised of the possibility of such damages. + + +MAINTENANCE OF THE WORK +======================= + +The Work has the status `author-maintained' if the Copyright Holder +explicitly and prominently states near the primary copyright notice in +the Work that the Work can only be maintained by the Copyright Holder +or simply that it is `author-maintained'. + +The Work has the status `maintained' if there is a Current Maintainer +who has indicated in the Work that they are willing to receive error +reports for the Work (for example, by supplying a valid e-mail +address). It is not required for the Current Maintainer to acknowledge +or act upon these error reports. + +The Work changes from status `maintained' to `unmaintained' if there +is no Current Maintainer, or the person stated to be Current +Maintainer of the work cannot be reached through the indicated means +of communication for a period of six months, and there are no other +significant signs of active maintenance. + +You can become the Current Maintainer of the Work by agreement with +any existing Current Maintainer to take over this role. + +If the Work is unmaintained, you can become the Current Maintainer of +the Work through the following steps: + + 1. Make a reasonable attempt to trace the Current Maintainer (and + the Copyright Holder, if the two differ) through the means of + an Internet or similar search. + + 2. If this search is successful, then enquire whether the Work + is still maintained. + + a. If it is being maintained, then ask the Current Maintainer + to update their communication data within one month. + + b. If the search is unsuccessful or no action to resume active + maintenance is taken by the Current Maintainer, then announce + within the pertinent community your intention to take over + maintenance. (If the Work is a LaTeX work, this could be + done, for example, by posting to comp.text.tex.) + + 3a. If the Current Maintainer is reachable and agrees to pass + maintenance of the Work to you, then this takes effect + immediately upon announcement. + + b. If the Current Maintainer is not reachable and the Copyright + Holder agrees that maintenance of the Work be passed to you, + then this takes effect immediately upon announcement. + + 4. If you make an `intention announcement' as described in 2b. above + and after three months your intention is challenged neither by + the Current Maintainer nor by the Copyright Holder nor by other + people, then you may arrange for the Work to be changed so as + to name you as the (new) Current Maintainer. + + 5. If the previously unreachable Current Maintainer becomes + reachable once more within three months of a change completed + under the terms of 3b) or 4), then that Current Maintainer must + become or remain the Current Maintainer upon request provided + they then update their communication data within one month. + +A change in the Current Maintainer does not, of itself, alter the fact +that the Work is distributed under the LPPL license. + +If you become the Current Maintainer of the Work, you should +immediately provide, within the Work, a prominent and unambiguous +statement of your status as Current Maintainer. You should also +announce your new status to the same pertinent community as +in 2b) above. + + +WHETHER AND HOW TO DISTRIBUTE WORKS UNDER THIS LICENSE +====================================================== + +This section contains important instructions, examples, and +recommendations for authors who are considering distributing their +works under this license. These authors are addressed as `you' in +this section. + +Choosing This License or Another License +---------------------------------------- + +If for any part of your work you want or need to use *distribution* +conditions that differ significantly from those in this license, then +do not refer to this license anywhere in your work but, instead, +distribute your work under a different license. You may use the text +of this license as a model for your own license, but your license +should not refer to the LPPL or otherwise give the impression that +your work is distributed under the LPPL. + +The document `modguide.tex' in the base LaTeX distribution explains +the motivation behind the conditions of this license. It explains, +for example, why distributing LaTeX under the GNU General Public +License (GPL) was considered inappropriate. Even if your work is +unrelated to LaTeX, the discussion in `modguide.tex' may still be +relevant, and authors intending to distribute their works under any +license are encouraged to read it. + +A Recommendation on Modification Without Distribution +----------------------------------------------------- + +It is wise never to modify a component of the Work, even for your own +personal use, without also meeting the above conditions for +distributing the modified component. While you might intend that such +modifications will never be distributed, often this will happen by +accident -- you may forget that you have modified that component; or +it may not occur to you when allowing others to access the modified +version that you are thus distributing it and violating the conditions +of this license in ways that could have legal implications and, worse, +cause problems for the community. It is therefore usually in your +best interest to keep your copy of the Work identical with the public +one. Many works provide ways to control the behavior of that work +without altering any of its licensed components. + +How to Use This License +----------------------- + +To use this license, place in each of the components of your work both +an explicit copyright notice including your name and the year the work +was authored and/or last substantially modified. Include also a +statement that the distribution and/or modification of that +component is constrained by the conditions in this license. + +Here is an example of such a notice and statement: + + %% pig.dtx + %% Copyright 2008 M. Y. Name + % + % This work may be distributed and/or modified under the + % conditions of the LaTeX Project Public License, either version 1.3 + % of this license or (at your option) any later version. + % The latest version of this license is in + % https://www.latex-project.org/lppl.txt + % and version 1.3c or later is part of all distributions of LaTeX + % version 2008 or later. + % + % This work has the LPPL maintenance status `maintained'. + % + % The Current Maintainer of this work is M. Y. Name. + % + % This work consists of the files pig.dtx and pig.ins + % and the derived file pig.sty. + +Given such a notice and statement in a file, the conditions +given in this license document would apply, with the `Work' referring +to the three files `pig.dtx', `pig.ins', and `pig.sty' (the last being +generated from `pig.dtx' using `pig.ins'), the `Base Interpreter' +referring to any `LaTeX-Format', and both `Copyright Holder' and +`Current Maintainer' referring to the person `M. Y. Name'. + +If you do not want the Maintenance section of LPPL to apply to your +Work, change `maintained' above into `author-maintained'. +However, we recommend that you use `maintained', as the Maintenance +section was added in order to ensure that your Work remains useful to +the community even when you can no longer maintain and support it +yourself. + +Derived Works That Are Not Replacements +--------------------------------------- + +Several clauses of the LPPL specify means to provide reliability and +stability for the user community. They therefore concern themselves +with the case that a Derived Work is intended to be used as a +(compatible or incompatible) replacement of the original Work. If +this is not the case (e.g., if a few lines of code are reused for a +completely different task), then clauses 6b and 6d shall not apply. + + +Important Recommendations +------------------------- + + Defining What Constitutes the Work + + The LPPL requires that distributions of the Work contain all the + files of the Work. It is therefore important that you provide a + way for the licensee to determine which files constitute the Work. + This could, for example, be achieved by explicitly listing all the + files of the Work near the copyright notice of each file or by + using a line such as: + + % This work consists of all files listed in manifest.txt. + + in that place. In the absence of an unequivocal list it might be + impossible for the licensee to determine what is considered by you + to comprise the Work and, in such a case, the licensee would be + entitled to make reasonable conjectures as to which files comprise + the Work. + diff --git a/macros/generic/etoolbox-generic/README.md b/macros/generic/etoolbox-generic/README.md new file mode 100644 index 0000000000..8b5fb17fe7 --- /dev/null +++ b/macros/generic/etoolbox-generic/README.md @@ -0,0 +1,8 @@ +This package implements a wrapper which allows the user to load the +LaTeX-independent part of LaTeX package `etoolbox` in other formats. + +Load the package by `\input etoolbox-generic`. + +The wrapper has been tested in plain TeX and ConTeXt on `etoolbox` version +`2020/10/05 v2.5k`. (The version of the wrapper should match the latest +`etoolbox` version.) diff --git a/macros/generic/etoolbox-generic/etoolbox-generic.tex b/macros/generic/etoolbox-generic/etoolbox-generic.tex new file mode 100644 index 0000000000..c1fcd869ac --- /dev/null +++ b/macros/generic/etoolbox-generic/etoolbox-generic.tex @@ -0,0 +1,80 @@ +% etoolbox-generic.tex 2023/10/10 v2.5k +% +% This is a loader for etoolbox.sty 2020/10/05 v2.5k in non-LaTeX formats. +% +% Copyright (c) 2023- Saso Zivanovic <saso.zivanovic@guest.arnes.si> +% +% This work may be distributed and/or modified under the conditions of the +% LaTeX Project Public License, either version 1.3c of this license or (at +% your option) any later version. The latest version of this license is in +% https://www.latex-project.org/lppl.txt and version 1.3c or later is part of +% all distributions of LaTeX version 2008 or later. +% +% This work has the LPPL maintenance status `maintained`. +% The Current Maintainer of this work is Saso Zivanovic. +% +% This work consist of this file and README.md. +% +% Prevent double-loading. +\ifcsname ver@etoolbox.sty\endcsname + \expandafter\endinput +\fi +% We need miniltx and a couple of auxiliary macros. +\input miniltx +\def\newif#1{% + \count@\escapechar \escapechar\m@ne + \let#1\iffalse + \@if#1\iftrue + \@if#1\iffalse + \escapechar\count@} +\def\@if#1#2{% + \expandafter\def\csname\expandafter\@gobbletwo\string#1% + \expandafter\@gobbletwo\string#2\endcsname + {\let#1#2}} +% We need to define \AtEndOfPackage; etoolbox uses it to undefine its +% catcodes. We're careful and restore the old (probably empty) definition at +% the end. +\let\etb@AtEndOfPackage\AtEndOfPackage +\def\AtEndOfPackage#1{\def\AtEndOfPackageDo{#1}} +% Kindly, all the LaTeX-specific code in etoolbox.sty occurs after +% the call to \IfFormatAtLeastTF. Redefining this macro to \endinput will +% therefore remove the LaTeX-specific part of the package: +\def\IfFormatAtLeastTF#1#2#3{\endinput} +\let\etb@extrafloats\extrafloats +\let\extrafloats\relax +\input etoolbox.sty +% Restore what we changed, and execute the end-of-package code. +\let\extrafloats\etb@extrafloats +\AtEndOfPackageDo +\let\AtEndOfPackage\etb@AtEndOfPackage +\let\AtEndOfPackageDo\relax +\cslet{ver@etoolbox.sty}\relax +% To have this package work in ConTeXt, we must patch the macros by replacing +% \unexpanded with \normalunexpanded. +\ifcsname normalunexpanded\endcsname + \def\etb@patchunexpanded#1{% + \patchcmd#1\unexpanded\normalunexpanded{}{% + \PackageError{etoolbox}{Cannot adapt command \string#1 to ConTeXt}{}% + }% + } + % \patchcmd itself uses \etb@patchcmd@scantoks which uses \unexpanded; so we + % temporarily redefine \unexpanded to patch \etb@patchcmd@scantoks. + \let\unexpanded\normalunexpanded + \etb@patchunexpanded\etb@patchcmd@scantoks + \let\unexpanded\protected + % Now we can patch the rest. + \etb@patchunexpanded\expandonce + \etb@patchunexpanded\etb@unexpandable + \etb@patchunexpanded\appto\etb@patchunexpanded\appto + \etb@patchunexpanded\gappto\etb@patchunexpanded\gappto + \etb@patchunexpanded\preto\etb@patchunexpanded\preto + \etb@patchunexpanded\gpreto\etb@patchunexpanded\gpreto + \etb@patchunexpanded\etb@hooktocmd + % Patching \protecting is a bit involved, as its parameters text is #. + \expandafter\def\expandafter\protecting\expandafter{\protecting{}} + \etb@patchunexpanded\protecting + \patchcmd\protecting{{}}{}{}{}% + \expandafter\def\expandafter\protecting\expandafter#\expandafter{\protecting} +\fi +\resetatcatcode +\endinput diff --git a/macros/generic/memoize/INSTALL.md b/macros/generic/memoize/INSTALL.md new file mode 100644 index 0000000000..99957a9181 --- /dev/null +++ b/macros/generic/memoize/INSTALL.md @@ -0,0 +1,216 @@ +# Installation from the TDS archive + +If Memoize is not (yet) offered by your TeX distribution, the easiest way to +install it is by downloading the TDS archive `memoize.tds.zip` from [Memoize's +CTAN page](https://ctan.org/pkg/memoize), and unpacking it into your `texmf` +directory. You will most likely also have to do the same for two auxiliary +packages Memoize depends on: [Advice](https://ctan.org/pkg/advice) and +[CollArgs](https://ctan.org/pkg/collargs). + +Read on only if you have an unstoppable urge to install from source and/or +compile the manual or the documented source code. + +# Installation from the source + +# Getting the sources + +There are several options: + +* Download and unpack the zip archive of the package from [Memoize's CTAN + page](https://ctan.org/pkg/memoize). + +* Download and unpack the TDS archive, or copy the files from your local + distribution. The sources reside in `<texmf>/source/generic/memoize`. + +* Clone the [GitHub repository](https://github.com/sasozivanovic/memoize). + +## Generating runtime files + +The easiest way to generate the runtime files is by running `make`. The +following command will generate (i) runtime TeX files for all supported formats +(currently: LaTeX, plain TeX and ConTeXt), and (ii) the man pages for the +accompanying scripts: + +``` +make runtime +``` + +To only generate the runtime TeX files, execute + +``` +make memoize.sty +``` + +Alternatively, you can generate the runtime files manually. The source of this +package was written using [EasyDTX](https://ctan.org/pkg/easydtx). Therefore, +you first have to convert the `.edtx` file into a regular `.dtx`: + +``` +edtx2dtx memoize.edtx > memoize.dtx +``` + +The next step is standard. Produce the runtime files by compiling the +installation file: + +``` +tex memoize.ins +``` + +If you require the ConTeXt runtime, replace all instances of `\expanded` and +`\unexpanded` in `t-memoize.tex` by `\normalexpanded` and `\normalunexpanded`, +respectively. One way to do this is: + +``` +sed -i -s -e 's/\\\(un\)\?expanded/\\normal\1expanded/g;' t-memoize.tex +``` + +The man pages are produced by converting their MarkDown sources by `pandoc` +(execute this in the `doc` subdirectory): + +``` +pandoc memoize-extract.1.md -s -t man -o memoize-extract.1 +pandoc memoize-clean.1.md -s -t man -o memoize-clean.1 +``` + +Additionally, links from `memoize-x.pl.1` and `memoize-x.py.1` to `memoize-x.1` +can be created by: + +``` +echo .so man1/memoize-extract.1 > memoize-extract.pl.1 +echo .so man1/memoize-extract.1 > memoize-extract.py.1 +echo .so man1/memoize-clean.1 > memoize-clean.pl.1 +echo .so man1/memoize-clean.1 > memoize-clean.py.1 +``` + +## Installation + +It is recommended to install the files into a TDS-compliant `texmf` directory, +as usual. Inspect file `FILES` or the TDS archive `memoize.tds.zip` to see +what goes where. + +Next, the scripts residing in `<texmf>/scripts/memoize` should be linked into +some directory listed in the executable search `PATH`. The scripts are the +following: + +* `memoize-extract.pl` +* `memoize-extract.py` +* `memoize-clean.pl` +* `memoize-clean.py` + +If you have downloaded the sources from GitHub, you can build the TDS +directories/archives of both Memoize and its auxiliary packages Advice and +CollArgs by issuing + +``` +make +``` + +This command creates: + +* TDS directories `memoize.tds`, `advice.tds` and `collargs.tds`, + +* CTAN directories `ctan/memoize`, `ctan/advice` and `ctan/memoize`, + +* TDS archives `memoize.tds.zip`, `advice.tds.zip` and `collargs.tds.zip` + inside the CTAN directories, and + +* CTAN archives `memoize.zip`, `advice.zip` and `collargs.zip` inside directory + `ctan`. + +The plain `make` shown above will also attempt to compile the documentation. +If you're not ready for that (yet), you can avoid that by executing this +instead: + +``` +make PDF= +``` + +# Compiling the documentation + +Compiling both the documented code listing and the manual requires a Unix-like +operating system. I have developed Memoize on Linux, but the documentation +should also be compilable under Cygwin on Windows (not tested). + +The documentation of Advice and CollArgs, both their manuals and documented +code listings, is included within Memoize's documentation. + +## Getting the source + +In principle, the options are the same as for the installation from the source, +but the GitHub option is strongly preferred here, as the other two options +require manually copying the sources of Advice and CollArgs into the Memoize +directory. That said: + +* Clone the [GitHub repository](https://github.com/sasozivanovic/memoize). + You're done. + +* Download and unpack the zip archives of all three packages from their CTAN + pages: https://ctan.org/pkg/memoize, https://ctan.org/pkg/advice and + https://ctan.org/pkg/collargs. + + Copy `advice.edtx` and `collargs.edtx` into the Memoize directory, alongside + `memoize.edtx`. + +* From TDS archives (of all three packages), or your local distribution's + `<texmf>` folder. This is not straightforward: + + 1. Make a local copy of directory `<texmf>/source/generic/memoize`; we'll + call it "the Memoize directory". + + 2. Copy directory `<texmf>/doc/generic/memoize` into the the Memoize + directory as `doc`. + + 3. Copy `memoize-extract.pl`, `memoize-extract.py`, `memoize-clean.pl` and + `memoize-clean.py` from directory `<texmf>/scripts` into the Memoize + directory. + + 4. Copy `advice.edtx` from `<texmf>/source/generic/advice` and + `collargs.edtx` from `<texmf>/source/generic/collargs` into the the + Memoize directory. + +## Compiling the documented code listing + +I have compiled the code docs with LuaLaTeX on a Linux system with +TeXLive 2023. If you have `make`, the easiest way to compile them is by +issuing + +``` +make doc/memoize-code.pdf +``` + +Alternatively, you can use `latexmk`, but you first have to convert the `.edtx` +sources of all three packages into `.dtx`, if you haven't done so yet: + +``` +edtx2dtx memoize.edtx > memoize.dtx +edtx2dtx advice.edtx > advice.dtx +edtx2dtx collargs.edtx > collargs.dtx +``` + +Then, you can execute `latexmk` from the `doc` subdirectory: + +``` +latexmk -lualatex -bibtex memoize-code +``` + +To compile the code docs manually, three iterations of `lualatex memoize-code` +with `makeindex -s gind.ist memoize-code.idx` between them should suffice. + +## Compiling the manual + +I have compiled the manual with LuaLaTeX on a Linux system with TeXLive 2023, +`make`, `latexmk`, `perl` and `sed` installed. Furthermore, you absolutely +have to run the compilation with some form of `--shell-escape`, as it executes +`make` and `sed` to build the examples. (There is no way to compile these from +the command line, as the instructions are baked into the manual source.) + +Given all this, either of the following should do the trick: + +* `make doc/memoize.pdf` from the Memoize directory; + +* `latexmk -lualatex -bibtex memoize` for the `doc` subdirectory; or + +* quite a few runs of `lualatex memoize` interspersed by `makeindex memoize.idx`. + +If all worked well, you can change `\usepackage{nomemoize}` in +`doc/memoize.tex` to `\usepackage{memoize}` and observe Memoize at work. diff --git a/macros/generic/memoize/LICENCE b/macros/generic/memoize/LICENCE new file mode 100644 index 0000000000..1b57559a83 --- /dev/null +++ b/macros/generic/memoize/LICENCE @@ -0,0 +1,416 @@ +The LaTeX Project Public License +=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +LPPL Version 1.3c 2008-05-04 + +Copyright 1999 2002-2008 LaTeX3 Project + Everyone is allowed to distribute verbatim copies of this + license document, but modification of it is not allowed. + + +PREAMBLE +======== + +The LaTeX Project Public License (LPPL) is the primary license under +which the LaTeX kernel and the base LaTeX packages are distributed. + +You may use this license for any work of which you hold the copyright +and which you wish to distribute. This license may be particularly +suitable if your work is TeX-related (such as a LaTeX package), but +it is written in such a way that you can use it even if your work is +unrelated to TeX. + +The section `WHETHER AND HOW TO DISTRIBUTE WORKS UNDER THIS LICENSE', +below, gives instructions, examples, and recommendations for authors +who are considering distributing their works under this license. + +This license gives conditions under which a work may be distributed +and modified, as well as conditions under which modified versions of +that work may be distributed. + +We, the LaTeX3 Project, believe that the conditions below give you +the freedom to make and distribute modified versions of your work +that conform with whatever technical specifications you wish while +maintaining the availability, integrity, and reliability of +that work. If you do not see how to achieve your goal while +meeting these conditions, then read the document `cfgguide.tex' +and `modguide.tex' in the base LaTeX distribution for suggestions. + + +DEFINITIONS +=========== + +In this license document the following terms are used: + + `Work' + Any work being distributed under this License. + + `Derived Work' + Any work that under any applicable law is derived from the Work. + + `Modification' + Any procedure that produces a Derived Work under any applicable + law -- for example, the production of a file containing an + original file associated with the Work or a significant portion of + such a file, either verbatim or with modifications and/or + translated into another language. + + `Modify' + To apply any procedure that produces a Derived Work under any + applicable law. + + `Distribution' + Making copies of the Work available from one person to another, in + whole or in part. Distribution includes (but is not limited to) + making any electronic components of the Work accessible by + file transfer protocols such as FTP or HTTP or by shared file + systems such as Sun's Network File System (NFS). + + `Compiled Work' + A version of the Work that has been processed into a form where it + is directly usable on a computer system. This processing may + include using installation facilities provided by the Work, + transformations of the Work, copying of components of the Work, or + other activities. Note that modification of any installation + facilities provided by the Work constitutes modification of the Work. + + `Current Maintainer' + A person or persons nominated as such within the Work. If there is + no such explicit nomination then it is the `Copyright Holder' under + any applicable law. + + `Base Interpreter' + A program or process that is normally needed for running or + interpreting a part or the whole of the Work. + + A Base Interpreter may depend on external components but these + are not considered part of the Base Interpreter provided that each + external component clearly identifies itself whenever it is used + interactively. Unless explicitly specified when applying the + license to the Work, the only applicable Base Interpreter is a + `LaTeX-Format' or in the case of files belonging to the + `LaTeX-format' a program implementing the `TeX language'. + + + +CONDITIONS ON DISTRIBUTION AND MODIFICATION +=========================================== + +1. Activities other than distribution and/or modification of the Work +are not covered by this license; they are outside its scope. In +particular, the act of running the Work is not restricted and no +requirements are made concerning any offers of support for the Work. + +2. You may distribute a complete, unmodified copy of the Work as you +received it. Distribution of only part of the Work is considered +modification of the Work, and no right to distribute such a Derived +Work may be assumed under the terms of this clause. + +3. You may distribute a Compiled Work that has been generated from a +complete, unmodified copy of the Work as distributed under Clause 2 +above, as long as that Compiled Work is distributed in such a way that +the recipients may install the Compiled Work on their system exactly +as it would have been installed if they generated a Compiled Work +directly from the Work. + +4. If you are the Current Maintainer of the Work, you may, without +restriction, modify the Work, thus creating a Derived Work. You may +also distribute the Derived Work without restriction, including +Compiled Works generated from the Derived Work. Derived Works +distributed in this manner by the Current Maintainer are considered to +be updated versions of the Work. + +5. If you are not the Current Maintainer of the Work, you may modify +your copy of the Work, thus creating a Derived Work based on the Work, +and compile this Derived Work, thus creating a Compiled Work based on +the Derived Work. + +6. If you are not the Current Maintainer of the Work, you may +distribute a Derived Work provided the following conditions are met +for every component of the Work unless that component clearly states +in the copyright notice that it is exempt from that condition. Only +the Current Maintainer is allowed to add such statements of exemption +to a component of the Work. + + a. If a component of this Derived Work can be a direct replacement + for a component of the Work when that component is used with the + Base Interpreter, then, wherever this component of the Work + identifies itself to the user when used interactively with that + Base Interpreter, the replacement component of this Derived Work + clearly and unambiguously identifies itself as a modified version + of this component to the user when used interactively with that + Base Interpreter. + + b. Every component of the Derived Work contains prominent notices + detailing the nature of the changes to that component, or a + prominent reference to another file that is distributed as part + of the Derived Work and that contains a complete and accurate log + of the changes. + + c. No information in the Derived Work implies that any persons, + including (but not limited to) the authors of the original version + of the Work, provide any support, including (but not limited to) + the reporting and handling of errors, to recipients of the + Derived Work unless those persons have stated explicitly that + they do provide such support for the Derived Work. + + d. You distribute at least one of the following with the Derived Work: + + 1. A complete, unmodified copy of the Work; + if your distribution of a modified component is made by + offering access to copy the modified component from a + designated place, then offering equivalent access to copy + the Work from the same or some similar place meets this + condition, even though third parties are not compelled to + copy the Work along with the modified component; + + 2. Information that is sufficient to obtain a complete, + unmodified copy of the Work. + +7. If you are not the Current Maintainer of the Work, you may +distribute a Compiled Work generated from a Derived Work, as long as +the Derived Work is distributed to all recipients of the Compiled +Work, and as long as the conditions of Clause 6, above, are met with +regard to the Derived Work. + +8. The conditions above are not intended to prohibit, and hence do not +apply to, the modification, by any method, of any component so that it +becomes identical to an updated version of that component of the Work as +it is distributed by the Current Maintainer under Clause 4, above. + +9. Distribution of the Work or any Derived Work in an alternative +format, where the Work or that Derived Work (in whole or in part) is +then produced by applying some process to that format, does not relax or +nullify any sections of this license as they pertain to the results of +applying that process. + +10. a. A Derived Work may be distributed under a different license + provided that license itself honors the conditions listed in + Clause 6 above, in regard to the Work, though it does not have + to honor the rest of the conditions in this license. + + b. If a Derived Work is distributed under a different license, that + Derived Work must provide sufficient documentation as part of + itself to allow each recipient of that Derived Work to honor the + restrictions in Clause 6 above, concerning changes from the Work. + +11. This license places no restrictions on works that are unrelated to +the Work, nor does this license place any restrictions on aggregating +such works with the Work by any means. + +12. Nothing in this license is intended to, or may be used to, prevent +complete compliance by all parties with all applicable laws. + + +NO WARRANTY +=========== + +There is no warranty for the Work. Except when otherwise stated in +writing, the Copyright Holder provides the Work `as is', without +warranty of any kind, either expressed or implied, including, but not +limited to, the implied warranties of merchantability and fitness for a +particular purpose. The entire risk as to the quality and performance +of the Work is with you. Should the Work prove defective, you assume +the cost of all necessary servicing, repair, or correction. + +In no event unless required by applicable law or agreed to in writing +will The Copyright Holder, or any author named in the components of the +Work, or any other party who may distribute and/or modify the Work as +permitted above, be liable to you for damages, including any general, +special, incidental or consequential damages arising out of any use of +the Work or out of inability to use the Work (including, but not limited +to, loss of data, data being rendered inaccurate, or losses sustained by +anyone as a result of any failure of the Work to operate with any other +programs), even if the Copyright Holder or said author or said other +party has been advised of the possibility of such damages. + + +MAINTENANCE OF THE WORK +======================= + +The Work has the status `author-maintained' if the Copyright Holder +explicitly and prominently states near the primary copyright notice in +the Work that the Work can only be maintained by the Copyright Holder +or simply that it is `author-maintained'. + +The Work has the status `maintained' if there is a Current Maintainer +who has indicated in the Work that they are willing to receive error +reports for the Work (for example, by supplying a valid e-mail +address). It is not required for the Current Maintainer to acknowledge +or act upon these error reports. + +The Work changes from status `maintained' to `unmaintained' if there +is no Current Maintainer, or the person stated to be Current +Maintainer of the work cannot be reached through the indicated means +of communication for a period of six months, and there are no other +significant signs of active maintenance. + +You can become the Current Maintainer of the Work by agreement with +any existing Current Maintainer to take over this role. + +If the Work is unmaintained, you can become the Current Maintainer of +the Work through the following steps: + + 1. Make a reasonable attempt to trace the Current Maintainer (and + the Copyright Holder, if the two differ) through the means of + an Internet or similar search. + + 2. If this search is successful, then enquire whether the Work + is still maintained. + + a. If it is being maintained, then ask the Current Maintainer + to update their communication data within one month. + + b. If the search is unsuccessful or no action to resume active + maintenance is taken by the Current Maintainer, then announce + within the pertinent community your intention to take over + maintenance. (If the Work is a LaTeX work, this could be + done, for example, by posting to comp.text.tex.) + + 3a. If the Current Maintainer is reachable and agrees to pass + maintenance of the Work to you, then this takes effect + immediately upon announcement. + + b. If the Current Maintainer is not reachable and the Copyright + Holder agrees that maintenance of the Work be passed to you, + then this takes effect immediately upon announcement. + + 4. If you make an `intention announcement' as described in 2b. above + and after three months your intention is challenged neither by + the Current Maintainer nor by the Copyright Holder nor by other + people, then you may arrange for the Work to be changed so as + to name you as the (new) Current Maintainer. + + 5. If the previously unreachable Current Maintainer becomes + reachable once more within three months of a change completed + under the terms of 3b) or 4), then that Current Maintainer must + become or remain the Current Maintainer upon request provided + they then update their communication data within one month. + +A change in the Current Maintainer does not, of itself, alter the fact +that the Work is distributed under the LPPL license. + +If you become the Current Maintainer of the Work, you should +immediately provide, within the Work, a prominent and unambiguous +statement of your status as Current Maintainer. You should also +announce your new status to the same pertinent community as +in 2b) above. + + +WHETHER AND HOW TO DISTRIBUTE WORKS UNDER THIS LICENSE +====================================================== + +This section contains important instructions, examples, and +recommendations for authors who are considering distributing their +works under this license. These authors are addressed as `you' in +this section. + +Choosing This License or Another License +---------------------------------------- + +If for any part of your work you want or need to use *distribution* +conditions that differ significantly from those in this license, then +do not refer to this license anywhere in your work but, instead, +distribute your work under a different license. You may use the text +of this license as a model for your own license, but your license +should not refer to the LPPL or otherwise give the impression that +your work is distributed under the LPPL. + +The document `modguide.tex' in the base LaTeX distribution explains +the motivation behind the conditions of this license. It explains, +for example, why distributing LaTeX under the GNU General Public +License (GPL) was considered inappropriate. Even if your work is +unrelated to LaTeX, the discussion in `modguide.tex' may still be +relevant, and authors intending to distribute their works under any +license are encouraged to read it. + +A Recommendation on Modification Without Distribution +----------------------------------------------------- + +It is wise never to modify a component of the Work, even for your own +personal use, without also meeting the above conditions for +distributing the modified component. While you might intend that such +modifications will never be distributed, often this will happen by +accident -- you may forget that you have modified that component; or +it may not occur to you when allowing others to access the modified +version that you are thus distributing it and violating the conditions +of this license in ways that could have legal implications and, worse, +cause problems for the community. It is therefore usually in your +best interest to keep your copy of the Work identical with the public +one. Many works provide ways to control the behavior of that work +without altering any of its licensed components. + +How to Use This License +----------------------- + +To use this license, place in each of the components of your work both +an explicit copyright notice including your name and the year the work +was authored and/or last substantially modified. Include also a +statement that the distribution and/or modification of that +component is constrained by the conditions in this license. + +Here is an example of such a notice and statement: + + %% pig.dtx + %% Copyright 2008 M. Y. Name + % + % This work may be distributed and/or modified under the + % conditions of the LaTeX Project Public License, either version 1.3 + % of this license or (at your option) any later version. + % The latest version of this license is in + % https://www.latex-project.org/lppl.txt + % and version 1.3c or later is part of all distributions of LaTeX + % version 2008 or later. + % + % This work has the LPPL maintenance status `maintained'. + % + % The Current Maintainer of this work is M. Y. Name. + % + % This work consists of the files pig.dtx and pig.ins + % and the derived file pig.sty. + +Given such a notice and statement in a file, the conditions +given in this license document would apply, with the `Work' referring +to the three files `pig.dtx', `pig.ins', and `pig.sty' (the last being +generated from `pig.dtx' using `pig.ins'), the `Base Interpreter' +referring to any `LaTeX-Format', and both `Copyright Holder' and +`Current Maintainer' referring to the person `M. Y. Name'. + +If you do not want the Maintenance section of LPPL to apply to your +Work, change `maintained' above into `author-maintained'. +However, we recommend that you use `maintained', as the Maintenance +section was added in order to ensure that your Work remains useful to +the community even when you can no longer maintain and support it +yourself. + +Derived Works That Are Not Replacements +--------------------------------------- + +Several clauses of the LPPL specify means to provide reliability and +stability for the user community. They therefore concern themselves +with the case that a Derived Work is intended to be used as a +(compatible or incompatible) replacement of the original Work. If +this is not the case (e.g., if a few lines of code are reused for a +completely different task), then clauses 6b and 6d shall not apply. + + +Important Recommendations +------------------------- + + Defining What Constitutes the Work + + The LPPL requires that distributions of the Work contain all the + files of the Work. It is therefore important that you provide a + way for the licensee to determine which files constitute the Work. + This could, for example, be achieved by explicitly listing all the + files of the Work near the copyright notice of each file or by + using a line such as: + + % This work consists of all files listed in manifest.txt. + + in that place. In the absence of an unequivocal list it might be + impossible for the licensee to determine what is considered by you + to comprise the Work and, in such a case, the licensee would be + entitled to make reasonable conjectures as to which files comprise + the Work. + diff --git a/macros/generic/memoize/Makefile b/macros/generic/memoize/Makefile new file mode 100644 index 0000000000..7edbd7ba34 --- /dev/null +++ b/macros/generic/memoize/Makefile @@ -0,0 +1,139 @@ +# Prepare the CTAN submission for all three packages. + +PACKAGES = memoize advice collargs + +all: ctan/memoize.zip + $(MAKE) -f Makefile.advice ctan/advice.zip + $(MAKE) -f Makefile.collargs ctan/collargs.zip + +# Prepare the CTAN submission. + +PACKAGE = memoize +VERSION = 1.0.0 +YEAR = 2023 +MONTH = 10 +DAY = 10 + +FORMAT = generic + +COMMON = memoize nomemoize memoizable +PLAIN = memoize-extract-one.tex +SOURCE = memoize.edtx memoize.ins # $(makefiles) + +SCRIPTS := memoize-extract memoize-clean +man-src := $(SCRIPTS:%=doc/%.1.md) +MAN := $(SCRIPTS:%=%.1) $(SCRIPTS:%=%.pl.1) $(SCRIPTS:%=%.py.1) +MAN := $(MAN:%=doc/%) +SCRIPTS := $(SCRIPTS:%=%.pl) $(SCRIPTS:%=%.py) + +%.pl.1: %.1 + echo .so man1/$*.1 > $@ # link to .1 man page +%.py.1: %.1 + echo .so man1/$*.1 > $@ # link to .1 man page + +.PHONY: runtime +runtime: $(RUNTIME) + +README = doc/README.memoize.md +INSTALL = INSTALL.md +MAKEFILE = Makefile +LICENCE = LICENCE + +makefiles = Makefile.package Makefile.runtimes Makefile.advice Makefile.collargs + +codedoc-source = memoize-code.tex \ + memoize-code.sty memoize-doc-common.sty + +manual-source = memoize.tex \ + memoize-doc.sty memoize-doc-common.sty yadoc.sty \ + memoize.mst + +PDF = memoize.pdf memoize-code.pdf + +codedoc-source := $(codedoc-source:%=doc/%) +manual-source := $(manual-source:%=doc/%) +pdf := $(PDF:%=doc/%) +DOC = $(sort $(codedoc-source) $(manual-source)) $(pdf) $(man-src) + +examples-src := Makefile ins.begin ins.mid ins.end +examples-src := $(examples-src:%=doc/examples/%) +#examples-src += $(shell git ls-files | grep ^doc/examples/.*dtx$) +examples-src += $(shell find doc/examples -name '*.dtx') + +# doc/attachments.lst is produced by compiling memoize.tex (without memoization). +# doc-examples will hold soft links to the relevant generated example files. +doc-examples := $(shell sed 's/^.* \(.*\) ##.*$$/\1/' doc/attachments.lst) +doc-examples := $(doc-examples:%=doc/examples/%) + +ctan/$(PACKAGE).zip: + $(TDS-BEGIN) +# Check for duplicate filenames: + echo $(doc-examples) | tr ' ' '\n' | uniq -d | ifne false + cd doc && zip ../$(TDS-DOC-DIR)/examples-src.zip $(examples-src:doc/%=%) +# For each line ($1 $2) in attachments.lst, link $1 to $2 ... + cd doc/examples && sed 's!^examples/!ln -sfr !' ../attachments.lst | sh +# ... and zip those links. + cd doc && zip -r ../$(TDS-DOC-DIR)/examples.zip $(doc-examples:doc/%=%) + $(TDS-END) + $(CTAN-BEGIN) + ln -sr $(TDS-DOC-DIR)/examples-src.zip $(CTAN-DIR)/doc + ln -sr $(TDS-DOC-DIR)/examples.zip $(CTAN-DIR)/doc + $(CTAN-END) + + + +doc/memoize-code.pdf: $(SOURCE) $(codedoc-source) + +doc/memoize.pdf: $(manual-source) $(examples-src) + +%.pdf: %.tex + latexmk -cd -lualatex -bibtex- $< && touch $@ + + + +# Maintanence + +test.tex = $(wildcard test*.tex) + +.PHONY: all runtime force clean versions-show + +.PRECIOUS: %.1 + +clean: # clean this directory + memoize-clean.py -a $(test.tex:%.tex=-p %.) $(test.tex:%.tex=-p %.memo.dir/) + latexmk -C -f $(test.tex) _region_ + +version: + $(MAKE) -f Makefile.collargs version + $(MAKE) -f Makefile.advice version + $(call EDIT-VERSION-LATEX,memoize.edtx,memoize) + $(call EDIT-VERSION-LATEX,memoize.edtx,nomemoize) + $(call EDIT-VERSION-LATEX,memoize.edtx,memoizable) + $(call EDIT-VERSION-CONTEXT,memoize.edtx) + $(call EDIT-VERSION-PLAIN,memoize.edtx,memoize) + $(call EDIT-VERSION-PLAIN,memoize.edtx,nomemoize) + $(call EDIT-VERSION-PLAIN,memoize.edtx,memoizable) + $(call EDIT-VERSION-PERL,memoize-extract.pl) + $(call EDIT-VERSION-PERL,memoize-clean.pl) + $(call EDIT-VERSION-PYTHON,memoize-extract.py) + $(call EDIT-VERSION-PYTHON,memoize-clean.py) + $(call EDIT-VERSION-MAN,doc/memoize-extract.1.md) + $(call EDIT-VERSION-MAN,doc/memoize-clean.1.md) +# Change the date of the latest release (identified by the version). + sed -Ei 's!^\\item\[\\githubrelease\{[0-9]{4}/[0-9]{2}/[0-9]{2}\}\{v$(VERSION)\}\] *$$!\\item\[\\githubrelease\{$(YEAR)/$(MONTH)/$(DAY)\}\{v$(VERSION)\}\]!' doc/memoize.tex + +define COLOR_VERSION +grep -E --color '[0-9]{4}[/-][0-9]{2}[/-][0-9]{2}|v?[0-9]\.[0-9]\.[0-9]|(January|February|March|April|May|June|July|August|September|October|November|December) [0-9]+, [0-9]{4}' +endef + +versions-show: + @grep -E '%<latex>\\ProvidesPackage|^%<context>%D\s*(version|date)=' $(PACKAGES:%=%.edtx) | ${COLOR_VERSION} + @grep __version__ *.py | ${COLOR_VERSION} + @grep VERSION *.pl | ${COLOR_VERSION} + @grep -E '^(footer|date):' doc/memoize-*.md | ${COLOR_VERSION} + @grep -E 'githubrelease' doc/memoize.tex | ${COLOR_VERSION} + +include Makefile.package +include Makefile.runtimes + +VERSION-MAN = of Memoize v$(VERSION) diff --git a/macros/generic/memoize/README.md b/macros/generic/memoize/README.md new file mode 100644 index 0000000000..9acde7b26e --- /dev/null +++ b/macros/generic/memoize/README.md @@ -0,0 +1,11 @@ +Memoize is a package for externalization of graphics and memoization of +compilation results in general, allowing the author to reuse the results of +compilation-intensive code. + +Memoize (i) induces very little overhead, as all externalized graphics is +produced in a single compilation. It features (ii) automatic recompilation upon +the change of code or user-adjustable context, and (iii) automatic +externalization of TikZ pictures and Forest trees, easily extensible to other +commands and environments. Furthermore, Memoize (iv) supports +cross-referencing, TikZ overlays and Beamer, (v) works with all major engines +and formats, and (vi) is adaptable to any workflow. diff --git a/macros/generic/memoize/doc/memoize-clean.1.md b/macros/generic/memoize/doc/memoize-clean.1.md new file mode 100644 index 0000000000..138e0ee871 --- /dev/null +++ b/macros/generic/memoize/doc/memoize-clean.1.md @@ -0,0 +1,62 @@ +--- +title: memoize-clean +section: 1 +header: User Manual +footer: memoize-clean of Memoize v1.0.0 +date: October 10, 2023 +hyphenate: false +--- + +# NAME +memoize-clean.pl, memoize-clean.py - Remove (stale) memo and extern files + + +# SYNOPSIS +**memoize-clean.pl** [*OPTIONS*] [*document1.mmz* ...] + +**memoize-clean.py** [*OPTIONS*] [*document1.mmz* ...] + + +# DESCRIPTION + +**memoize-clean** is a script accompanying Memoize, a TeX package which allows +the author to reuse the results of compilation-intensive code such as TikZ +pictures. + +By default, this script removes stale memo and extern files. A stale memo or +extern is an extern produced during a compilation of a previous version of the +document which was neither used nor produced during the last compilation of the +document. Typically, stale files arise when we change the memoized code (or +its context). + +**memoize-clean.pl** removes all memo and extern files with prefixes mentioned +in the listed **.mmz** files and by options **\--prefix** which are not +explicitly mentioned in the **.mmz** files. + +Before deleting anything, **memoize-clean.pl** lists all the files it would +delete and asks for confirmation. + +# OPTIONS + +**-p, \--prefix** +: Add a memo/extern prefix for cleaning. This option may be given multiple + times. + +**-a, \--all** +: Remove all memos and externs, rather than only the stale ones. + +**-y, \--yes** +: Do not ask for confirmation. + +**-q, \--quiet** +: Don't describe what's happening. + +**-h, \--help** +: Show help. + +**-V, \--version** +: Show the Memoize version number and exit. + +# SEE ALSO + +[Memoize manual](https://ctan.org/pkg/memoize), section 6.6.3. diff --git a/macros/generic/memoize/doc/memoize-code.pdf b/macros/generic/memoize/doc/memoize-code.pdf Binary files differnew file mode 100644 index 0000000000..7eeed4b3bf --- /dev/null +++ b/macros/generic/memoize/doc/memoize-code.pdf diff --git a/macros/generic/memoize/doc/memoize-code.sty b/macros/generic/memoize/doc/memoize-code.sty new file mode 100644 index 0000000000..30d48f364c --- /dev/null +++ b/macros/generic/memoize/doc/memoize-code.sty @@ -0,0 +1,276 @@ +%% memoize-code.sty +%% +%% This file is a part of Memoize, a TeX package for externalization of +%% graphics and memoization of compilation results in general, available at +%% https://ctan.org/pkg/memoize and https://github.com/sasozivanovic/memoize. +%% +%% Copyright (c) 2020- Saso Zivanovic <saso.zivanovic@guest.arnes.si> +%% (Sa\v{s}o \v{Z}ivanovi\'{c}) +%% +%% This work may be distributed and/or modified under the conditions of the +%% LaTeX Project Public License, either version 1.3c of this license or (at +%% your option) any later version. The latest version of this license is in +%% https://www.latex-project.org/lppl.txt and version 1.3c or later is part of +%% all distributions of LaTeX version 2008 or later. +%% +%% This work has the LPPL maintenance status `maintained'. +%% The Current Maintainer of this work is Saso Zivanovic. +%% +%% The files belonging to this work and covered by LPPL are listed in +%% (<texmf>/doc/generic/memoize/)FILES. + +\ProvidesPackage{memoize-code} +\RequirePackage{memoize-doc-common} + +% Typesetting the code & indentation +\MacroIndent=0pt +\MacroTopsep = 0pt +\MacrocodeTopsep = 7pt plus 2pt minus 2pt +\AddToHook{env/macrocode/before}{\macrocodepar}% +\let\macrocodepar\par + +% A mark for extraction into the manual. We need to remove some vertical space +% when this environment occurs alone in a doc block. +\newenvironment{listingregion}[1]{% + \ifvmode + \let\macrocodepar\relax + \AddToHookNext{env/macrocode/begin}{\MacrocodeTopsep=0pt\relax}% + \def\listingregionskip{\vspace{-\baselineskip}}% + \listingregionskip + \else + \def\listingregionskip{}% + \fi +}{\listingregionskip} + +\setlist{nosep} + +% Paragraph headings in the left margin, same as macro environments. +\RenewDocumentCommand\paragraph{s t. m}{% + \setbox0=\hbox{% + \mbox{% + \textcolor{blue}{% + #3% + \IfBooleanT{#1}{\mbox{\ ---}}% + \IfBooleanT{#2}{\hspace{0.7em}}% + }% + }% + }% + \noindent + \ifdim\wd0>\marginparwidth + \hangindent\dimexpr\wd0-\marginparwidth\relax + \hangafter-1\relax + \fi + \marginpar{\ifdim\wd0<\marginparwidth\hfill\fi\box0}% + \AddToHookNext{env/macrocode/before}{\par\nohang}% + \pgf@keys@utilifnextchar\par{\myparagraph@gobblepar}{\ignorespaces}% +} +\long\def\myparagraph@gobblepar#1{\ignorespaces} + +\def\macrocodeenvname{macrocode} +\def\MacroFont{\fontencoding\encodingdefault + \fontfamily\ttdefault + \fontseries\mddefault + \fontshape\shapedefault + \small + \ifx\@currenvir\macrocodeenvname\else\color{blue}\fi +} +\let\AltMacroFont\MacroFont % No special font for guarded code. + +% Typeset one-line guards to the left, +% indent multi-line guard begin/ends by the guard level, +% and let guards be orange. +% Move the codeline number left. +\def\Module#1{% + \begingroup + \IfBeginWith{#1}{*}{% + \def\next{}% + }{% + \IfBeginWith{#1}{/}{% + \def\next{}% + \advance\guard@level-1 + } + {% + \def\next##1{% + \guard@level0 + \setbox0=\lastbox + \llap{\unhbox0 ##1}% + }% + }% + }% + \next{% + \loop + \ifnum\guard@level>0 + \hskip 1em + \advance\guard@level-1 + \repeat + \color{orange}\mod@math@codes$\langle\mathsf{#1}\rangle$\hspace{0.1em}% + }% + \endgroup +} + + +% doc elements +\NewDocElement[idxgroup={\texttt{/mmz} keys}, idxtype={\texttt{/mmz}}]{Key}{key} +\NewDocElement[idxgroup={\texttt{/collargs} keys}, idxtype={\texttt{/collargs}}]{CollargsKey}{collargskey} +\NewDocElement[idxgroup={\texttt{/advice} keys}, idxtype={\texttt{/advice}}]{AdviceKey}{advicekey} +\NewDocElement[idxgroup={\texttt{/mmz/auto} keys}, idxtype={\texttt{/mmz/auto}}]{MmzAutoKey}{mmzautokey} +\NewDocElement[idxgroup={\texttt{/handlers} keys}, idxtype={\texttt{/handlers}}]{HandlersKey}{handlerskey} +\NewDocElement[idxgroup={\texttt{/advice/install} keys}, idxtype={\texttt{/advice/install}}]{AdviceInstallkey}{adviceinstallkey} + +% Funny: without repeating this here, I can't use commas to separate several macros. +\RenewDocElement[macrolike=true, idxtype=,idxgroup=,printtype=]{Macro}{macro} +\NewDocElement[idxtype={Lua function}, idxgroup=Lua functions]{LuaFunction}{luafun} + + +\newcommand\PrintMainMacro[1]{{\MacroFont\string#1}\SpecialMainMacroIndex{#1}} + +\newcommand\MyIndex[3]{% + \global\advance\c@CodelineNo 1 + \csuse{special@index}{#1\actualchar#2\encapchar#3}% + \global\advance\c@CodelineNo -1 + \ignorespaces +} +\NewDocumentCommand\exclamationmark{}{!} + + +% hanging indent for "key" and "macro" environments +\newcommand\hang[2]{% + \setbox0=\hbox{\MacroFont \string#2\relax}% "\relax" is short enough to never trigger a hanging indent + \ifdim\wd0>\dimexpr\marginparwidth\relax + \hangindent\dimexpr\wd0-\marginparwidth\relax + \hangafter-#1\relax + \AddToHookNext{env/macrocode/before}{\par\nohang}% + \AddToHookNext{para/after}{\aftergroup\nohang}% + \fi + \ignorespaces +} +\newcommand\nohang{\hangindent 0pt\relax} +\newcommand\indentmacrocode{% + \settowidth\MacroIndent{\theCodelineNo\ }% + \addtolength\MacroIndent{\hangindent}% +} +\newcommand\noindentmacrocode{% + \setlength\MacroIndent{0em}% + \AddToHookNext{env/macrocode/begin}{\MacrocodeTopsep 0pt\relax}% +} + +% and now ... autohang! +% (the macros should be listed without spaces after commas) +\usepackage{advice} +\pgfkeys{ + /my/.cd, + .install advice, + advice/hang/.style={outer handler=\autohang}, + advice=\key{hang}, + advice=\macro{hang}, + advice=\autokey{hang}, + advice=\mmzautokey{hang}, + advice=\collargskey{hang}, + advice=\handlerskey{hang}, + advice=\adviceinstallkey{hang}, + advice=\luafun{hang}, +} +\def\hangn{0} +\def\hanglength{0} +\def\hangstring{} +\newcommand\autohang[2][]{% + \ifstrequal{#1}{noprint}{}{% + \def\temp{#2}% + \@for\hangtemp:=\temp\do{\computehang}% + \edef\temp{\noexpand\hang{\hangn}{\expandonce\hangstring}}\temp + }% + \AdviceOriginal[#1]{#2}% +} +\def\computehang{% + \StrLen{\expandafter\string\hangtemp}[\temp]% + \edef\hangn{\the\numexpr\hangn+1} + \ifnum\temp>\hanglength\relax + \let\hanglength\temp + \let\hangstring\hangtemp + \fi +} + + + +%%% Various: +\newcommand\MS{\textsuperscript{M}\S} % manual section + +% Read in the manual .aux, for \MS cross-references. +\begingroup +\let\orig@newlabel\newlabel +\def\newlabel#1#2{% + \IfBeginWith{#1}{sec:}{\orig@newlabel{#1}{#2}}{}% +} +\def\doline{% + \expandafter\futurelet\expandafter\firsttoken\expandafter\dolinei\line\fi} +\def\dolinei{\ifx\firsttoken\newlabel}% +\openin0=memoize.aux +\loop + \unless\ifeof0 + \read0 to \line + \doline +\repeat +\closein0 +\endgroup + + + +%%%% Filter the index, keeping only the entries with a corresponding "main" +%%%% entry. + +\AddToHook{enddocument/end}{% + \immediate\closeout\@indexfile % it is never explicitly closed! + \filterindex +} + +\newwrite\tempidx +\def\filterindex{% + \IfFileExists{\jobname.idx}{% + \begingroup + \catcode`\%=12 + \def\indexentry{\filterindex@parse\filterindex@first@pass}% + \immediate\openout\tempidx \jobname.idx.orig + \input \jobname.idx + \immediate\closeout\tempidx + \def\indexentry{\filterindex@parse\filterindex@second@pass}% + \immediate\openout\tempidx \jobname.idx + % \tracingall + \input \jobname.idx.orig + \immediate\closeout\tempidx + \endgroup + }{}% +} + +\def\filterindex@parse{% + \catcode`\\=12 + \filterindex@parse@i +} +\long\def\filterindex@parse@i#1#2#3{\filterindex@parse@ii#1#2{#3}} +\long\def\filterindex@parse@ii#1#2=#3hdclindex#4#5#6{% + \catcode`\\=0 + #1{#2}{#3}{#4}{#5}{#6}% +} + +\long\def\filterindex@first@pass#1#2#3#4#5{% + \filterindex@write{#1}{#2}{#3}{#4}{#5}% + \IfStrEq{#4}{main}{% + \csdef{\detokenize{filt@#1}}{}% + }{}% +} +\long\def\filterindex@second@pass#1#2#3#4#5{% + \ifcsdef{\detokenize{filt@#1}}{% + \filterindex@write{#1}{#2}{#3}{#4}{#5}% + }{% + }% +} +\long\def\filterindex@write#1#2#3#4#5{% + \immediate\write\tempidx{\string\indexentry\detokenize{{#1=#2hdclindex{#3}{#4}}{#5}}}% +} + + + +% Local Variables: +% TeX-engine: luatex +% TeX-master: "memoize-code.tex" +% TeX-auto-save: nil +% End:
\ No newline at end of file diff --git a/macros/generic/memoize/doc/memoize-code.tex b/macros/generic/memoize/doc/memoize-code.tex new file mode 100644 index 0000000000..6daa583383 --- /dev/null +++ b/macros/generic/memoize/doc/memoize-code.tex @@ -0,0 +1,120 @@ +\documentclass[a4paper,11pt]{ltxdoc} + +\usepackage[left=1cm,right=1cm,top=1cm,bottom=1cm, + marginparwidth=0cm,marginparsep=0pt, + nohead,includefoot +]{geometry} +\settowidth\marginparsep{\ } + +% There is a \mmzset in memoize-doc-common. +\usepackage{nomemoize} + +% Hypdoc (which loads hyperref) must be loaded before memoize-code. +\usepackage{hypdoc} +\hypersetup{colorlinks=true, allcolors=blue} + +\usepackage{memoize-code} % Also loads memoize-doc-common + +\pdfsystem{makeindex -s gind.ist \jobname.idx} + +\CodelineIndex +\EnableCrossrefs + +\title{The documented source of Memoize, Advice and CollArgs} +\author{SaÅ¡o Živanović\\[2mm] + \emailsymbol~\url(mailto:){saso.zivanovic@guest.arnes.si}\\ + \homepagesymbol~\url(http://){spj.ff.uni-lj.si/zivanovic}\\ + \faGithub~\url(https://){github.com/sasozivanovic}} + +\datefrompackageversion{collargs} +\edef\allpackagever{CollArgs v\packagever} +\datefrompackageversion{advice} +\epreto\allpackagever{Advice v\packagever, } +\datefrompackageversion{memoize} +\epreto\allpackagever{Memoize v\packagever, } +\let\packagever\allpackagever + +\hypersetup{ + % pdftitle={Memoize}, + pdfauthor={SaÅ¡o Živanović}, + pdfsubject={externalization}, + pdfkeywords={LaTeX, externalization, memoization} +} + +\begin{document} + +\maketitle + +\def\docdir{..} +\DocInput{\docdir/memoize.dtx} + +\section{Auxiliary packages} +\label{sec:code:utils} + +\subsection{Extending commands and environments with Advice} +\label{sec:code:auto} + +\DocInput{\docdir/advice.dtx} + +\subsection{Argument collection with CollArgs} +\label{sec:code:collargs} + +\DocInput{\docdir/collargs.dtx} + +\clearpage +\restoregeometry + +\section{The scripts} +\label{sec:code:scripts} + +\def\continuation{\llap{\textcolor{purple}{$\rightarrow$}\,}} + +\subsection{The Perl extraction script \texttt{memoize-extract.pl}} + +\lstinputlisting[firstline=20, % manual (exclude the license information) + language=Perl, + breaklines, breakindent=4em, postbreak=\continuation, + commentstyle=, +]{../memoize-extract.pl} + +\subsection{The Python extraction script \texttt{memoize-extract.py}} + +\lstinputlisting[firstline=20, % manual (exclude the license information) + language=Python, + breaklines, breakindent=4em, postbreak=\continuation, + commentstyle=, +]{../memoize-extract.py} + +\subsection{The Perl clean-up script \texttt{memoize-clean.pl}} + +\lstinputlisting[firstline=20, % manual (exclude the license information) + language=Perl, + breaklines, breakindent=4em, postbreak=\continuation, + commentstyle=, +]{../memoize-clean.pl} + +\subsection{The Python clean-up script \texttt{memoize-clean.py}} + +\lstinputlisting[firstline=20, % manual (exclude the license information) + language=Python, + breaklines, breakindent=4em, postbreak=\continuation, + commentstyle=, +]{../memoize-clean.py} + +\clearpage + +\IndexPrologue{\section*{Index}Numbers written in red refer to the code line + where the corresponding entry is defined; numbers in blue refer to the code + lines where the entry is used.} + +\setcounter{IndexColumns}{2} +\def\main#1{\begingroup\hypercolor{link}{red}#1\endgroup} +\PrintIndex + +\end{document} + +%%% Local Variables: +%%% mode: latex +%%% TeX-engine: luatex +%%% TeX-master: t +%%% End: diff --git a/macros/generic/memoize/doc/memoize-doc-common.sty b/macros/generic/memoize/doc/memoize-doc-common.sty new file mode 100644 index 0000000000..fe6ab0023f --- /dev/null +++ b/macros/generic/memoize/doc/memoize-doc-common.sty @@ -0,0 +1,173 @@ +%% memoize-doc-common.sty +%% +%% This file is a part of Memoize, a TeX package for externalization of +%% graphics and memoization of compilation results in general, available at +%% https://ctan.org/pkg/memoize and https://github.com/sasozivanovic/memoize. +%% +%% Copyright (c) 2020- Saso Zivanovic <saso.zivanovic@guest.arnes.si> +%% (Sa\v{s}o \v{Z}ivanovi\'{c}) +%% +%% This work may be distributed and/or modified under the conditions of the +%% LaTeX Project Public License, either version 1.3c of this license or (at +%% your option) any later version. The latest version of this license is in +%% https://www.latex-project.org/lppl.txt and version 1.3c or later is part of +%% all distributions of LaTeX version 2008 or later. +%% +%% This work has the LPPL maintenance status `maintained'. +%% The Current Maintainer of this work is Saso Zivanovic. +%% +%% The files belonging to this work and covered by LPPL are listed in +%% (<texmf>/doc/generic/memoize/)FILES. + +\ProvidesPackage{memoize-doc-common} + +\usepackage[inline,shortlabels]{enumitem} +\usepackage{tcolorbox} +\tcbuselibrary{listings,hooks,raster,skins} +\usepackage{tikz} +\usepackage{xstring} +\usepackage{microtype} + +\usepackage{listings} +\lstset{ + language=[LaTeX]TeX, + columns=fullflexible, + basicstyle=\ttfamily\small, + commentstyle=\color{purple}, + moredelim={[is][\pstyle]{~}{~}} +} +\lstMakeShortInline[moredelim={[is][\pstyle]{~}{~}}]{ˇ}% +\tcbset{ + linerange/.style={listing options app={linerange=#1}}, +} + + +%%%%%%%%%%%%%% +% title page % + +\usepackage{pdftexcmds} +\let\pdfsystem\pdf@system + +% version and date +\def\datefrompackageversion#1{% + \setbox0=\hbox{% + \def\ProvidesPackage##1[##2]{% + \gdef\@packagever{##2}% + \endinput + }% + \input{#1.sty}% + }% + \expandafter\parsepackagever@\@packagever\parsepackagever@ +} +\def\parsepackagever@#1/#2/#3 v#4 #5\parsepackagever@{% + \year=#1\relax\month=#2\relax\day=#3\relax + \def\packagever{#4}\def\packagedesc{#5}% +} + +\renewcommand\maketitle{% + \begingroup + \mmzset{disable, auto={tcolorbox}{clear options}}% \mmznext does not work here -- todo: maybe it does now + \begin{tcolorbox}[ + colback=emphcolor, + fonttitle=\LARGE\bf, + halign title=center, + halign upper=center, + halign lower=center, + tikznode lower, + adjusted title=\@title, + ] + \packagever\\ + \@date + \tcblower + \Large\@author + \end{tcolorbox} + \endgroup +} +\colorlet{emphcolor}{yellow!90!black} +\usepackage{marvosym} +\let\emailsymbol\Letter +\let\homepagesymbol\ComputerMouse +\usepackage{fontawesome} % for \faGithub + + +%%%%%%%%%%%%%%%%%%%%%%%% +% \verb@eol@error hack % + +% The problem: doc.sty (re)defines \verb "to check for newlines in its argument +% since a missing delimiter is difficult to detect in doc source." In +% principle, that's great, but I really hate it that I can't use Emacs's +% "fill-paragraph" without fear of getting the "Text for \noexpand\verb command +% ended by end of line" error. +% +% Solution: the original \verb@eol@error defines a newline (which is active at +% that point) to throw an error. We redefine \verb@eol@error to define a +% newline to gobble an immediately following percent character (assuming that +% we are processing .dtx documentation) and spaces. + +\begingroup + \obeylines% + \gdef\verb@eol@error{\obeylines% + \def^^M{\futurelet\verb@eol@nextchar\verb@eol@peek}% + }% +\endgroup +\gdef\verb@eol@peek{% + \if\@percentchar\verb@eol@nextchar + \expandafter\verb@eol@ignorepercentchar + \else + \space % we need one space, though! + \expandafter\verb@eol@ignorespaces + \fi +} +\def\verb@eol@ignorepercentchar#1{% #1 = "%" + \@xobeysp\verb@eol@ignorespaces} +\begingroup\catcode`\ =13 \gdef\verb@eol@otherspace{ }\endgroup +% |\ignorespaces| wouldn't work here, as our spaces are of category 13 +\def\verb@eol@ignorespaces{% + \futurelet\verb@eol@nextchar\verb@eol@ignorespaces@peek} +\def\verb@eol@ignorespaces@peek{% + \expandafter\ifx\verb@eol@otherspace\verb@eol@nextchar + \expandafter\verb@eol@ignorespaces@space + \fi +} +\def\verb@eol@ignorespaces@space#1{\verb@eol@ignorespaces} + +% The same hack, but for \lstinline of package listings. +% https://tex.stackexchange.com/a/473459/16819 + +\def\lst@InlineM#1{\gdef\lst@inlinechars{% + \lst@Def{`#1}{\lst@DeInit\egroup\global\let\lst@inlinechars\@empty}% + \lst@Def{13}{\lst@ProcessSpace}}% <--- + \lst@inlinechars} + + +%%%%%%%%%%% +% various % + +\usepackage{hologo} +\def\TikZ;{{\rm Ti\emph{k}Z}} +\def\PGF;{PGF} +\newcommand\PGFmanual[1]{% + \href + {http://mirrors.ctan.org/graphics/pgf/base/doc/pgfmanual.pdf} + {\S#1 of the Ti\emph{k}Z \& \textsc{PGF} manual}% +} +\newcommand\Alt{\,$\vert$\,} +\newcommand\hyp{\discretionary{}{}{}} +\newcommand\fncomma{\ensuremath{{^,}}} + + +% load hyperref before this + +\protected\def\hypercolor#1#2{% #1 = url/link/.., #2 = color + \expanded{% + \noexpand\HyColor@HyperrefColor{#2}\expandonce{\csname @#1color\endcsname}% + }% +} + +\RenewDocumentCommand\url{ O{} D(){} m }{% + {\hypercolor{url}{#1}\href{#2#3}{\texttt{#3}}}} + +\def\pkgcolor{black} +\NewDocumentCommand\pkg{O{\pkgcolor}m}{{% + \hypercolor{url}{#1}% + \href{https://ctan.org/pkg/#2}{\texttt{#2}}}} diff --git a/macros/generic/memoize/doc/memoize-doc.sty b/macros/generic/memoize/doc/memoize-doc.sty new file mode 100644 index 0000000000..2a1d7482c4 --- /dev/null +++ b/macros/generic/memoize/doc/memoize-doc.sty @@ -0,0 +1,711 @@ +%% memoize-doc.sty +%% +%% This file is a part of Memoize, a TeX package for externalization of +%% graphics and memoization of compilation results in general, available at +%% https://ctan.org/pkg/memoize and https://github.com/sasozivanovic/memoize. +%% +%% Copyright (c) 2020- Saso Zivanovic <saso.zivanovic@guest.arnes.si> +%% (Sa\v{s}o \v{Z}ivanovi\'{c}) +%% +%% This work may be distributed and/or modified under the conditions of the +%% LaTeX Project Public License, either version 1.3c of this license or (at +%% your option) any later version. The latest version of this license is in +%% https://www.latex-project.org/lppl.txt and version 1.3c or later is part of +%% all distributions of LaTeX version 2008 or later. +%% +%% This work has the LPPL maintenance status `maintained'. +%% The Current Maintainer of this work is Saso Zivanovic. +%% +%% The files belonging to this work and covered by LPPL are listed in +%% (<texmf>/doc/generic/memoize/)FILES. + +% \usepackage{lstdoc} % this must be loaded before hyperref +\usepackage{shortvrb} +\AtBeginDocument{\MakeShortVerb{\|}} +% \usepackage{hypdoc} +\usepackage[hyperindex=false]{hyperref} +\hypersetup{ + colorlinks=true, + allcolors=black, + linkcolor=Hyperlink, +} + +\usepackage{microtype} +%\SetExpansion[context=sloppy,stretch=30,shrink=50,step=1]{encoding=*}{}% + +\usepackage{fnpct} +\usepackage{memoize-doc-common} + +% explicitly load advice (and thereby collargs) for when we load nomemoize +\usepackage{advice} + +\usepackage{forest} +\useforestlibrary{edges} + +% \usepackage[extract=latex]{memoize} +% \usepackage{memoize} +\usepackage[infoshow]{tracefnt} + +\tcbuselibrary{xparse,breakable} + +\mmzset{ + auto={tcolorbox}{memoize, options=verbatim}, + auto={tcboxedraster}{memoize, options=verbatim}, + auto={tcbraster}{memoize, options={verbatim,ignore spaces}}, + auto={tcblisting}{memoize, options={verbatim,ignore spaces}}, + auto csname={tcbinputexample code}{memoize, args=mmm+m, ignore spaces}, + auto=\tcbinputlisting{memoize, args=m, options=ignore spaces}, + % The following doesn't work perfectly, but it's good enough. + auto key={/tcb/breakable}{run if memoizing, outer handler=\makeunmemoizableunless{false}}, + auto key={/tcb/float}{run if memoizing, outer handler=\makeunmemoizableunless{}}, +} + +\def\makeunmemoizableunless#1#2\pgfeov{% + \ifstrequal{#1}{#2}{}{\mmzUnmemoizable}% + \AdviceOriginal#1\pgfeov +} + +\newcommand\introboxtitle[1]{\strut\textcolor{emphcolor}{\textbf{#1}}} + +\usepackage{fancyhdr} +\usepackage[skip=0.3\baselineskip plus 2pt]{parskip} +\usepackage[bottom]{footmisc} +\usepackage{xparse} +\usepackage{multicol} + + + + +\usepackage{graphicx} + +\usepackage{cprotect} +\setlist[description]{ + itemsep=0.3\baselineskip plus 2pt minus 1pt, + parsep=0.3\baselineskip plus 2pt +} +\SetEnumitemKey{comma}{itemjoin={{, }}} +\SetEnumitemKey{and}{itemjoin={{, }}, itemjoin*={{, and }}} + +% \usepackage{lstdoc} % this must be loaded before hyperref +%\usepackage{lstautogobble} % todo: does not work! +\def\listings@file@value@in@reset{} +\tcbset{ + listing file/.append code={% + \ifmemoizing + \ifstrequal{#1}{\jobname.listing}{% + % We're in tcolorbox's reset, do nothing + }{% + \mmzset{ + context={% + listing file={#1}, + md5sum=\csuse{pdf@filemdfivesum}{#1}, + }, + }% + }% + \fi + }, + centering/.style={before=\centering}, +} +\colorlet{pointcolor}{green!50!black} +\def\pstyle{\color{pointcolor}}% todo: hyperlink? +\setlength\textfloatsep{0.5\baselineskip plus 2pt minus 2pt} +\tcbset{ + top=1mm, bottom=1mm, + beforeafter skip balanced=0.5\baselineskip plus 2pt minus 2pt, + raster every box/.style={top=1mm, bottom=1mm}, + before upper={\parskip0.3\baselineskip plus 2pt\relax}, + every box on layer 1/.style={ + fonttitle=\bfseries, + }, + my listing options/.style={ + listing options app={ + moredelim={[is][\pstyle]{~}{~}}, + commentstyle=\color{purple}, + morekeywords={unexpanded,expanded,expandonce,NewDocumentCommand,IfValueTF}, + breakindent=10pt, + }, + }, + my listing options, + % for listings inside a tcboxedraster ... + reset/.append style=my listing options, + % titles + attach shifted boxed title to top right/.style={ + attach boxed title to top right={ + xshift=1.5mm, yshift=-\tcboxedtitleheight/2, #1}, + }, + attach shifted boxed title to top right/.default={}, + attach shifted boxed title to top left/.style={ + attach boxed title to top left={ + xshift=-1.5mm, yshift=-\tcboxedtitleheight/2, #1}, + }, + attach shifted boxed title to top left/.default={}, + my boxed title/.style={ + enhanced, title={#1}, + fonttitle=\scriptsize, + boxed title style={size=small}, + }, + my boxed title/.value required, + extern page/.style={ + my boxed title=extern page, + boxed title style={colback=blue!70!black, colframe=blue!25!white}, + attach shifted boxed title to top right, + }, + document page/.style={ + my boxed title=document page, + boxed title style={colback=green!60!black, colframe=green!25!white}, + attach shifted boxed title to top right, + }, + % end titles + bad/.style={enhanced, + finish={ + \begin{tcbclipinterior} + \draw[red!40!white, opacity=0.4, line width=1cm] + (interior.south west)--(interior.north east); + \end{tcbclipinterior} + }, + }, + mark region/.style 2 args={ + enhanced, + underlay={ + \fill[pointcolor,opacity=0.1] + ([yshift=-\kvtcb@boxsep-\kvtcb@top-2.5ex*(#1-1)]interior.north west) rectangle ([yshift=-\kvtcb@boxsep-\kvtcb@top-2.5ex*#2]interior.north east); + }, + }, + warning/.style={colback=red!15}, + to be continued on right/.style={ + frame code={% line thickness is uniform and depends on tcolorbox's leftrule + \draw[color=tcbcolframe, + line width=2*\kvtcb@left@rule, rounded corners=2*\kvtcb@left@rule, + ] + (interior.north east)--(interior.north west) + --(interior.south west)--(interior.south east); + }, + interior code app={ + \draw[color=tcbcolframe,line width=\kvtcb@left@rule,dashed] + ([xshift=-0.5*\kvtcb@left@rule]interior.south east) + --([xshift=-0.5*\kvtcb@left@rule]interior.north east); + }, + }, +} +\def\examplepdfpath{\examplepath.c1.pdf} +\NewDocumentCommand{\includeexamplepdf}{ O{} O{\examplepdfpath} D(){} m}{% + \mbox{% + \tcbincludegraphics[reset,capture=hbox,graphics options={clip,#4},#1]{#2}#3% + }% +}% +\def\ExampleName#1{\edef\examplename{#1}\ignorespaces} +\def\makeexample#1{\pdfsystem{make -C \exampledir\space#1}\ignorespaces} +% \def\makeexample#1{\pdfsystem{make -B -C \exampledir\space#1}} +\def\examplepath{\exampledir\examplename} +\RequirePackage{intopdf} +\RequirePackage{fontawesome} +\tcbset{ + example title/.style={ + enhanced, + % uniform height and (zero) depth + before title={\begingroup \setbox0\hbox\bgroup}, + after title={\egroup\ht0 1.2ex \dp0 0pt \box0 \endgroup}, + title={\texttt{#1}}, + top=2mm, + attach shifted boxed title to top left, + fonttitle={\small\hypercolor{link}{white}}, + }, + example title/.default=\examplename.tex, + attachment/.estore in=\attachmentpath, + attachment name/.estore in=\attachmentname, + attachment name/.default=noname.tex, + no attachment/.style={attach/.style={}}, + attachment=\jobname.listing.attachment, + attachment name, + % attach example/.style={ + % attachment/.expanded=\examplepath.tex.work, + % attachment name/.expanded=\examplename.tex, + % attach, + % }, + attach/.style={ + overlay app={% + \node at (frame.north east) [ + fill=white, draw=gray, thick, + ellipse, inner xsep=-0.5mm, inner ysep=0, shift={(-2mm,-2mm)}, rotate=45, + ]{% + \pdfsystem{make -f examples/Makefile \attachmentpath}% + \expandafter\myattachandlink\expandafter[\attachmentname]{\attachmentpath}[application/x-tex]{Click here to open the code.}{\rotatebox{-45}{\faPaperclip}} + }; + } + }, + excerpt/.code 2 args={% + \makeexample{#2.excerpt SOURCE=#1}% + }, +} +\NewDocumentCommand\attachexample{O{\examplename.tex} O{\examplepath.tex.c1.attachment}}{% + {\textsuperscript{\kern-0.25em \expandafter\myattachandlink\expandafter[#1]{#2}[application/x-tex]{Click here to open the code.}{\rotatebox{-45}{\faPaperclip}}\kern-0.25em \relax}}} +\newcommand\myattachandlink[2][\filename]{% + \begingroup + \def\filename{#2}% + \immediate\write\attachments{#2 #1 ## line \the\inputlineno}% + \ifmemoizing + \xtoksapp\mmzCCMemo{% + \immediate\write\attachments{#2 #1 ## line \the\inputlineno}% + }% + \fi + \expanded{% + \endgroup + \noexpand\attachandlink[#1]{#2}% + }% +} +\newwrite\attachments +\immediate\openout\attachments attachments.lst +\AtEndDocument{\immediate\closeout\attachments} + +\NewTCBInputListing{\tcbinputexample}{ + >{\edef\ProcessedArgument} D(){\examplename} O{.tex} O{.c1} +m } +{% + /mmz/context={dtxmd5sum=\csuse{pdf@filemdfivesum}{\exampledir#1#2.dtx}}, + example title={#1#2}, + % enlarge left by=\leftmargin,% this takes care of using this box in lists + listing and comment, + one file/.style={ + listing file={\exampledir#1#2#3}, + attachment={\exampledir#1#2#3}, + }, + listing file=\exampledir#1#2#3.listing, + /utils/exec={\edef\examplepdfpath{\exampledir#1#3.pdf}},% + attachment=\exampledir#1#2#3.attachment, + attachment name=#1#2,% + #4, + attach, +} +\nommzkeys{context} + +\tcbset{ + full width/.style={grow to left by=\leftmargin}, + listing and compile/.style={ + listing and comment, + comment={% + \input\examplepath.tex.c#1 + } + }, + listing and compile/.default=1, +} + +\pgfkeys{ + /handlers/.detokenize/.style={\pgfkeyscurrentpath/.expand once=\detokenize{#1}}, + /utils/typeout/.code={\typeout{#1}}, +} +\let\pdfsystem\pdf@system +\let\pdffilemdfivesum\pdf@filemdfivesum + +\usepackage{booktabs} +\usepackage{tabularx} + + +\usetikzlibrary{decorations.pathmorphing, arrows.meta} +\usepackage{tikzlings-penguins,tikzlings-koalas,tikzlings-owls} +\tikzset{ + ->/.tip={Kite}, + snake arrow/.style= + {->, decorate, + decoration={snake, amplitude=.4mm, segment length=2mm, post length=2mm}, + }, +} + +% title page +\renewcommand\abstractname{\vskip-\baselineskip} + +% sections +\newtcbox{\section@box}[3]{tile,before=\noindent,after=, + fontupper=\normalfont#1\bfseries,capture=minipage, + colback=emphcolor, + boxsep=0mm,top=#2,bottom=#2,left=\oddsidemargin+1in,right=\oddsidemargin+1in, + spread sidewards,#3 +} + +\newif\ifsectionclearpage +\sectionclearpagetrue +\newif\ifsubsectionclearpage +\subsectionclearpagetrue + +\renewcommand\section{% + \ifsectionclearpage\clearpage\fi + \@startsection{section}{1}{\z@}% + {5mm}{5mm}% + {\section@box{\Large}{4mm}{fuzzy halo=1mm with blue!50!gray}}} +\renewcommand\subsection{% + \ifsubsectionclearpage\ifnum\c@subsection=0 \else\clearpage\fi\fi + \@startsection{subsection}{2}{\z@}% + {-3.25ex\@plus -1ex \@minus -.2ex}{1.5ex \@plus .2ex}% + {\section@box{\large}{2mm}{fuzzy halo=1mm with blue!50!gray}}} +\renewcommand\subsubsection{% + \mmznext{disable}% + \@startsection{subsubsection}{2}{\z@}% + {-3.25ex\@plus -1ex \@minus -.2ex}{1.5ex \@plus .2ex}% + {\section@box{}{2mm}{}}} +\newtcbox{\paragraph@box}{tile,before=,after=,capture=hbox, + fontupper=\normalfont\normalsize\bfseries\hypercolor{link}{black}\vphantom{dj}, + box align=base, + boxsep=0mm, + left=\oddsidemargin+1in, enlarge left by=-\oddsidemargin-1in, + top=1.2mm, enlarge top by=-1.2mm, + bottom=1mm, enlarge bottom by=-1mm, + right=12mm, enlarge right by=-8mm, arc=0mm, rightrule=0mm, + interior style={left color=emphcolor, right color=white}, +} +\let\oldparagraph\paragraph +\def\paragraphskip{3.25ex \@plus1ex \@minus.2ex} +\RenewDocumentCommand\paragraph{sm}{% + \mmznext{disable}% + \@startsection{paragraph}{4}{\z@}{\paragraphskip}{-1em}{\paragraph@box}{#2}% + \IfBooleanT{#1}{\hspace*{\dimexpr-8mm+0.5em}}% + \hskip 0pt plus .5em + \IfBooleanF{#1}{\nopagebreak}% % todo: this is bad, if the paragraph is long +} + +\NewTotalTCBox{\Emph}{m}{my code box, fontupper=\rm}{#1} +\NewTotalTCBox{\EmphTT}{m}{my code box}{#1} +\NewTotalTCBox{\EmphVerbatim}{v}{my code box}{#1} +\tikzset{ + emphshade/.style={ + opacity=0.6, + top color=#1, bottom color=#1, middle color=emphcolor, + }, + emphshade/.default=white, +} +\tcbset{ + my code box/.style={ + reset, enhanced, verbatim, + boxsep=0pt, left=0.3ex,right=0.3ex, box align=base, + interior hidden, frame hidden, + overlay={\shade[emphshade] + ([yshift=0.7ex]frame.north west) rectangle ([yshift=-0.7ex]frame.south east); + }, + }, +} + +% verbatim code in the implementation +\lstdefinestyle{code}{style=tcblatex,xleftmargin=\leftmargin,aboveskip=\parskip} + + + + + + +\newcommand\therefore{\ensuremath{\Rightarrow}} +\newcommand\nohyphen{\discretionary{}{}{}} +\let\gobble\@gobble + +\lstset{ + rangebeginprefix=\ \ \%\%\%\ begin\ listing\ region:\ , + rangebeginsuffix=, + rangeendprefix=\ \ \%\%\%\ end\ listing\ region:\ , + rangeendsuffix=, + includerangemarker=false, + keepspaces=true, +} + +\def\relaxmmzcommands{% + \let\mmzPrefix\gobble + \let\mmzNewCMemo\gobble + \let\mmzNewCCMemo\gobble + \def\mmzNewExtern##1##2##3##4##5{}% + \let\mmzUsedCMemo\gobble + \let\mmzUsedCCMemo\gobble + \let\mmzUsedExtern\gobble +} + +\usepackage{xstring} +\usepackage{placeins} + +\NewDocumentCommand\makeexcerpt{ m O{.tex} D(){../../memoize.edtx} }{% + \ExampleName{#1}% + \makeexample{#1#2.excerpt SOURCE=#3}% + \ignorespaces +} + +\newcommand\sed[2]{% + \begingroup + \def\cmd##1{\noexpand\\##1}% + \def\0{\bslash0}% + \def\1{\bslash1}% + \def\2{\bslash2}% + \def\3{\bslash3}% + \def\4{\bslash4}% + \def\5{\bslash5}% + \def\6{\bslash6}% + \def\7{\bslash7}% + \def\8{\bslash8}% + \def\9{\bslash9}% + \def~{\noexpand~}% + \def\n{\string\n}% + \def\({\bslash(}% + \def\){\bslash)}% + \let\lbrace\sedlbrace + \let\rbrace\sedrbrace + \edef\nobrace{[^\sedlbrace\sedrbrace]} + \edef\marg{{[^\sedrbrace]*}}% + \def\repeat##1{\bslash\sedlbrace##1\bslash\sedrbrace}% + \pdfsystem{sed -i "#1" #2}% + \endgroup +} +\begingroup +\catcode`\(=1 +\catcode`\)=2 +\catcode`\{=12 +\catcode`\}=12 +\gdef\sedlbrace({)% +\gdef\sedrbrace(})% +\endgroup + +\usepackage{pifont} +\newcommand\yes{{\small\ding{51}}} % pifont +\newcommand\no{{\small\ding{55}}} % pifont + +%\newcommand\oarg[1]{{\ttfamily[}\meta{#1}{\ttfamily]}} + +\definecolor{Option}{rgb}{0.118,0.546,0.222} +\definecolor{Definition}{rgb}{0.784,0.06,0.176} +\definecolor{ExampleFrame}{rgb}{0.628,0.705,0.942} +\definecolor{ExampleBack}{rgb}{0.963,0.971,0.994} +\definecolor{Hyperlink}{rgb}{0.281,0.275,0.485} +\definecolor{Fade}{rgb}{0.5,0.5,0.5} +\definecolor{EasyDefinition}{rgb}{0.06,0.784,0.176} + +{\catcode`\|=\z@ \catcode`\\=12 +|gdef|bslash{\}} +\DeclareRobustCommand\cs[1]{\texttt{\bslash\detokenize{#1}}}% +\DeclareRobustCommand\env[1]{\texttt{#1}}% +%\def\marg#1{{\ttfamily\char`\{}\meta{#1}{\ttfamily\char`\}}} +\def\marg#1{\hskip 0pt plus 1pt{\ttfamily\char`\{}\meta{#1}{\ttfamily\char`\}}} +\def\oarg#1{\hskip 0pt plus 1pt{\ttfamily[}\meta{#1}{\ttfamily]}} +\def\bracestt#1{\hskip 0pt plus 1pt{\ttfamily\char`\{#1\char`\}}} +\def\braces#1{\hskip 0pt plus 1pt{\ttfamily\char`\{}#1{\ttfamily\char`\}}} +\def\bracketstt#1{\hskip 0pt plus 1pt{\ttfamily[#1]}} +\def\brackets#1{\hskip 0pt plus 1pt{\ttfamily[}#1{\ttfamily]}} +\DeclareRobustCommand\meta[1]{% + \ensuremath\langle + \ifmmode \expandafter \nfss@text \fi + {% + \meta@font@select + \edef\meta@hyphen@restore{\hyphenchar\the\font\the\hyphenchar\font}% + \hyphenchar\font\m@ne + \language\l@nohyphenation + #1\/% + \meta@hyphen@restore + }% + \ensuremath\rangle +} +\def\meta@font@select{\rm\itshape} +\DeclareRobustCommand\metabf[1]{% + \begingroup + \def\meta@font@select{\rm\itshape\bfseries}% + \meta{#1}% + \endgroup +} +{\catcode`\%=12 \gdef\percentchar{%}} +\def\coloredpercentchar{\hbox{\textcolor{purple}{\percentchar}}} +\newcommand\alt{\ensuremath{\vert}}% without space around, unlike \Alt +\newcommand\code[1]{\texttt{\spaceskip 0.5em plus 0.2em minus 0.2em #1}} +\newcommand\Arg[1]{\texttt{\##1}} + +\def\TikZ{\href{https://ctan.org/pkg/pgf}{{\rm Ti\emph{k}Z}}\xspace} +\def\PGF{\href{https://ctan.org/pkg/pgf}{PGF}\xspace} +\def\Emacs{\href{https://www.gnu.org/software/emacs}{Emacs}\xspace} +\def\AucTeX{\href{https://www.gnu.org/software/auctex}{AUC\hologo{TeX}}\xspace} + +% collargs reference, pi = programmer's interface +% Shrink the glyphs in the footnote if necessary. +\tcbset{ + pi/.style={ + overlay={% + \node at (frame.north east) [anchor=north west, inner sep=0] {\footnotemark}; + }, + after app={% + \footnotetext{% + % \adjustspacing=2 % already 2 ... microtype? + \myfontexpansionon + \yadocset{before/.code={\cspreto{yadoc@name@prefix}{\myfontexpansionon}}}% + \mbox{The programmer's interface:} #1.% + }% + }, + }, +} +\DeclareRobustCommand\myfontexpansionon{% + \directlua{font.setexpansion(font.current(),50,50,1)}% +} +\DeclareRobustCommand\myfontexpansionoff{% + \directlua{font.setexpansion(font.current(),0,0,1)}% +} + +\AtBeginDocument{% + \setbox0=\hbox{% + \footnotesize\ttfamily + \directlua{ninettfont=font.current()}% + }% +} + +% \AtBeginDocument{% +% \setbox0=\hbox{% +% \footnotesize\ttfamily +% \global\expandafter\let\expandafter\fnttfont\font@name +% %\expandglyphsinfont\fnttfont 30 500 1 autoexpand +% }% +% } + +\usepackage{yadoc} +\newif\ifyadoc@short +\newif\ifyadoc@long +\yadocset{ + name color=Definition, + before body=\parskip 0.3\baselineskip plus 2pt, + % after body=\medskip, + head/.style={ + interior style={fill, left color=red!5,right color=red!5!black!2}, + }, + easy/.style={ + name color=green!50!black, + head/.style={ + interior style={fill, left color=green!5,right color=black!5}, + }, + }, + keypath color opacity=30, + index encapsulator={`}, + index annotation font=\color{gray}, + def options/.append style={index page number format=colorhyperpage{red}}, + aux options/.append style={index page number format=colorhyperpage{red}}, + ref options/.append style={index page number format=hyperpage}, + par/.style={parameters={#1}}, + desc/.style={description={#1}}, + keypath=/mmz, + xparse modifier item/.style={ + index annotation=\texttt{xparse} modifier, + label prefix=xparse, + }, + xparse type item/.style={ + index annotation=\texttt{xparse} type, + label prefix=xparse, + }, + script item/.style={ + index annotation=script, + label prefix=script, + parameters prefix=\space, + }, + key path item/.style={ + index annotation=keypath, + label prefix:=keypath:/, + name prefix=/, + description=keypath, + }, + option item/.style={ + name prefix={\ifdefempty\yadoc@short@name{}{-\yadoc@short@name\,\Alt\,}--}, + ref prefix={\protect\ifyadoc@short-\yadoc@short@name\protect\else--}, + ref suffix={\protect\fi}, + index prefix={--}, + index suffix={\ifdefempty\yadoc@short@name{}{\Alt-\yadoc@short@name}}, + parameters prefix=\ , + label prefix:={\yadoc@of--}, + index annotation=option of {\docref[into index=false,link color=gray]{\yadoc@of}}, + }, + short name/.store in=\yadoc@short@name, + short/.is if=yadoc@short, + long/.is if=yadoc@long, + conditional/.style={ + parameters={\texttt{true}\Alt\texttt{false}}, + /utils/exec={% + \appto\yadoc@description{default \texttt{true}, initially \texttt{#1}}% + }, + }, + conditional/.value required, + package item/.style={ + index annotation=package, + label prefix=pkg, + description=package, + }, + pkg/.style={ + after/.style={index annotation/.append={\space of package \pkg{#1}}} + }, + hook item/.style={ + label prefix=hook, + index annotation=hook, + }, + pdfvariable item/.style={ + label prefix=pdfvar, + index annotation={\hologo{LuaTeX}'s \refcmd{pdfvariable} register}, + }, + pdfcmd item/.style={ + label prefix=cmd, + name prefix={% + \textbackslash + \protect\ifyadoc@short + \protect\else + \protect\ifyadoc@long + pdf% + \protect\else + % todo: revert to the real original font rather than just say \rmfamily + \rmfamily(\ttfamily pdf\rmfamily)\ttfamily + \protect\fi + \protect\fi + }, + index prefix=\textbackslash, + }, +} +\def\colorhyperpage#1#2{% + \begingroup + \hypercolor{link}{#1}% + \hyperpage{#2}% + \endgroup +} + +\patchcmd{\@setref}{??}{#3??}{}{} +\patchcmd{\real@setref}{??}{#3??}{}{} + +\mmzset{ + auto={tcb@manual@entry}{nomemoize}, + auto={doc}{nomemoize}, +} + +\DeclareRobustCommand\refkey[2][]{\docref[#1]{key:#2}} +\DeclareRobustCommand\refmmz[2][]{\docref[#1]{key:/mmz/#2}} +\DeclareRobustCommand\refmmzauto[2][]{\docref[#1]{key:/mmz/auto/#2}} +\DeclareRobustCommand\refmmzpath[2][]{\docref[#1]{key:/mmz/path/#2}} +\DeclareRobustCommand\refcollargs[2][]{\docref[#1]{key:/collargs/#2}} +\DeclareRobustCommand\refenv[2][]{\docref[#1]{env:#2}} +\DeclareRobustCommand\refcmd[2][]{\docref[#1]{cmd:#2}} +\DeclareRobustCommand\reffile[2][]{\docref[#1]{file:#2}} +\DeclareRobustCommand\refkeypath[2][]{\docref[#1]{keypath:#2}} +\DeclareRobustCommand\refpkg[2][]{\docref[#1]{pkg:#2}} +\DeclareRobustCommand\refscript[2][]{\docref[#1]{script:#2}} + +\usepackage{xspace} +\newcommand\dmmz{\docref{file:mmz}\xspace} + +\newcommand\hreftt[2]{\href{#1}{\texttt{#2}}} + +\ifcsname ifregion\endcsname + \ifregion{% + \AddToHook{begindocument/before}{% + \makeatletter + \InputIfFileExists{memoize.aux}{}{}% + \makeatother + }% + \AddToHook{enddocument}{% + \printindex + }% + } +\fi + +\AddToHook{begindocument}{% + \robustify\hyperref + \robustify\hologo + \robustify\Alt +} + +\newcommand\noprint[1]{\setbox0=\hbox{#1}} +\usepackage{wrapfig} + + +% \usepackage{caption} +% \captionsetup{labelformat=empty} + +% Local Variables: +% TeX-master: "memoize.tex" +% TeX-engine: luatex +% End: diff --git a/macros/generic/memoize/doc/memoize-extract.1.md b/macros/generic/memoize/doc/memoize-extract.1.md new file mode 100644 index 0000000000..434f2fea2f --- /dev/null +++ b/macros/generic/memoize/doc/memoize-extract.1.md @@ -0,0 +1,94 @@ +--- +title: memoize-extract +section: 1 +header: User Manual +footer: memoize-extract of Memoize v1.0.0 +date: October 10, 2023 +hyphenate: false +--- + +# NAME +memoize-extract.pl, memoize-extract.py - Extract extern pages out of the PDF + + +# SYNOPSIS +**memoize-extract.pl** [*OPTIONS*] *document.mmz* + +**memoize-extract.py** [*OPTIONS*] *document.mmz* + + +# DESCRIPTION + +**memoize-extract** is a script accompanying Memoize, a TeX package which +allows the author to reuse the results of compilation-intensive code such as +TikZ pictures. + +Memoize dumps the created externs (boxes containing the typeset material to be +reused) onto their own pages in the produced PDF file. It is the job of +**memoize-extract** to extract these extern pages into separate PDF files. At +subsequent compilations, Memoize will include those extern files into the +document, without compiling their source again. + +Memoize communicates with **memoize-extract** through file *document.mmz*. When +*document.tex* is compiled to produce *document.pdf*, Memoize produces +*document.mmz*, which records which pages in the produced document are extern +pages and to which extern files they should be extracted. Therefore, after +compiling *document.tex*, the externs should be extracted by +**memoize-extract** *document.mmz*. + +*document.mmz* also records the expected width and height of each extern. In +case of a mismatch, **memoize-extract** refuses to extract the page and removes +the extern file if it already exist, and prints a warning message to the +standard error. The script furthermore refuses to extract the page if a +(c)c-memo associated to the extern does not exist, and to write to any file +whose absolute path does not occur under the current directory or the directory +set by TEXMFOUTPUT (in *texmf.cnf* or as an environment variable); TEXMFOUTPUT +is also not allowed to point to the root directory, except on Windows, where it +can only point to a drive root. + +The Perl (.pl) and the Python (.py) version of the script are functionally +equivalent. The Perl script requires library +[PDF::API2](https://metacpan.org/pod/PDF::API2), and the Python script requires +library [pdfrw2](https://pypi.org/project/pdfrw2). + +# OPTIONS + +**-P, \--pdf** *filename.pdf* +: The externs will be extracted from *filename.pdf*. By default, + they are extracted from *document.pdf*. + +**-p, \--prune** +: Remove the extern pages from the PDF after extraction. + +**-k, \--keep** +: Do not modify the *document.mmz* to mark the externs as extracted. By + default, they are commented out to prevent double extraction. + +**-f, \--force** +: Extract the extern even if the size-check fails. + +**-l, \--log** *filename* +: Log size mismatches to *filename* rather than to the standard error. + +**-w, \--warning-template** *string* +: Wrap the size mismatch warning text into the given template: \\warningtext in + the template will be replaced by the warning text. + +**-q, \--quiet** +: Don't describe what's happening. + +**-e, \--embedded** +: Prefix all messages to the standard output with the script name. + +**-m, \--mkdir** +: A paranoid *mkdir -p*. (No extraction occurs, *document.mmz* is interpreted as a directory name.) + +**-h, \--help** +: Show help and exit. + +**-V, \--version** +: Show the Memoize version number and exit. + +# SEE ALSO + +[Memoize manual](https://ctan.org/pkg/memoize), section 6.6.1. diff --git a/macros/generic/memoize/doc/memoize.mst b/macros/generic/memoize/doc/memoize.mst new file mode 100644 index 0000000000..68a11283a5 --- /dev/null +++ b/macros/generic/memoize/doc/memoize.mst @@ -0,0 +1 @@ +encap '`' diff --git a/macros/generic/memoize/doc/memoize.pdf b/macros/generic/memoize/doc/memoize.pdf Binary files differnew file mode 100644 index 0000000000..5642f641b6 --- /dev/null +++ b/macros/generic/memoize/doc/memoize.pdf diff --git a/macros/generic/memoize/doc/memoize.tex b/macros/generic/memoize/doc/memoize.tex new file mode 100644 index 0000000000..8bc647f0ba --- /dev/null +++ b/macros/generic/memoize/doc/memoize.tex @@ -0,0 +1,9094 @@ +\documentclass[a4paper,11pt]{article} +\usepackage[margin=2cm,top=1cm,bottom=1cm,nohead,includefoot]{geometry} + +\usepackage{nomemoize} +\usepackage{memoize-doc} +\mmzset{ + % recompile, + % readonly, + % disable, + % inverse search=false, + % include context in ccmemo, + % direct ccmemo input, + % trace, +} +\def\exampledir{examples/} + +\title{\pkg[white]{Memoize}} +\author{SaÅ¡o Živanović\\[2mm] + \emailsymbol~\url(mailto:){saso.zivanovic@guest.arnes.si}\\ + \homepagesymbol~\url(http://){spj.ff.uni-lj.si/zivanovic}\\ + \faGithub~\url(https://){github.com/sasozivanovic}} +\datefrompackageversion{memoize} +\preto\packagever{v} + +\hypersetup{ + pdftitle={Memoize}, + pdfauthor={SaÅ¡o Živanović}, + pdfsubject={externalization}, + pdfkeywords={LaTeX, externalization, memoization} +} + +\usepackage{makeidx} +\pdfsystem{makeindex -s memoize.mst \jobname.idx} +\makeindex +\usepackage[hangindent=1em,justific=raggedright,font=small]{idxlayout} +\appto\indexfont{\def\pkgcolor{gray}} + +\AddToHook{begindocument}{% so that the region compilation takes these in + \docForeign{ + scope={ + after/.style={index annotation/.prefix={\hologo{LaTeX}\space}}, + cmd={name=begin}, cmd={name=end}, + cmd={name=label}, cmd={name=@currentlabel}, + cmd={name=ref}, cmd={name=pageref}, + cmd={name=index}, + cmd={name=NewDocumentCommand}, cmd={name=GetDocumentCommandArgSpec}, + env={name=verbatim}, + cmd={name=ReadonlyShipoutCounter}, + hook={name=begindocument}, + cmd={name=usepackage}, + }, + scope={ + before/.style={index annotation=\hologo{TeX} primitive}, + cmd={name=shipout}, + cmd={name=hbox}, cmd={name=vbox}, + cmd={name=ignorespaces}, cmd={name=unskip}, + cmd={name=errmessage}, + cmd={name=catcode}, + cmd={name=mag}, + }, + scope={ + before/.style={index annotation=\hologo{eTeX} primitive}, + cmd={name=scantokens}, + cmd={name=currentgrouplevel}, + }, + scope={ + before/.style={index annotation=\hologo{pdfTeX} primitive}, + cmd={name=quitvmode}, + }, + scope={ + before/.style={index annotation=\hologo{LuaTeX} primitive}, + pdfcmd={name=savepos}, + cmd={name=pdfvariable}, + }, + pdfvariable={name=compresslevel}, + pdfvariable={name=horigin}, + pdfvariable={name=vorigin}, + scope={ + after/.style={index annotation/.prefix={\hologo{ConTeXt}\space}}, + cmd={name=realpageno}, + }, + pdfcmd={name=primitive, index annotation=\hologo{LuaTeX}/\hologo{XeTeX} primitive}, + register={name=pdfmajorversion}, + register={name=pdfminorversion}, + scope={ + after/.style={index annotation/.prefix={\hologo{plainTeX}\space}}, + cmd={name=llap}, cmd={name=rlap}, + }, + scope={ + pkg=forest, + env={name=forest}, + cmd={name=Forest}, + }, + scope={ + pkg=tikz, keypath=/tikz, + cmd={name=tikz}, + env={name=tikzpicture}, + key={name=remember picture}, + key={name=overlay}, + cmd={name=tikzexternalize}, + cmd={name=pgfsys@getposition}, + }, + file={name=texmf.cnf}, + env={name=tcblisting, pkg=tcolorbox}, + cmd={name=vref, pkg=varioref}, + cmd={name=cref, pkg=cleveref}, + cmd={name=crefrange, pkg=cleveref}, + xparse type={name=l, index annotation/.append={\ (up to begin-group)}}, + xparse type={name=u, index annotation/.append={\ (until tokens)}}, + xparse type={name=r, index annotation/.append={\ (required delimited)}}, + xparse type={name=R, index annotation/.append={\ (required delimited with default)}}, + xparse type={name=d, index annotation/.append={\ (optional delimited)}}, + xparse type={name=D, index annotation/.append={\ (optional delimited with default)}}, + xparse type={name=o, index annotation/.append={\ (standard optional)}}, + xparse type={name=O, index annotation/.append={\ (standard optional with default)}}, + xparse type={name=t, index annotation/.append={\ (optional token)}}, + xparse type={name=s, index annotation/.append={\ (optional star)}}, + xparse type={name=m, index annotation/.append={\ (mandatory)}}, + xparse type={name=g, index annotation/.append={\ (optional group)}}, + xparse type={name=G, index annotation/.append={\ (optional group with default)}}, + xparse type={name=v, index annotation/.append={\ (verbatim)}}, + xparse type={name=e, index annotation/.append={\ (embellishments)}}, + xparse type={name=E, index annotation/.append={\ (embellishments with defaults)}}, + xparse modifier={name=+, index annotation/.append={\ (long)}}, + xparse modifier={name=!, index annotation/.append={\ (don't ignore spaces)}}, + xparse modifier={name=>, index annotation/.append={\ (argument processor)}}, + generic={name=shell-escape, name prefix=-, label prefix:=-, + index annotation=option of \hologo{TeX} binaries}, + generic={name=enable-write18, name prefix=--, label prefix:=--, + index annotation=option of \hologo{TeX} binaries}, + generic={name=TEXMFOUTPUT, index annotation={variable in + \reffile[into index=false]{texmf.cnf}}}, + }% + \docindex{% + cmd={name=pdfprimitive, see={\refcmd[into index=false,short]{primitive}}}, + cmd={name=pdfsavepos, see={\refcmd[into index=false,short]{savepos}}}, + cmd={name=pdfhorigin, see={\docref[into index=false]{pdfvar:horigin}}}, + cmd={name=pdfvorigin, see={\docref[into index=false]{pdfvar:vorigin}}}, + cmd={name=pdfcompresslevel, see={\docref[into index=false]{pdfvar:compresslevel}}}, + pdfvariable={name=majorversion, see={\docref[into index=false,long]{reg:pdfmajorversion}}}, + pdfvariable={name=minorversion, see={\docref[into index=false,long]{reg:pdfminorversion}}}, + }% +} + +\begin{document} + + +\maketitle +\thispagestyle{empty} + +\begin{abstract} + Memoize is a package for externalization of graphics and memoization of + compilation results in general, allowing the author to reuse the results of + compilation-intensive code. + Memoize + \begin{enumerate*}[(i)] + \item induces very little overhead, as all externalized graphics is produced + in a single compilation. It features + \item automatic recompilation upon the change of code or user-adjustable + context, and + \item automatic externalization of \TikZ pictures and Forest trees, easily + extensible to other commands and environments. Furthermore, Memoize + \item supports cross-referencing, \TikZ overlays and Beamer, + \item works with all major engines and formats, and + \item is adaptable to any workflow. + \end{enumerate*} +\end{abstract} + +\bigskip + +\ExampleName{titlepage} +\makeexample{\examplename.pdf N=2} % N=2, because we show the .mmz file later +\begingroup +\tcbset{page box/.style={ + box align=center,boxsep=2mm,top=0pt,bottom=0pt,left=0pt,right=0pt, + enhanced, remember as=#1, + }}% +\begin{tcboxedraster} + [raster columns=1] + {title=The two steps of externalization of graphics in Memoize} + \tcbinputexample{ + title=\tt doc.tex, + listing only, + remember as=tex, + } + \begin{tcolorbox}[example title=doc.pdf, no attachment, remember as=pdf1] + \includeexamplepdf[page box=extern1]{page=1,trim=1in 1in 1in 1in}\hfill + \includeexamplepdf[page box=extern2]{page=2,trim=1in 1in 1in 1in}\hfill + \includeexamplepdf[page box=extern3]{page=3,trim=1in 1in 1in 1in}\hfill + \includeexamplepdf[page box=docpage]{page=4}% + \end{tcolorbox} + \vspace{10mm} + \begin{tcboxedraster}[raster columns=3, raster column skip=1cm]{blankest} + \begin{tcolorbox}[example title=.pdf]\centering + \includeexamplepdf[page box=externfile1]{page=1,trim=1in 1in 1in 1in}\hfill + \end{tcolorbox} + \begin{tcolorbox}[example title=.pdf]\centering + \includeexamplepdf[page box=externfile2]{page=2,trim=1in 1in 1in 1in}\hfill + \end{tcolorbox} + \begin{tcolorbox}[example title=.pdf]\centering + \includeexamplepdf[page box=externfile3]{page=3,trim=1in 1in 1in 1in}\hfill + \end{tcolorbox} + \end{tcboxedraster} +\end{tcboxedraster} + +\medskip + +\begin{tcolorbox}[title=Using the externalized graphics] + \begin{tcolorbox}[example title=doc.pdf, + halign=center, remember as=pdf2] + \tcbox[remember, enhanced]{\footnotesize + We all love Ti\emph{k}Zlings! + \def\mytikzling#1#2{% + \tcbox[blankest, baseline=1mm, remember as=used extern #1] + {\tikz[x=0.3cm,y=0.3cm]{#2;}}}% + \mytikzling1{\penguin} is a penguin, + \mytikzling2{\koala} is a koala, and + \mytikzling3{\owl} is an owl. + } + \end{tcolorbox} +\end{tcolorbox} + +\begin{tikzpicture}[remember picture, overlay] + \draw[->, snake arrow, thick, red] (tex.west) to[out=-90-60, in=90+60] (pdf1.west); + \draw[->, snake arrow, thick, red] (tex.east) to[out=-90+19, in=90-19] (pdf2.east); + \draw[->, thick, red] (extern1.south) to[out=south, in=north] (externfile1); + \draw[->, thick, red] (extern2.south) to[out=south, out looseness=1.1, in=north, in looseness=0.9] (externfile2); + \draw[->, thick, red] (extern3.south) to[out=south, out looseness=0.7, in=north, in looseness=0.9] (externfile3); + \draw[->, thick,blue] (externfile1) to[out=south, in=north] ([yshift=0.8mm]used extern 1.north); + \draw[->, thick,blue] (externfile2) to[out=south, in=north] (used extern 2.north); + \draw[->, thick,blue] (externfile3) to[out=south, in=north] ([yshift=0.3mm]used extern 3.north); +\end{tikzpicture} +\endgroup + +\begin{tcolorbox}[colback=emphcolor, fontupper=\footnotesize] + This manual also documents packages Advice + ({\datefrompackageversion{advice}v\packagever}) and CollArgs + ({\datefrompackageversion{collargs}v\packagever}). These are auxiliary + packages which were developed alongside Memoize, but are distributed as + independent packages as they might be useful outside the context of Memoize, + as well. See sections~\ref{sec:advice} and \ref{sec:ref:advice} for Advice, and + sections~\ref{sec:collargs} and \ref{sec:ref:collargs} for CollArgs. +\end{tcolorbox} + + + +\section*{Introduction} +\label{sec:intro} + +\begin{tcolorbox}[ + title={\introboxtitle{What} is externalization and why you might want it?}, + ] + If you have ever worked on a long document full of \TikZ pictures and maybe + Forest trees, you have probably had some compilation-enforced coffee breaks + --- even on modern computers, compiling pictures, trees and such takes a lot + of time. And you might have wondered, why do I need to compile these + pictures over and over again? --- after all, I'm not changing them anymore! + Enter \emph{externalization}, a mechanism designed to deal precisely with + your issue, by saving the produced pictures into separate PDFs and including + those PDFs in subsequent compilations --- in no time at all! +\end{tcolorbox} + +\begin{tcolorbox}[ + title={\introboxtitle{Why} yet another externalization package?}, + ] + \TikZ, the popular and all-powerful graphics language for \TeX, ships + including an externalization library (described in \PGFmanual{53}). \TikZ's + library does an excellent job, but with one caveat. Assume you're using it + for the first time (or after a clean-up) on that long document full of \TikZ + pictures and Forest trees. It will take ages to produce all the externalized + graphics. Why? Because to get you up to speed, your document has to be + compiled many many times --- once for each and every externalized picture. + Even with \TikZ's advanced mechanism for skipping the parts of the document + irrelevant for the picture at hand, the first externalization can be a + daunting task. +\end{tcolorbox} + +\begin{tcolorbox}[ + title={\introboxtitle{How} does Memoize save your time?}, + ] + There is a reason why \TikZ uses an entire compilation cycle to produce a + single externalized picture: \TeX\ itself can only produce a single PDF per + compilation (at least at the moment). Memoize evades this limitation by + dumping the externalized pictures right in the middle of the document + (ouch!). More precisely, an externalized picture occurs in the PDF twice, + first on a special page of its own and then on a regular page, where you + intended it to be. The daring dump obviously necessitates a second step of + the procedure, when those special pages are \emph{extracted} into separate + PDFs, called \emph{externs}, which are then included into the document in + subsequent compilations. + + This two-step procedure, illustrated on the cover page of this manual, is + very fast. The first step, which externalizes \emph{all} the pictures into + the document itself (the squiggly red arrow), takes virtually no more time + than a regular compilation. The time needed for the second step, extraction + (the normal red arrows), depends on the system setup, but it ranges from + little to almost none. +\end{tcolorbox} + +\begin{tcolorbox}[ + title={\introboxtitle{When} should I use Memoize?}, + ] + In short, whenever you are writing a document containing lots of \TikZ + pictures, Forest trees, or other time-consuming constructs, and you are bored + waiting for the compilation to finish. + + Using Memoize on a paper containing a single picture does not make much of a + difference. But with more complex documents, the speed-up can be immense. + For example, the compilation time of my 400-page book containing about 160 + Forest trees was reduced by more than half, and the compilation time of a + 260-page Beamer presentation with a hundred complex dynamic trees went from + four minutes to a mere half minute! +\end{tcolorbox} + + +\begin{tcolorbox}[ + title={\introboxtitle{How} much extra work does Memoize require?}, + ] + In principle, none. + + For one, while allowing for manual memoization of selected document chunks + (\S\ref{sec:tut:memoize}), Memoize features a system which automatically + triggers memoization at each invocation of selected commands and + environments. Out of the box, Memoize \emph{automemoizes} \TikZ + pictures\noprint{\refenv{tikzpicture}} and Forest + trees\noprint{\refenv{forest}}, but the author can easily submit (almost) any + command or environment to automemoization (\S\ref{sec:tut:automemoization}). + Memoize also does its best to automatically prevent memoization of code that + cannot be externalized, like \TikZ pictures with \refkey{/tikz/remember + picture}, and to abort memoization in case the memoized code yields any + errors. +\end{tcolorbox} + + +\begin{tcolorbox}[ + title={\introboxtitle{Why} is Memoize not called Externalize?}, + ] + Fundamentally, Memoize is about producing and utilizing \emph{memos} --- + pieces of \hologo{TeX} code replicating the effect of the compilation of a + document chunk in a computationally less intensive manner. Typically, each + memo has an associated extern, which is where the effect of + \emph{typesetting} is stored, but conceptually, memos come first. For + example, the extern is included back into the document by the memo, and a + memo may be associated with any number of externs, including zero. + + Memos solve several externalization-related problems in a generic fashion, + allowing for a multitude of applications. For example, they store the + information about the associated externs, so that an extern can be integrated + back into the document as a box with the original orientation, width, height + and depth. They solve the problem of cross-referencing from and into the + memoized code by storing its \emph{context} (\S\ref{sec:cross-referencing}) + and replicating any \cs{label}s which occur in it. They are also crucial for + externalizing pictures in Beamer frames overlay by overlay. + + Incidentally, the term ``memoization'' is used with some programming + languages to refer to the process of remembering the result of the function, + along with the given arguments, so that on subsequent invocations of the + function with the same arguments, the result can be returned from memory + rather than recomputing it. I would say that, give or take the functions, + what Memoize does fits the bill. +\end{tcolorbox} + + +\begin{tcolorbox}[ + title={\introboxtitle{How} can I make my command (auto)memoizable?}, + ] + Any command which interacts with the rest of the document, like a command + which produces a float, must receive special treatment. Some issues can be + resolved from within Memoize. Other issues require a memoization-compatible + (re)implementation of the command. It is in the hope that package writers + will adapt their ``difficult'' commands to Memoize that this package offers a + documented interface to the memoization process, fully described in + sections~\ref{sec:tut:package}, \ref{sec:memoization-drivers} + and~\ref{sec:tut:automemoization-details}. + + An advanced user might also want to know that Memoize ships with two + auxiliary packages, which form the base of Memoize's automemoization feature. + Package Advice implements a generic framework for extending the functionality + of selected commands and environments, while package CollArgs provides a + command which can determine the argument scope of any command whose argument + structure conforms to \pkg{xparse}'s argument specification. +\end{tcolorbox} + +\begin{tcolorbox}[ + title={\introboxtitle{What else} is out there?}, + ] + Not long before submitting Memoize to CTAN, I became aware of another new + externalization package, \pkg{robust-externalize}, and it seems that the same + happened to the author of that package \Smiley, who found the + proof-of-concept version of Memoize, which was available + at \href{https://github.com/sasozivanovic/memoize}{GitHub} for a while. + + The key idea behind \pkg{robust-externalize} seems to be to extract the code + submitted to externalization into separate files, and add the necessary + preamble. While a compilation from scratch takes more time than with Memoize + (but less than with \TikZ library), the approach allows for parallel + compilation of externs --- nice! +\end{tcolorbox} + +\newpage +\tableofcontents + + +\section{Before you start} +\label{sec:setup} + +\subsection{Installing the extern extraction software} +\label{sec:install} + +Good news: using Memoize can be as easy as writing +\EmphVerbatim{\usepackage{memoize}} in the preamble. + +Bad news: Memoize won't work out of the box. The culprit is the extern +extraction --- the process which ships the externalized graphics from the main +document into separate extern files; for details, see the title page +illustration and the ``How'' box in the Introduction. For the extraction to +work, you will probably have to install some additional software, and you might +also have to allow your \hologo{TeX} to execute the extraction script. But +there's a silver lining: once Memoize is set up, it is set up for good. + +\begin{tcolorbox}[title=What do I have to do?] + + In principle, all you have to do for Memoize to work \emph{under the default + configuration} is install Perl libraries + \hreftt{https://metacpan.org/pod/PDF::API2}{PDF::API2} and + \hreftt{https://metacpan.org/pod/Path::Class}{Path::Class}; see the Perl + section below for the installation guideline. + +\end{tcolorbox} + +Consult section~\ref{sec:extraction} if you want to use an extraction method +other than the default \refmmz{extract=perl}-based method or adapt Memoize to +your particular workflow (for example, if you're compiling via a Makefile). + +If you installed Memoize through the package manager of your \hologo{TeX} +distribution, your system should be already set up to allow the execution of +Memoize's extraction scripts. If this is not the case, please contact either +me or the maintainer of your distribution; until the issue is resolved, you +have to either +\begin{enumerate}[(a)] +\item compile documents loading Memoize with command-line option + \docref{-shell-escape} (on \hologo{TeX}Live) or + \docref{--enable-write18} (on MiK\hologo{TeX}), or +\item set up the restricted shell escape mode to allow for the execution of + \refscript{memoize-extract.pl}.\footnote{\label{fn:shell-escape-commands}On + \hologo{TeX}Live, execute \code{tlmgr conf texmf shell\_escape\_commands} + to get the list of currently allowed commands \meta{current}, then add the + script by executing \code{tlmgr conf texmf shell\_escape\_commands + \meta{current},memoize-extract.pl}. On MiK\hologo{TeX}, you get the + \meta{current} list by \code{initexmf + --show-config-value=[Core]AllowedShellCommands[]}, and add to it by + \code{initexmf + --set-config-value=[Core]AllowedShellCommands[]=\meta{current};memoize-extract.pl}.} +\end{enumerate} + +Once you have set up your system, I advise you to follow the instructions in +section~\ref{sec:tut:test} to test if the setup was successful. + + +\begingroup +\setlength\intextsep{0pt} + +\paragraph{Perl} If you're running GNU/Linux or macOS, Perl is most likely +already installed on your system. On Windows, you might have to install it. I +tested Memoize with Strawberry Perl, available +at \url(https://){strawberryperl.com}; see \url(https://){www.perl.org} for +other options. + +\begin{wrapfigure}[3]{r}{0.25\linewidth} + \raisebox{0.5mm}[\dimexpr\height-0.5mm][\depth]{% + \mmznext{no verbatim} + \begin{tcolorbox}[ + % example title=Installing the required libraries in Perl, + % attach shifted boxed title to top right, + % top=2.5mm, + fontupper=\small, + left=2mm, + force nobeforeafter, + ] + |cpan PDF::API2| + + |cpan Path::Class| + \end{tcolorbox}% + }% +\end{wrapfigure} +Once Perl is installed on your system, you will need to install two Perl +libraries as well: the PDF processing library +\hreftt{https://metacpan.org/pod/PDF::API2}{PDF::API2}, and the cross-platform +path specification manipulation library +\hreftt{https://metacpan.org/pod/Path::Class}{Path::Class}. On some GNU/Linux +distributions, these libraries are included as packages --- just use your +package manager to install them. Otherwise, install them from +\hreftt{https://www.cpan.org}{CPAN} using +\hreftt{https://metacpan.org/pod/CPAN}{cpan} tool, as shown in the box. + + +\paragraph{Python} Installing (Python and) the required Python library is only +necessary if you decide to use the Python-based extraction script; see +section~\ref{sec:extraction}. + +If you're running GNU/Linux, install Python using your package manager. +Otherwise, download the installer from \url(https://){www.python.org}. You can +install Python even if you don't have administrator privileges: simply uncheck +the ``Install launcher for all users'' when running the installer. + +\begin{wrapfigure}[2]{r}{0.25\linewidth} + \raisebox{0.2mm}[\dimexpr\height-0.2mm][\depth]{% + \mmznext{no verbatim} + \begin{tcolorbox}[ + % example title=Installing the required library in Python, + % attach shifted boxed title to top right, + % top=2.5mm, + fontupper=\small, + left=2mm, + nobeforeafter, + ] + |pip install pdfrw2| + \end{tcolorbox}% + }% +\end{wrapfigure} + +Once Python is installed on your system, you will also need to install the +|pdfrw2| library (or its predecessor, |pdfrw|, which will work just as well). +Some GNU/Linux distributions offer this library as a package; if this is not +the case, and on other operating systems, install it +from \href{https://pypi.org}{The Python Package Index} +using \href{https://pip.pypa.io}{pip} tool (if you +run \href{https://pip.pypa.io}{pip} as a superuser, it will install the library +system-wide, otherwise locally), as shown in the box. + +\endgroup % \intextsep + + +\subsection{The configuration commands} +\label{sec:mmzset} + +Memoize can be configured using command \Emph{\refcmd{mmzset}\marg{keylist}} +(and friends). The \meta{keylist} argument is a comma-separated list of +configuration settings, each setting having the form \meta{key}|=|\meta{value}, +or sometimes just \meta{key}. The \meta{keylist} argument is processed by package +\pkg{pgfkeys} (see \PGFmanual{88}), so you can use all the bells and whistles +of that fantastic PGF utility (like easily defining your own styles). + +Here's some examples of \refcmd{mmzset}. For one, to whet your appetite to learn +about the various keys in the \refkeypath{/mmz} path, but more importantly now, to show +you that white-space is irrelevant in the \meta{keylist} argument, so you can +format the keylist as you wish --- as long as it does not contain an empty +line.\footnote{I like to add a comma after the final key as well, as shown in + the bottom left example, because if I don't, I often forget to insert it when + I add more keys.} + +\begin{tcbraster}[raster columns=4, raster valign=bottom] + \begin{tcboxedraster}[raster columns=1]{blankest, raster multicolumn=3, valign=bottom} + \begin{tcblisting}{listing only} +~\mmzset~{memo dir,recompile,memoize=circuitikz} + \end{tcblisting} + \begin{tcblisting}{listing only} +~\mmzset~{memo dir, recompile, memoize=circuitikz} + \end{tcblisting} + \end{tcboxedraster} + \begin{tcblisting}{listing only} +~\mmzset~{ + memo dir, + recompile, + padding=2in} + \end{tcblisting} +\end{tcbraster}\par +\begin{tcbraster}[raster columns=4, raster valign=top] + \begin{tcblisting}{listing only, raster multicolumn=2} +~\mmzset~{ + memo dir, readonly, + memoize=\qrcode{om}, + deactivate=\label, + disable, +} + \end{tcblisting} + \begin{tcblisting}{listing only} +~\mmzset~{ + memo dir, + recompile, + % comment + padding=2in +} + \end{tcblisting} + \begin{tcblisting}{listing only, bad} +~\mmzset~{ + memo dir, + recompile, + + padding=2in +} + \end{tcblisting} +\end{tcbraster} + +Command \refcmd{mmzset} can be used any time after loading the package. It is +very common in the preamble, but also useful in the document body, where its +effect is local to the \hologo{TeX} group.\footnote{Any keys with a non-local + effect are explicitly marked as such in the reference section.} For example, +you can use the idiom on the left below to force recompilation of a single +Forest tree somewhere in the middle of the document (in the code listings +below, highlighting marks the resulting scope of the \refmmz{recompile} +directive). However, as applying some setting to a single piece of +automatically memoized code is common, Memoize provides a special command for +the occasion: the keys given as the argument of \Emph{\refcmd{mmznext}} will be +applied only at the next instance of automemoization, overriding any keys set +by \refcmd{mmzset} in case of a conflict. If the command is given more than +once, only the final invocation takes effect. + +\begin{tcbraster}[raster columns=2, raster equal height=rows, raster force size=false] +\begin{tcblisting}{listing only, mark region={3}{6}} +~{~ + ~\mmzset~{recompile} + \begin{forest} + [VP[V][DP]] + \end{forest} + % ... +~}~ +\end{tcblisting} +\begin{tcblisting}{listing only, add to width=-1em, mark region={3}{5}} +% ... +~\mmznext~{recompile} +\begin{forest} + [VP[V][DP]] +\end{forest} +% ... + +\end{tcblisting} +\end{tcbraster} + +I like to follow |\usepackage{memoize}| by \refcmd{mmzset}, but if you prefer, you +can also provide the document-wide configuration as \Emph{package options.} +Note, however, that \hologo{LaTeX} removes spaces from package +options, so keys such as \refmmz{memo dir} won't work. That said, the following are +equivalent --- both will force re-externalization of all the externs in the +document. + +\begin{tcbraster}[raster columns=2, raster equal height=rows] + \begin{tcblisting}{listing only} +\usepackage{memoize} +\mmzset{recompile} + \end{tcblisting} + \begin{tcblisting}{listing only} +\usepackage[recompile]{memoize} + \end{tcblisting} +\end{tcbraster} + + +\subsection{The configuration file} +\label{sec:memoize.cfg} + +Memoize allows you to configure settings which apply to more than just one +document. It does that by attempting to load file \Emph{\reffile{memoize.cfg}} +just before executing package options. Given how \hologo{TeX} searches for +files, the location of this file determines whether it applies system-wide, +user-wide or directory-wide. The directory-wide location is clearly the +directory itself. The user-wide and system-wide location depend on the +\hologo{TeX} distribution and which format(s) you want to use +\reffile{memoize.cfg} with:\footnote{The |memoize| subfolder is not + obligatory.} + +\begin{center} + \meta{the relevant texmf tree root}|/tex/|\meta{format}|/memoize/memoize.cfg| +\end{center} + +You can say |generic| for \meta{format} if you want the configuration file to +be accessible by all formats, otherwise \meta{format} should be one of the +formats supported by Memoize: |plain|, |latex| or |context|. The texmf root +directory depends on the distribution, here's how to figure out what it is: + +\begin{tcboxedraster} + [raster columns=1, raster valign=top, raster force size=false] + {title=The roots of TEXMF trees} +\begin{tcolorbox}[title={\hologo{TeX} Live\hfill\url[white](https://){tug.org/texlive}}] + \begin{description}[itemsep=0pt] + \item[user-wide] + |tlmgr conf texmf TEXMFHOME| + or |kpsewhich -var-value TEXMFHOME|\\ + the default (on Linux): |/home/|\meta{username}|/texmf| + \item[system-wide] + |tlmgr conf texmf TEXMFLOCAL| + or |kpsewhich -var-value TEXMFLOCAL|\\ + the default (on Linux): |/usr/local/texlive/texmf-local| + \end{description} + + \smallskip + Don't forget to run |texhash| or |mktexlsr| after creating \reffile{memoize.cfg}. +\end{tcolorbox} +\begin{tcolorbox}[title={MiK\hologo{TeX}\hfill\url[white](https://){miktex.org}}, before upper={\parskip0.3\baselineskip plus 2pt\relax},] + Open the MiK\hologo{TeX} Console, select the ``Settings'' page and then the + ``Directories'' tab. + + If there is a folder marked with the ``Generic'' purpose with attribute + ``User'' (for a user-wide \reffile{memoize.cfg}) or ``Common'' (for a system-wide + \reffile{memoize.cfg}), that's the folder you are looking for. Otherwise, create a + folder following MiK\hologo{TeX}'s instructions and add it to the list. + + Don't forget to click ``Refresh file name database'' (in the ``Tools'' menu + of the MiK\hologo{TeX} Console) after creating \reffile{memoize.cfg}. +\end{tcolorbox} +\end{tcboxedraster} + +Note that a directory config file will override the user config, and the user +config will in turn override the system-wide one. This should not concern you +too much, because you will probably only want to use the user-wide config +anyway,\cprotect\footnote{If you want to have a directory-wide configuration + based on (rather than overriding) the user-wide configuration, your could + write down the real user-wide config in, say, |memoize.user.cfg| (located + user-wide), and then |\input| this file by both the user-wide and the + directory-wide \reffile{memoize.cfg}. Of course, the same logic can be used to base + a user-wide config on the system-wide one.} which might look something like +this: + +\ExampleName{memoize} +\makeexample{\examplename.cfg.c1} +\tcbinputexample[.cfg]{listing only} + +I recommend including \refmmz{memo dir} in your \reffile{memoize.cfg}, +as shown in the first line. It reduces the clutter in the document +directories; see section~\ref{sec:tut:memodir} for details. + +The \refmmz{extract} line concludes the story on ``permanently'' selecting the +extraction method, started in chapter~\ref{sec:setup}. As the final note, +observe that key \refmmz{extract} only makes sense as a package option or as a +\refcmd{mmzset} key in \reffile{memoize.cfg}. It will have no effect as a +\refcmd{mmzset} key in the document, because the extraction happens while the +package is loaded. + + + +\section{Your first memoized documents} +\label{sec:tut} + +\subsection{Let's see if it works!} +\label{sec:tut:test} + +\ExampleName{test} +\noprint{\refcmd{tikz}\refenv{tikzpicture}\refenv{forest}}% + +Take example file \texttt{\examplename.tex},\footnote{Where can you find + the example files? For one, they are integrated into this manual, so if your + PDF viewer supports attachments, you can simply click on the paperclip icon + on the top right of the example box (even if you're offline). Otherwise, + visit the |examples| subdirectory of wherever you found this document + \Smiley. Online, the Memoize documentation can be found at CTAN: + \url{https://ctan.org/pkg/memoize}; and if your \hologo{TeX} installation + includes the documentation, you should also find it in directory \meta{the + root of your \hologo{TeX} installation}|/doc/generic/memoize|. + + Incidentally, while we present a full example document in this section, many + code listings will only present the parts of the file relevant for the + discussion, for brevity. The example \emph{files}, however, will remain + full, compilable documents, including the document preamble etc.} or some +simple document containing a \TikZ picture or a Forest tree, add +\EmphVerbatim{\usepackage{memoize}} to the preamble,\footnote{Memoize likes to + be loaded early. If you get the error \texttt{Extern extraction from + document \meta{jobname}.pdf was unsuccessful}, move + \cs{usepackage}\bracestt{memoize} up the preamble; see + section~\ref{sec:loading-order} for details.} and compile it twice. + +\begin{itemize} +\item The first compilation of the example should produce a three-page PDF. + The first two pages are the \emph{extern pages} holding the externalized + graphics, while the final page is the (sole page of the) real document. Note + that you can see each extern twice: first on a page of its own, and then + wherever it belongs to in the real document. +\item At the second compilation, the extern pages should have disappeared from + the PDF, meaning they were successfully extracted into extern files, which + are now embedded into the main document. +\end{itemize} + +\makeexample{\examplename.pdf} +\tcbinputexample{ + sidebyside, + comment={\centering + \includeexamplepdf[extern page,left=2mm,right=2mm]{page=1,trim=1in 1in 1in 1in}\quad + \includeexamplepdf[extern page,left=2mm,right=2mm]{page=2,trim=1in 1in 1in 1in}\\[2ex] + \includeexamplepdf[document page]{page=3} + }, +} + +You might want to play with this example a bit now. +For example, if you reverse the order of the \TikZ picture and the Forest +tree, you should notice that the externs don't get recompiled. You won't see +any extern pages again until you change the actual code of the picture or the +tree --- or until you add some other picture or tree, of course. + +If you \emph{don't} want to automatically memoize \TikZ pictures and/or Forest +trees, you can switch off their \emph{automemoization} using key +\Emph{\refmmz{deactivate}}. This key takes a list of command and environment +names. As you can see below, the command and the environment must be +deactivated separately. + +\begin{tcblisting}{listing only} +\mmzset{ + deactivate={\tikz, tikzpicture}, % deactivate automemoization of all TikZ pictures + deactivate=\tikz, % deactivate only the command + deactivate=tikzpicture, % deactivate only the environment +} +\end{tcblisting} + + +\subsection{Memoizing by hand} +\label{sec:tut:memoize} + +In the previous section, we have compiled our very first document which used +Memoize. In that document, we only had to load the package, as Memoize knows +how to externalize \TikZ pictures and Forest trees without any help from the +author. But what if you want to externalize some other code? The manual way of +doing this is by surrounding the code by a \Emph{\refenv{memoize}} environment, +or by making it the argument of the \Emph{\refcmd{mmz}} command. The only +difference between the two is that the environment, but not the command, +ignores any spaces surrounding the given code. + +\ExampleName{manual} +\makeexample{\examplename.pdf} +\tcbinputexample{ + sidebyside, lefthand ratio=0.528, + comment={\centering + \includeexamplepdf[extern page]{page=1,trim=1in 1in 1in 1in}\hfill + \includeexamplepdf[extern page]{page=2,trim=1in 1in 1in 1in} + }, +} + +Both the \refenv{memoize} environment and the \refcmd{mmz} command take a +configuration keylist as the optional argument, so their full syntax is +\cs{begin}\marg{\refenv{memoize}}\oarg{keylist}\meta{code to be + externalized}\cs{end}\marg{\refenv{memoize}} and and +\refcmd{mmz}\oarg{keylist}\marg{code to be externalized}. The keys given in +this optional argument take precedence over the keys set by \refcmd{mmzset}. +Note that \refcmd{mmznext} does not apply to manual memoization. + +Manual memoization is great for one-shot memoizations, but you can use it +within your own macros as well. For example, assume that you don't want to +externalize \TikZ pictures in general (so you have \refmmz{deactivate}d +automemoization of the \cs{tikz} command, as explained at the end of +section~\ref{sec:tut:test}), but that you want to easily memoize selected +pictures. You could define a memoized variant of the \cs{tikz} command, as shown +below (and similary for the environment). (Note the |%| comment characters in +the definition of |\mmztikz|. The definition was intentionally broken into +several lines to remind you that the spaces around the argument of \refcmd{mmz} +matter.) + +\ExampleName{mmztikz} +\makeexample{\examplename.pdf} +\tcbinputexample{ + comment={\centering + \includeexamplepdf[extern page]{page=1,trim=1in 1in 1in 1in} + \quad + \includeexamplepdf[document page]{page=2} + }, +} + +\clearpage +\subsection{Memoizing automatically} +\label{sec:tut:automemoization} + +Out of the box, Memoize automatically externalizes \TikZ pictures and Forest +trees. Let us see how other commands and environments can be submitted to this +\emph{automemoization} process. + +We start with the simpler case of environments (fortunately, externalizing +environments also makes sense more often than externalizing commands). You can +submit an environment to automemoization by writing +\EmphTT{\refmmz{auto}=\marg{environment name}\braces{\refmmzauto{memoize}}} (as +a key in \refcmd{mmzset}). The natural (but not the only possible) location +for this instruction is the preamble. Below, we automemoize environment +\env{circuitikz} of package \pkg{circuitikz}, used for drawing electronic +circuits.\footnote{In section~\ref{sec:tut:test}, we learned that + automemoization can be switched off using key \refmmz{deactivate}. Memoize + also offers key \refmmz{activate}, but you probably won't have to use it, as + an \refmmz{auto} declaration automatically activates the submitted command.} + +\ExampleName{automemoize-environment} +\makeexample{\examplename.pdf} +\tcbinputexample{ + sidebyside, lefthand ratio=0.6, + comment={\centering + \includeexamplepdf[extern page]{page=1,trim=1in 1in 1in 1in,scale=0.8} + }, +} + +Commands are a bit harder to automemoize, because Memoize cannot possibly know +how far the arguments of a command extend (in contrast, the end of an +environment is clearly marked). With commands, we must inform Memoize about +their argument structure, which we achieve using key \refmmzauto{args} in the +second argument of key \refmmz{auto}: +\EmphTT{\refmmz{auto}=\meta{command}\braces{\refmmzauto{memoize}, + \refmmzauto{args}=\marg{argument specification}}}. We can only leave out +\refmmzauto{args} if the command was defined by \refcmd{NewDocumentCommand} or +similar; in this case, Memoize can retrieve the argument specification on its +own. + +And how does the \meta{argument specification} look like? It is a sequence of +letters, each letter determining an argument type. Memoize recognizes the same +argument types and their letters as package \pkg{xparse} (which defines +\refcmd{NewDocumentCommand} and friends), so you should look at (section 1 of) +the documentation of that package for details, if and when you need them. +Here, we focus on the two most commonly used types, \docref{xparse:m} and \docref{xparse:o}, and add the +optional star for good measure: +\begin{center} + \begin{tabular}{lll} + \toprule + letter & argument type & example\\ + \midrule + \docref{xparse:m} & mandatory argument \\ + & --- either surrounded with braces & |\foo{arg}|\\ + & --- or a single token & |\foo a|\\ + \docref{xparse:o} & optional argument, surrounded with brackets & |\foo[arg]|\\ + \docref{xparse:s} & optional star & |\foo*|\\ + \bottomrule + \end{tabular} +\end{center} + +Below, we write |args=om| because command \cs{qrcode} (of package \pkg{qrcode}) +takes two arguments: a bracketed optional argument, followed by a mandatory +argument (in braces). + +\ExampleName{automemoize-command} +\makeexample{\examplename.pdf} +\tcbinputexample{ + sidebyside, lefthand ratio=0.603, top=0mm, + comment={\centering + \includeexamplepdf[extern page]{page=1,trim=1in 1in 1in 1in}\quad + \includeexamplepdf[extern page]{page=2,trim=1in 1in 1in 1in} + }, +} + +This should get you started with automemoization. We'll provide some further +basic information in sections~\ref{sec:tut:working-on-a-picture} +and~\ref{sec:tut:verbatim} of the tutorial, but only package writers will +probably ever need the gory details from +section~\ref{sec:tut:automemoization-details}. It is my sincere hope that they +will support Memoize in their packages, where necessary, so that you don't even +have to write the \refmmz{auto} declarations, but there is one thing you should +know if you encounter a package supporting Memoize: you should load it +\emph{after} Memoize! + + + +\subsection{Working on a picture} +\label{sec:tut:working-on-a-picture} + + +Memoize automatically recompiles a picture when the code producing the picture +changes. However, sometimes we can modify a picture without changing its code, +like when we modify the definition of a command used in the code. In the +example below, a predefined style |emph| is applied to the node, producing a +node with a red background. Let's say we compile the document (with +memoization) and then change this style to set the yellow background. +Curiously, the node will remain red. + +\ExampleName{recompile} +\makeexample{\examplename.pdf N=3} +\begin{tcbraster}[raster columns=2, raster valign=top, raster column skip=4mm] + \tcbinputexample{ + after title pre={\ (version~1)}, + comment={\centering + \includeexamplepdf[document page]{page=2} + }, + } + \tcbinputexample[.tex][.c2]{ + no attachment, + after title pre={\ (version~2)}, + comment={\centering + \includeexamplepdf[document page]{page=1} + }, + } +\end{tcbraster} + +The curious thing happens (or rather, doesn't happen) because Memoize doesn't +keep track of how commands and styles are defined; it just uses the extern file +it created when the old style was in effect. To get a yellow node, we must ask +Memoize to reexternalize the picture. The simplest way to do that is by using +the \Emph{\refmmz{recompile}} key; below, we write +\refcmd{mmznext}\bracestt{\refmmz{recompile}} just before the picture and +compile the document again (remember from section~\ref{sec:mmzset} that +whatever keys we provide through \refcmd{mmznext} only apply to the instance of +automemoization). After the compilation, we may (and should) remove the +\refmmz{recompile} directive (otherwise, Memoize will produce the extern page +again and again). + +\tcbinputexample[.tex][.c3]{ + no attachment, + after title pre={\ (version~3)}, + sidebyside, lefthand ratio=0.6, + comment={\centering + \includeexamplepdf[document page]{page=2} + }, +} + +It is also common to put (again, for the space of a single compilation) +\refcmd{mmzset}\braces{\refmmz{recompile}} in the preamble, or to use +\refmmz{recompile} as the package option. Either will remake all the +externalized graphics in the document, so you can be sure all of them use the +latest version of your macros and styles. + +We'll revisit the issue of memoized code depending on macros and styles defined +elsewhere in section~\ref{sec:tut:redefinitions}. In this section, we will +learn how the issue can be \emph{avoided}, at least to some extent. One idea +is to turn off memoization for the picture(s) we are currently working on; +another idea is to let Memoize know which definitions the picture relies on. +The simplest way to achieve the former is by using key \Emph{\refmmz{disable}}; +by putting it into a \hologo{TeX} group, we can localize its effect to the +selected pictures. + +\ExampleName{disable} +\makeexample{\examplename.pdf} +\tcbinputexample{ + sidebyside, + lefthand ratio=0.6, + top=0mm, % manual top, bottoms and \\[...] to fit the page + comment={\centering + \includeexamplepdf[extern page,bottom=0.8mm]{page=1,trim=1in 1in 1in 1in}\\[0.5mm] + \includeexamplepdf[extern page,bottom=0.8mm]{page=2,trim=1in 1in 1in 1in}\\[0.5mm] + \includeexamplepdf[document page,bottom=0.8mm]{page=3} + }, +} + +\begin{tcolorbox}[warning] + As you can imagine, key \refmmz{disable} is complemented by \refmmz{enable}, + but it is perhaps worth mentioning a problem that can arise if you disable + memoization for a part of your document by enclosing it in a pair of + \refcmd{mmzset}\braces{\refmmz{disable}} and + \refcmd{mmzset}\braces{\refmmz{enable}}. Yes, it might work at the moment, + but say you later (e.g.\ when you are preparing the final version of the + document) decide to disable memoization for the entire document, and say you + try to do this by writing \refcmd{mmzset}\braces{\refmmz{disable}} in the + preamble. As shown below on the left, you're in for a surprise: memoization + will still be enabled in the part of the document following + \refcmd{mmzset}\braces{\refmmz{enable}}! The solution is to always disable + memoization for a part of the document by using + \refcmd{mmzset}\braces{\refmmz{disable}} in a \hologo{TeX} group (i.e.\ the + braces), as shown on the right. (In the examples below, the shaded areas + mark the parts of the document where memoization is \emph{disabled}.) +\end{tcolorbox} + +\makeexample{disable-bad.tex.c1} +\makeexample{disable-good.tex.c1} +\makeexample{disable-nomemoize.tex.c1} +\makeexample{disable-nommz.tex.c1} +\makeexample{disable-auto-cmd.tex.c1} +\makeexample{disable-auto-env.tex.c1} + +\begin{tcbraster}[raster columns=2, raster equal height=rows] + \tcbinputexample(disable-bad){ + listing and comment, bad, mark region={4}{7}, + comment={\raggedright The upper \refcmd{mmzset}\braces{\refmmz{disable}} + does not have the intended effect, + i.e.\ it doesn't apply to the whole document!}} + \tcbinputexample(disable-good){ + listing and comment, mark region={4}{10}, + comment={\raggedright The upper \refcmd{mmzset}\braces{\refmmz{disable}} applies + to the entire document, as expected.}} +\end{tcbraster} + +In fact, it might be better to disable memoization using environment +\Emph{\refenv{nomemoize}} or macro \Emph{\refcmd{nommz}}. I also like these +commands because it is easy to add and remove prefix |no| to switch manual +memoization (triggered using environment \refenv{memoize} or macro +\refcmd{mmz}) off and on. + +\begin{tcbraster}[raster columns=2, raster equal height=rows] + \tcbinputexample(disable-nomemoize){listing and comment, mark region={2}{2}, + comment=\raggedright Disable using the dedicated environment.} + \tcbinputexample(disable-nommz){listing and comment, mark region={2}{2}, + comment=\raggedright Disable using the dedicated command.} +\end{tcbraster} + +It is also possible to disable memoization for all occurrences of a selected +command or environment. In fact, we're already familiar with the procedure +from section \ref{sec:tut:automemoization}, where we used key +\refmmzauto{memoize} inside the second argument of \refmmz{auto} to +automatically memoize all instances of the command or environment given as the +first argument. All we have to do to auto-disable rather than auto-memoize, is +substitute \refmmzauto{nomemoize} for \refmmzauto{memoize}. Note that this +prevents memoization of not only the given command or environment, but also of +any (manual or automatic) memoization which would otherwise occur during its +execution; for example, if |\foo| executes \cs{tikz} under the hood, +autodisabling |\foo| prevents memoization of the inner \cs{tikz}, even though +that command is normally automemoized. + +\begin{tcbraster}[raster columns=2, raster equal height=rows] + \tcbinputexample(disable-auto-cmd){ + listing and comment, + mark region={3}{3}, mark region={5}{5}, + comment=\raggedright Autodisable within a command., + } + \tcbinputexample(disable-auto-env){ + listing and comment, + mark region={3}{5}, + comment=\raggedright Autodisable within an environment., + } +\end{tcbraster} + +All that said, Memoize actually offers a neater way to switch off the +externalization for the picture I'm currently working on. The +\Emph{\refmmz{readonly}} key instructs Memoize to use whatever externs it had +already produced (thereby reducing the document compilation time), but to +abstain from producing any new externs. In effect, the stuff we are currently +working on does not undergo memoization and therefore does not produce the +clutter which can potentially lead to trouble described in the \refmmz{recompile} +examples above. + +What I like to do is load the package using |\usepackage[readonly]{memoize}|, +work on stuff, and once I'm happy with the most recent pictures, remove +\refmmz{readonly} from the package options for one compilation. + +\ExampleName{readonly} +\makeexample{\examplename.pdf N=2} + +\begin{tcbraster}[raster columns=5, raster valign=top, raster column skip=4mm] + \tcbinputexample{ + raster multicolumn=2, + after title pre={\ (work in progress)}, + comment={\centering + \includeexamplepdf[document page]{page=1} + }, + } + \tcbinputexample[.tex][.c2]{ + raster multicolumn=3, + no attachment, + title={\tt readonly.tex} (the final version), + comment={\centering + \includeexamplepdf[extern page]{page=1,trim=1in 1in 1in 1in}\quad + \includeexamplepdf[document page]{page=2} + }, + } +\end{tcbraster} + +We're now ready to tackle a completely different way of avoiding the issue, by +informing Memoize which definitions the externalized picture depends on. We do +this by appending these definitions to \emph{context} --- when the context of a +picture changes, Memoize recompiles the picture, same as if the code of the +picture itself was changed. (We will talk about context in more detail in +section~\ref{sec:cross-referencing}.) + +A command can be added as a dependency using key \Emph{\refmmz{meaning to + context}}. Below, we make the following externalized picture depend on the +definition of macro \cs{answer}; changing this definition will result in the +recompilation of the extern. In general, \refmmz{meaning to context} accepts a +comma-separated list of command and environment names, e.g.\ +\code{\refmmz{meaning to context}=\bracestt{\cs{foo},bar}} (note the braces). + +Memoize offers several variants of \refmmz{meaning to context}, applicable to +various types of commands. For example, the easiest way of making the picture +depend on the definition of a \pkg{pgfkeys} style is to use handler +\Emph{\refkey{/handlers/.meaning to context}} --- note the dot in the name, and +observe that \code{emph/.meaning to context} below is executed within +\cs{tikzset}, not \refcmd{mmzset}; see \PGFmanual{87} to learn about key +handlers. + +\ExampleName{meaning-to-context} +\makeexample{\examplename.pdf N=2} +\tcbinputexample{ + after title pre={\ (version~1)}, + sidebyside, + comment={\centering + \includeexamplepdf[extern page]{page=1,trim=1in 1in 1in 1in} + }, +} + +\tcbinputexample[.tex][.c2]{ + after title pre={\ (version~2)}, + sidebyside, + comment={\centering + \includeexamplepdf[extern page]{page=1,trim=1in 1in 1in 1in} + }, + no attachment, +} + +All variants of \refmmz{meaning to context} (see reference +section~\ref{sec:ref:memoization:basic} for the full list) may be used within +the externalized picture itself, e.g.\ \code{\cs{node}[emph, + emph/\refkey{/handlers/.meaning to context}]\bracestt{...}} is perfectly +valid --- and also handy when you want to limit the effect of the handler to a +single picture, as \refkey{/handlers/.meaning to context} cannot be used within +\refcmd{mmznext}. + + + +\subsection{Keeping a clean house} +\label{sec:tut:memodir} + +Memoize produces lots of auxiliary files. For each piece of memoized code, it +produces two \emph{memo} files (we will learn more about these in +section~\ref{sec:memos}), which will be joined by the extern PDF upon the next +compilation.\footnote{If you're using the \hologo{TeX}-based extraction, each + extern (|.pdf|) is also accompanied by a log file (|.log|) produced during + the compilation that extracted the extern.} You can recognize these files +easily: their names start with the name of your document and include one or two +long hexadecimal numbers. + +\ExampleName{dirty-house} +\makeexample{\examplename.pdf} +{ + \relaxmmzcommands + \edef\marshal{% + \noexpand\def\noexpand\mynewextern.//\examplename + }\marshal.#1-#2.pdf\relax{% + \forestset{ + append={[\examplename.#1.memo\vphantom{p},pointcolor]}, + append={[\examplename.#1-#2.memo\vphantom{p},pointcolor]}, + append={[\examplename.#1-#2.pdf,pointcolor]}, + }% + } + \tcbinputexample{ + comment={% + \begin{tcolorbox}[reset, left=2mm, bottom=0mm, + my boxed title=the folder contents (after two compilations), + attach shifted boxed title to top right, + ] + \begin{forest} + before typesetting nodes={for tree={ + font=\scriptsize\tt, grow'=0, folder, s sep=0}}, + [\meta{the document folder}, inner xsep=0, + TeX={% + \def\mmzNewExtern##1##2##3##4##5{\mynewextern##1\relax}% + \input{\examplepath.mmz}% + }, + [\examplename.tex] + [\examplename.pdf] + [\normalsize\dots, no edge] + [\examplename.mmz, tikz={\draw[dotted](.east)--+(5em,0ex) node [anchor=south west, yshift=-1ex, align=justify, text width=14em, font=\footnotesize]{This is another auxiliary file produced by Memoize. We will mention it at the end of the section.};}] + ] + \end{forest} + \end{tcolorbox} + }, + } +} + +To top it off, changing the memoized code will produce new memo (and extern) +files, with the old files staying in place. This is all by design --- the +first hexadecimal number in these filenames is the MD5 sum of the memoized code +and that's how Memoize knows which memo belongs to which piece of +code\footnote{The second hexadecimal number in the memo and extern filenames is + the MD5 sum of the \emph{context}. The context is crucial for properly + externalizing code containing cross-references, see + section~\ref{sec:cross-referencing} for details.} --- but it has the downside +that the folder containing your document can get quite cluttered (imagine the +directory listing as above, but for a document with a hundred externalized +pictures which you have been working on for a month). + +\ExampleName{clean-house} + +I like to keep a clean house by instructing Memoize to put memos and friends +into their own directory. This can be achieved by writing +\refcmd{mmzset}\braces{\Emph{\refmmz{memo dir}}} into the preamble (anytime +after loading the package).\footnote{The \refmmz{memo dir} key is in fact + merely an abbreviation for a sequence of \refmmzpath{dir} and + \refmmzpath{prefix} within \refmmz{path}; use these keys if you need more + control over the name and location of the auxiliary files. Furthermore, there + is also the \refmmz{no memo dir} key, which reverts the configuration back to + the dirty default.} This will put the memo and the extern files into folder +\meta{document name}|.memo.dir| (and it will also omit the \meta{document name} +prefix in their filenames, because it makes no sense to repeat it there). + +\makeexample{\examplename.pdf} +{% + \relaxmmzcommands + \edef\marshal{% + \noexpand\def\noexpand\mynewextern./\examplename + }\marshal.memo.dir/#1-#2.pdf\relax{% + \forestset{ + append={[#1.memo\vphantom{p},pointcolor]}, + append={[#1-#2.memo\vphantom{p},pointcolor]}, + append={[#1-#2.pdf,pointcolor]}, + }% + } + \tcbinputexample{ + comment={% + \begin{tcolorbox}[reset, left=2mm, bottom=0mm, + my boxed title=the folder contents (after two compilations), + attach shifted boxed title to top right, + ] + \begin{forest} + before typesetting nodes={for tree={ + font=\scriptsize\tt, grow'=0, folder, s sep=0}}, + [\meta{the document folder}, inner xsep=0 + [\examplename.tex] + [\examplename.pdf] + [\normalsize\dots, no edge] + [\examplename.mmz] + [\examplename.memo.dir,pointcolor, + % tikz={\draw[dotted](.east)--+(9em,0ex) node [anchor=south west, yshift=-1ex, font=\footnotesize, emphshade=black!5!white, text width=15em, align=justify]{You need to create this directory by hand, or allow Memoize to create it by adding |mkdir| to the list of restricted shell escape commands.};}, + tikz={\draw[dotted](.east)--+(9em,0ex) node [anchor=west, font=\footnotesize, emphshade=black!5!white]{This directory must be created, somehow.};}, + TeX={% + \def\mmzNewExtern##1##2##3##4##5{\mynewextern##1\relax}% + \input{\examplepath.mmz}% + }, + ] + ] + \end{forest} + \end{tcolorbox} + }, + } +} + +\begin{tcolorbox}[title={Why is \refmmz[link color=white]{memo dir} not the default?}] + The \code{clean-house} example most likely compiled just fine, and you are + wondering why \refmmz{memo dir} is not in effect by default. Well, out of + the box, \hologo{TeX} cannot create directories, so it is the fact that + Memoize \emph{can} create them (at least under the default settings) which + requires explanation. By default, Memoize triggers extraction by executing + the Perl extraction script \refscript{memoize-extract.pl}, and it is this + script which actually creates the memo directory. However, not everybody + will necessarily use this script \dots\ so \refmmz{memo dir} should not be + the default. + + The Python extraction script \refscript{memoize-extract.pl}, used when + \refmmz{extract}|=|\refmmz{extract=python}, works the same as the Perl + variant. With \hologo{TeX}-based extraction + \refmmz{extract}|=|\refmmz{extract=tex}, things are different. If you are + compiling the document with a full shell escape mode + (\docref{-shell-escape}), Memoize successfully creates the directory with the + system command |mkdir|.\footnote{|mkdir| is the default value of key + \refmmz{mkdir command}, but executing the extraction method + \refmmz{extract=perl} or \refmmz{extract=python} overrides this default.} + However, if you're using the restricted shell escape mode, the attempt to + create the directory won't succeed unless you include |mkdir| among the + restricted shell escape commands (see footnote~\ref{fn:shell-escape-commands} + on page~\pageref{fn:shell-escape-commands} for how to do this, but note that + it is not recommended). + + If you are using external extraction, you have to create directory + \texttt{\examplename.memo.dir} by hand, prior to the first compilation of the + document (with Memoize). This is the case even if you are performing the + extraction using one of the shipped extraction methods, and it is due to the + fact that Memoize needs the memo directory to be present even before extern + extraction, because it writes the |.memo| files into the same directory. + (When Memoize uses the extraction script to create the memo directory, it + does so completely independently of extraction, and prior to creating any + |.memo| files.) +\end{tcolorbox} + +I actually suggest adding \refcmd{mmzset}\braces{\refmmz{memo dir}} into your +user-wide \reffile{memoize.cfg} (see section~\ref{sec:memoize.cfg} for details +on this file). This will keep all your houses clean --- without work! --- as +Memoize will automatically use the memo directory for any document your create. + +It is always safe to delete memos (|.memo|) and externs (|.pdf|s residing next +to memos), in the sense that you cannot lose data this way.\footnote{The same + goes for the extern |.log| files produced by the \hologo{TeX}-based + extraction.} + +\begin{itemize} + +\item Many memos and externs are typically stale anyway, i.e.\ they reflect + some previous state of your document and are not needed anymore. These files + can be deleted without any repercussions whatsoever (unless you later revert + to a previous version of the document, of course). In fact, you might + \emph{want} to delete them periodically, or at least once you finish writing + the document. As it is hard to figure out which memos/externs are stale, + Memoize ships with a clean-up script: writing \refscript{memoize-clean.pl} + \meta{document name}\dmmz (replace |.pl| with |.py| if you use Python rather + than Perl) into the command line will delete all the \emph{stale} auxiliary + files belonging to the document. + +\item If you delete a memo or an extern currently in use, you will trigger + recompilation of their code --- so deleting a memo or an extern is actually a + perfectly legal alternative to using the \refmmz{recompile} key!\footnote{For the + users of the \hologo{TeX}-based extraction: deleting the |.log| file does + \emph{not} trigger recompilation.} + +\end{itemize} + +It is also safe to delete the \dmmz file (or any other kind of record file, see +section~\ref{sec:record-files}) residing next to your document's |.pdf|. The +\dmmz file contains the information about which externs should be extracted +from the |.pdf|. Deleting it before this is done (by default, before compiling +the document again) will prevent the extraction (same as if providing the +package option \code{\refmmz{extract}=\refmmz{extract=no}}) and ultimately +result in the recompilation of the externs produced in the previous run. +Deleting it after the extraction will have almost no effect: it will only only +prevent the clean-up script from working (the \dmmz file also lists the +currently active memos and externs, and thereby indirectly informs the clean-up +script which files are stale). For further information on the \dmmz file, see +section~\ref{sec:.mmz}. + + + +\subsection{Writing a book?} +\label{sec:tut:multifile} + +Books and other long documents are usually produced from sources which reside +in more than a single file, and to speed up the editing process, authors +usually use some system which allows them to compile each chapter separately. +Can Memoize --- designed for virtually the same task of speeding up the editing +process --- work sensibly in this kind of situation? More precisely, can the +book and the individual chapters share the memos and the externs? Yes they +can! If we instruct Memoize to use the same memo directory for both the book +job and the chapter jobs, then we can externalize graphics when compiling a +chapter and have the externs included when compiling the book (and vice +versa).\footnote{Package \pkg{docmute} makes \hologo{LaTeX} ignore the preamble of + the chapter file when including this file into the main document.} All we +need to do is use our old friend \refmmz{memo dir} from section~\ref{sec:tut:memodir} +--- we see now that this setting is good for more than just keeping a clean +house! + +\ExampleName{book} +\makeexample{\examplename.tex.c1} +\makeexample{chapters/chapter1.tex.c1} + +\begin{tcbraster}[raster columns=2, raster valign=top] + \tcbinputexample{listing only} + \tcbinputexample(chapters/chapter1){listing only} +\end{tcbraster} + +In the above example, the individual chapters reside in files stored in the +|chapters| subdirectory, and that's why the |book.tex| preamble uses +\code{\refmmz{memo dir}=chapters/book} (rather than \code{\refmmz{memo + dir}=book} or just \refmmz{memo dir}). However, Memoize has no trouble +with a situation where the main file and the chapters reside in the same +folder; the setup is even simpler, as we then say \code{\refmmz{memo dir}=book} +in both the book and the chapter preamble. The more complicated situation was +chosen to point out the following potential problem with the setup where the +chapters reside in a subdirectory. + +If you're anything like me, you would first go for having a memo directory +immediately contained in the project directory (so |examples/book.memo.dir| +above) and set up \refmmz{memo dir} as shown below. Well, this won't work, or +at least it won't work with the vanilla \hologo{TeX} Live, because \hologo{TeX} +will refuse to \emph{write} into (memo) files outside the directory where it +was executed,\cprotect\footnote{In \hologo{TeX} Live, the \reffile{texmf.cnf} + option controlling this behaviour is called |openout_any|. By default, it is + set to |p| (paranoid), which ``disallow[s] opening dot files [and] + \emph{going to parent directories}, and restrict[s] absolute paths to be + under \texttt{\$TEXMFOUTPUT}'' (emphasis mine).} and this is precisely what +the chapter compilation is asked to do below. + +\begin{tcboxedraster}[raster columns=2, raster valign=top]{blankest, bad} + \begin{tcblisting}{example title, title=the main file, listing only} +\mmzset{~memo dir=book~} +% ... +\input{chapters/chapter1.tex} + \end{tcblisting} + \begin{tcblisting}{example title, title=a chapter file, listing only} +\mmzset{~memo dir=../book~} + \end{tcblisting} +\end{tcboxedraster} + +\pagebreak % manual + +Section~\ref{sec:tut:working-on-a-picture} presented some ideas on how to work +on a single picture. Those ideas can be all easily applied to the multi-file +situation. For example, you could use \refmmz{readonly} on the chapter that you're +working on (and that chapter only). This way, the preview of the chapter will +not be tarnished by the extern pages, and if you periodically compile it +without \refmmz{readonly}, or compile the book (which does not have the \refmmz{readonly} +set), you will have a reasonably up-to-date set of externs. + +\begin{tcboxedraster}[raster columns=2, raster valign=top]{blankest} + \begin{tcblisting}{example title, title=the main file, listing only} +\mmzset{memo dir=chapters/book} +% ... +\input{chapters/chapter1.tex} + \end{tcblisting} + \begin{tcblisting}{example title, title=the current chapter file, listing only} +\mmzset{memo dir=book, ~readonly~} + \end{tcblisting} +\end{tcboxedraster} + +\ExampleName{memoize.cfg._region_} +\makeexample{\examplename.tex.c1} +\begin{tcolorbox}[title=For \Emacs users] + I often use this \refmmz{readonly} trick myself, but with a twist. As an Emacs user, + I don't use a \hologo{TeX}-based mechanism (such as the \pkg{docmute} package) to + compile a chapter, but rely on the region compilation feature of Emacs' + AUC\hologo{TeX} package. AUC\hologo{TeX} offers a way to compile the current + buffer (if you don't know what an Emacs buffer is, read ``file'') or region + (roughly speaking, the selected text). It does that by putting the buffer or + the region into a file called |_region_.tex| while dressing it up in the + preamble of the original document (when I'm working on a multi-file document, + it correctly pulls the preamble from the main document). This results in a + compilable region file. My trick is to detect whether I'm compiling a region + (this is the job of |\ifregion|), and if so, put Memoize into the \refmmz{readonly} + mode (an alternative trick would be to \refmmz{disable} it). + + This is the trick in a nutshell, but to make it really work we have to + address one further issue: the original document and the region have to share + memos and externs. This happens automatically if the original document sets + \refmmz{memo dir} explicitly (e.g.\ if a document called |doc.tex| contains + \code{\refmmz{memo dir}=doc} in the preamble), but I'm lazy and don't want to + write this in every document --- if I have to do that, what's the point of + \refmmz{memo dir} I put into my \reffile{memoize.cfg} in + section~\ref{sec:tut:memodir}? Fortunately, the region file starts with + |\message{ !name(|\meta{original document name}|.tex)}| to indicate the + origin. The complicated part of the code below (everything following + \refcmd{mmzset}\braces{\refmmz{readonly}} parses this header to extract the + \meta{original document name}, which is then fed to \refmmz{memo dir}. Now, + the trick works automatically for any document.\footnote{The assumption here + is that \refmmz{memo dir} is in effect for the original document. If not, + the trick can be adapted to use (\refmmzpath{dir} and) \refmmzpath{prefix} + within \refmmz{path}.} + + \tcbinputexample{ + title=\tt memoize.cfg, + attachment name=memoize-region.cfg, + listing only, + } +\end{tcolorbox} + + +\subsection{Writing a presentation?} +\label{sec:tut:beamer} + +Memoize ships with built-in support for the most widespread \hologo{LaTeX} +presentation class, Beamer, in the sense that it can externalize a picture +which changes from overlay to overlay. Before we learn how to use that +functionality, however, there's a peculiarity about loading Memoize in Beamer +to address. + +\ExampleName{beamer} +\makeexample{\examplename.pdf} + +\tcbinputexample{ + comment and listing, + listing options app={lastline=2}, + warning, no attachment, title=, + % + comment={Beamer opens the document PDF while loading the class, while + Memoize requires the PDF from the previous compilation intact in order to + extract the externs (when extraction is triggered internally, which is the + default setting). The solution is to load Memoize (a package) before + Beamer (a class), which can be done by using \cs{RequirePackage} instead of + the usual \cs{usepackage}. Easy, if hacky.}, + % +} + +To memoize a piece of code which produces different results on different +overlays --- by virtue of containing |\pause|, |\only|, and\slash or related +commands --- apply key \Emph{\refmmz{per overlay}}. Without this key, +externalization of the picture will end badly, with a single extern (the final +one) appearing on all overlays. The key may be invoked either from a prior +\refcmd{mmznext} command,\footnote{Of course, \refmmz{per overlay} may also be + invoked from \refcmd{mmzset}, but I guess this won't make sense often. For + example, if you set it for the entire presentation, and the presentation + contains static memoized pictures as well, you will compile those pictures + more times than necessary: once for each overlay, whereas once per frame + would suffice. It might occasionally make sense, however, to use \refmmz{per + overlay} as an \refmmz{auto} option --- consult + section~\ref{sec:tut:verbatim} to learn what that is.} or executed in the +memoized code itself. The example below illustrates the latter option, and +also shows that we may invoke it via its full path, +\refkeypath{/mmz}|/|\refmmz{per overlay}, when listed among options processed by +\pkg{pgfkeys}.\footnote{Read section~\ref{sec:per-overlay} to learn how the + Beamer support is implemented. The implementation only uses Memoize's public + interface, so understanding it should help if you need to support some other + presentation package.} + +\tcbinputexample{ + listing options app={firstline=3}, + middle=1mm, + comment={\def\dpwidth{0.27\linewidth}\def\epwidth{0.15\linewidth}% + \raisebox{-\height}{\includeexamplepdf[extern page,left=0mm,right=0mm]{width=\epwidth,page=1,trim=1in 1in 0.4in 0.6in}}\hfill + \raisebox{-\height}{\includeexamplepdf[document page,left=1mm,right=1mm]{page=2,width=\dpwidth}}\hfill + \raisebox{-\height}{\includeexamplepdf[extern page,left=0mm,right=0mm]{width=\epwidth,page=3,trim=1in 1in 0.4in 0.6in}}\hfill + \raisebox{-\height}{\includeexamplepdf[document page,left=1mm,right=1mm]{page=4,width=\dpwidth}}% + }, +} + +\begin{tcolorbox}[warning] + If the memoized code changes the value of Beamer's pause counter + |beamerpauses|, e.g.\ by issuing a |\pause|, take care that (i) \refmmz{per + overlay} is executed prior to any changes of |beamerpauses|, and that (ii) + the final value of this counter in the memoized code is the same for all + overlays. +\end{tcolorbox} + + +\subsection{When stuff sticks out} +\label{sec:tut:padding} + +Some constructs --- like plain \hologo{TeX}'s \refcmd{llap} and \refcmd{rlap}, +and, notably, \TikZ overlays --- fool \hologo{TeX} into thinking that the +``size'' of the typeset material is different than what it actually is. This +can cause trouble for externalization: a piece of your picture might disappear! +In a sentence, the solution is to manually set the \emph{padding} of the +externs, but let's slow down a bit. + +The \TikZ picture in the following example consists of node with a pin on the +right, but let's say we want to horizontally center this picture so that only +the node rather than the entire picture (including the pin) will be centered. +This can be achieved by adding key \refkey{/tikz/overlay} to the pin (actually, +we need to add it to both the pin and its edge). \TikZ normally updates the +extents (called the bounding box) of the picture every time it puts something +in it; when \refkey{/tikz/overlay} is in effect, however, these updates are +temporarily disabled. In effect, the \refkey{/tikz/overlay} key on the pin +below will fool \hologo{TeX} into thinking that the node is all there is to the +picture, so centering will work as desired. + +\ExampleName{overlay} +\makeexample{\examplename.pdf N=4} +\tcbinputexample{ + after title pre={\ (no memoization)}, + comment={\centering + % \includeexamplepdf[document page]{page=1,trim=1.8in 7.4in 1.8in 1.7in} + \includeexamplepdf[document page]{page=1} + }, +} + +What happens when we try to externalize this picture? The example below shows +what would happen if Memoize had no concept of \emph{padding} --- which we +simulate by setting \code{\refmmz{padding}=0pt}.\footnote{Unlike in the rest of + the manual, the extern pages in this section are shown without trimming the + whitespace.} Along with the rest of \hologo{TeX}, Memoize would be fooled +into thinking that the picture comprises of the node only, so the pin would +never make it into the extern. You would end up with a document missing the +pin!\footnote{On the first compilation, the document page containing the figure + without padding looks fine, as it uses the result of the compilation rather + than the extern file. But on the second compilation, when Memoize actually + uses the extern, the pin disappears.} + +\tcbinputexample[.tex][.c2]{ + after title pre={\ (memoization without padding)}, + attachment name=\examplename-no-padding.tex, + sidebyside, lefthand ratio=0.3, + comment={\centering + \includeexamplepdf[size=minimal,boxrule=0.5mm,extern page, + attach boxed title to top right={xshift=1.5mm,yshift=-1mm}, + ]{page=1} + }, +} + + +By default, Memoize puts an inch of space around (what it thinks is) the +externalized picture, and if the overlayed parts of the picture fit into this +inch of space, you will find them in the extern and therefore also in the +document. In our example, however, the default padding is not enough --- the +pin is only partially visible.\footnote{You might wonder why I didn't make the + default padding much bigger, like 10 inches. \hologo{TeX} wouldn't be + bothered (unless the resulting extern size exceeded its maximum dimension), + but you might be, because with such a large default padding, all the externs + would be huge, most often bigger than the document pages, and remember that + the externs are first dumped into the document, where they can bother you.} + +\tcbinputexample[.tex][.c3]{ + after title pre={\ (memoization with default padding)}, + attachment name=\examplename-default-padding.tex, + listing and comment, + middle=0.9mm, + comment={\centering + \includeexamplepdf[extern page,size=minimal,boxrule=0.5mm]{page=1} + }, +} + +The solution is to set the padding manually. Below, I used \refmmz{padding + right} to only increase the padding on the right side (clearly, we also have +\refmmz{padding left}, \refmmz{padding top} and \refmmz{padding bottom}), but +if you're not bothered by a large extern, you can just use +\Emph{\refmmz{padding}}, which sets all four sides at once. By the way, having +too much padding (almost) never hurts, and as you see, you can use (simple) +arithmetics in the value of these keys. + +\tcbinputexample[.tex][.c4]{ + after title pre={\ (memoization with extra padding)}, + attachment name=\examplename-extra-padding.tex, + listing and comment, + middle=0.9mm, + comment={\centering + \includeexamplepdf[extern page,size=minimal,boxrule=0.5mm]{page=1} + }, +} + +\enlargethispage{1ex} % manual +Incidentally, the \refmmz{padding} keys only change how +the externalized picture is \emph{stored}. Memoize remembers the size of the +extern as seen by \hologo{TeX} (e.g.\ the bounding box of the picture as +reported by \TikZ, with overlayed parts of the picture protruding out of it), +and it uses that size when integrating the extern into the document --- so +everything works as it should! + + + +\subsection{The verbatim mode} +\label{sec:tut:verbatim} + +Not all code will peacefully submit to memoization. In particular, this is the +case for environments which process the environment body verbatim (or perform +some other kind of \refcmd{catcode} magic). A simple environment of this kind is the +standard \hologo{LaTeX} \refenv{verbatim}, but let us illustrate the issue with +\refenv{tcblisting}, which typesets a code listing alongside its compiled effect. +(This environment is defined by the |listings| library of package \pkg{tcolorbox} +and was used extensively during the production of this manual.) To manually +memoize a \refenv{tcblisting} environment, we enclose it in a \refenv{memoize} +environment with a \Emph{\refmmz{verbatim}} key in the optional argument --- +without this key, the example below would produce nothing but +errors.\footnote{Memoize also offers a \emph{partial} verbatim mode, triggered + by key \refmmz{verb}; in this mode, the braces retain their usual category + codes. Also note that the effect of \refmmz{verbatim} can be ``undone'' by + key \refmmz{no verbatim}.} + +\ExampleName{verbatim-manual} +\makeexample{\examplename.pdf} +\tcbinputexample{ + sidebyside, + comment={\centering + \includeexamplepdf[extern page]{page=1,trim=1in 1in 1in 1in}\\[1ex] + \footnotesize + (The document page is the same as for the \texttt{verbatim-auto} example below.) + }, +} + +Using \refmmz{verbatim} from \refcmd{mmzset} or \refcmd{mmznext} works just as +well, and the latter can be very useful with automemoization, when some +environment (say, \env{tcolorbox}) generally does not require the verbatim mode, +but a specific occurrence does (say, because it contains some verbatim +construction such as \verb!|!\meta{verbatim text}\verb!|! of the \pkg{ltxdoc} +class). + +However, for an environment such as |tcblisting|, it makes the most sense to +declare it verbatim in general, so that all instances of the environment will +be processed in the verbatim mode. This is simple to do: add \refmmz{verbatim} +to the \refmmz{auto} keylist. + +\ExampleName{verbatim-auto} +\makeexample{\examplename.pdf} +\tcbinputexample{ + sidebyside, % lefthand ratio=0.62, + comment={\centering + \footnotesize + (The extern page is the same as for the \texttt{verbatim-manual} example above.)\\[1ex] + \includeexamplepdf[document page]{page=2}\\ + }, +} + +In fact, you can add any \refkeypath{/mmz} key to the \refmmz{auto} keylist, +and the key will be applied to all occurrences of the command or the +environment. For example, adding \refmmz{recompile} to the declaration of +|tcblisting| above would recompile all and only the |tcblisting| environments; +and as an \refmmz{auto} declaration only updates (rather than completely +replaces) a previous declaration, you can also say things like +\code{\refmmz{auto}=\refcmd{tikz}\braces{\refmmz{recompile}}} to recompile all +\TikZ pictures produced by the \cs{tikz} command (handy, as you don't know how +automemoization for \cs{tikz} was declared unless you've read +section~\ref{sec:tut:automemoization-details} or looked at the Memoize's source +code). + + +\subsection{The final version of your document} +\label{sec:tut:final} + +Bluntly put, you might want to disable Memoize when compiling the final version +of your document, at least if you intend to distribute it in electronic form, +for two reasons: +\begin{itemize} +\item An externalized picture cannot contain hyperlinks. Any hyperlinks (or + hyperlink anchors) contained in the original picture will silently disappear + during the production of the extern. +\item When the document contains many externs, the size of the resulting PDF + can be several times the size of the PDF compiled without externalization. +\end{itemize} + +Below, we list several ways of fully disabling Memoize. You're of course +already familar with the first two ways, but what's this \refpkg{nomemoize} +package? The rationale behind this package is that if you want to be +absolutely sure that there is no trace of memoization in your document (for +example, see the \refmmz{disable} -- \refmmz{enable} pitfall in +section~\ref{sec:tut:working-on-a-picture}), the best thing to do is to not +load the package at all. However, you have all those \refcmd{mmzset}s etc.\ in +your source, so the document won't compile without +\cs{usepackage}\braces{\refpkg{memoize}}, right? Right, but wrong. Enter +\Emph{\refpkg{nomemoize}}, a dummy package which accepts all the commands that +Memoize does, but does nothing. In effect, your document will compile, but you +can be sure that not a single memo or extern was loaded or produced. + +\begin{tcbraster}[raster columns=3, raster force size=false, raster valign=top] + \begin{tcblisting}{listing only, add to width=2.4em, enhanced} +\usepackage[~disable~]{memoize} + \end{tcblisting} + \begin{tcblisting}{listing only, add to width=-1.6em, enhanced} +\usepackage{memoize} +\mmzset{~disable~} + \end{tcblisting} + \begin{tcblisting}{listing only, add to width=-0.8em, enhanced} +\usepackage{~no~memoize} + \end{tcblisting} +\end{tcbraster} + +There is one issue you might need to resolve manually before package +\refpkg{nomemoize} works as intended, though. If you have used any +\refkeypath{/mmz} keys outside \refcmd{mmzset}, you need to list them in +\refcmd{nommzkeys}. For example, if you used \refmmz{per overlay} in the manner +illustrated in section~\ref{sec:tut:beamer}, i.e.\ as +\refkeypath{/mmz}|/|\refmmz{per overlay} among the \TikZ keys, you need to +write \refcmd{nommzkeys}\braces{\refmmz{per overlay}} into the document +preamble. + +Another thing you might want to do once you have produced the final version of +the document (in fact, just before you disable Memoize for good) is clean up. +As we saw in sections~\ref{sec:tut:memodir} and~\ref{sec:tut:redefinitions}, +Memoize produces a lot of auxiliary files (memos and externs) and it keeps the +old versions around! Once your document is prepared, you can reduce the +clutter (and save some disk space) by deleting memos and externs belonging to +the work-in-progress versions of your document, and keep only those used in the +final version. + +You could achieve this by deleting all the memos and externs (if you're using +the \refmmz{memo dir} directive, this amounts to the entire contents of the +memo directory) and compiling your document for the final couple of times. +However, there is an easier (but \hologo{TeX}-external) way: on the command +line, change into the directory containing your (main) document and write +\EmphTT{\refscript{memoize-clean.pl}} \meta{document name}\dmmz (substitute +|.py| for |.pl| to use Python rather than Perl). The script will inspect the +contents of the \dmmz record file to see which memos and externs were used in +the final compilation, and delete all other memos and externs belonging to the +given document. + +\begin{tcolorbox}[warning] + Deleting memos and externs is never an irreversible operation, as you can + always recreate them, but it is still wise to be cautious when cleaning up. + For one, avoid cleaning up after a compilation which produced errors; a + failed compilation can lead to an incomplete \dmmz file, which can in turn + lead to over-deletion. Another bad idea is cleaning up after disabling + Memoize for a part of a document, for the same reason. + + All that said, Memoize takes some precautions itself. It will cowardly + refuse to perform the clean-up when the \dmmz file is missing the + end-of-file marker (|\endinput|), assuming that this indicates a fatal error + in the previous compilation. It will do the same in case the \dmmz file is + absent or empty. The latter is assumed to be a result of a globally + \refmmz{disable}d memoization, but note that clean-up will be performed if + memoization was disabled using package \refpkg{nomemoize}: that package does not + touch the \dmmz file, so cleaning up should work as intended. +\end{tcolorbox} + +As the final note, memos and externs (cleaned-up or not) may be copied (along +the document source) to another directory or machine, where they should be +picked up by Memoize. There is no need to copy the \dmmz file (assuming that +the document PDF contains no extern pages waiting for extraction). + + +\section{Digging deeper} +\label{sec:potential-pitfalls} + + + +\subsection{Good to know} +\label{sec:tut:good-to-know} + + +\paragraph{Line- and page-breaking} An extern can't be broken across lines or +pages. + +Externalization of a chunk of code produces a PDF, which is included into the +document at subsequent compilations as a picture --- an unbreakable object (a +horizontal box) with fixed width and height. Therefore, the original code +should produce an unbreakable object as well. For example, this means that you +cannot externalize some text and expect \hologo{TeX} to break it across lines +or pages on subsequent compilations. If you try, the compilation will succeed +--- without an error! --- but your externalized text will end up in a single +line, as shown below. + +\ExampleName{no-linebreaking} +\makeexample{\examplename.pdf N=2} +\tcbinputexample{ + bad, + comment={\centering + \includeexamplepdf[extern page,to be continued on right] + {page=1,trim=1in 1in 14in 1in}\\[1ex] + \includeexamplepdf[document page]{page=2}\\[1ex] + \includeexamplepdf[document page,title=the expected document page] + [\examplepath.c2.pdf]{page=1} + }, +} + +That said, you \emph{can} externalize a paragraph or some other vertical mode +material using \refmmz{capture}|=|\refmmz{capture=vbox}, but beware that the +vertical spacing between the memoized material and its surroundings might +change. + + +\paragraph{\env{remember picture}} +\TikZ pictures using this key cannot be externalized. + +Memoize will silently refuse to externalize any \TikZ picture using +\refkey{/tikz/remember picture} (see \PGFmanual{17.13}). Such pictures +interact with the outside world --- they either reference or are referenced by +other pictures --- and are as such unsuitable for externalization. For +example, while the colored boxes in this manual are generally externalized --- +out of principle \Smiley\ --- the title page illustration is not, and it cannot +be, because of the arrows connecting the various \TikZ pictures composing that +illustration. Some packages use the \refkey{/tikz/remember picture} mechanism +under the hood, and are thus subject to this limitation; one example is package +\pkg{todonotes}, but in general, any package dealing with absolute positions on the +page will be limited in this way. + +How does Memoize deal with this situation? Well, by cowardly refusing to +externalize any code which uses \refkey{/tikz/remember picture} or a similar +mechanism for dealing with absolute positions. Luckily, any such mechanism +eventually boils down to the \hologo{TeX} primitive \refcmd{savepos}, so +Memoize hacks --- or as we will say in this manual, \emph{advises} --- this +primitive to abort any ongoing memoization. Initializing and then aborting the +memoization takes some time, to be sure, but the overhead is negligible, +especially in the light of the fact that \emph{not} aborting wreaks real havoc. + +Memoize actually provides a user interface for aborting memoization. +Memoization can be aborted either manually, by executing \refcmd{mmzAbort}, or +automatically. The latter is a generalization of the automemoization idea: a +command such as \refcmd{savepos} can be advised to abort memoization by +\refmmz{auto}|=|\refcmd{savepos}\bracestt{\refmmzauto{abort}}. In Memoize, +commands take your advice seriously, so memoization will be aborted whenever +the advised command or environment is encountered. + + +\paragraph{Indirectly embedded environments} Such environments cannot be +memoized. + +Read this if you got an error message such as |Environment "tikzpicture" ended +as "foo"|. + +Some environments are defined so that they embed another environment using the +idiom shown on the left: the begin-code of the outer environment opens the +inner environment, and the end-code of the outer environment closes the inner +environment. While this is a fine, and common, idiom, it messes up the +memoization of the inner environment. In the example on the right, trying to +\emph{auto}memoize a \env{minipage} environment (not recommended at all!) causes +trouble with package \pkg{sectionbox}.\footnote{This is a \texttt{Package + collargs Error} because Memoize outsources the actual work of collecting + the environment body to the auxiliary package CollArgs, described in + section~\ref{sec:collargs}.}\fncomma\cprotect\footnote{Why does this happen? + As mentioned in section~\ref{sec:tut:memodir}, Memoize keeps track of memos + and externs by the MD5 sum of the memoized code. But to compute that sum for + an environment, Memoize has to \emph{grab} the environment body, meaning it + has to collect the body in advance. This presents no problem when + \refcmd{end}\meta{environment name} is already present in the input stream at the +time \refcmd{begin}\meta{environment name} is executed, like when you use the + environment normally in your document, or when some macro expands so that it + produces both \cs{begin}\meta{environment name} and \cs{end}\meta{environment + name} simultaneously --- so there would be no problem above if + |\end{minipage}| occured in the beginning code of \env{sectionbox}. The idiom +presented above is problematic for memoization because at the time \hologo{TeX} +executes |\begin{sectionbox}|, putting |\begin{minipage}| into the input + stream, |\end{sectionbox}| is not yet executed and remains as it is. The + input stream therefore contains a pair of |\begin{minipage}| and + |\end{sectionbox}|. In the normal, non-memoizing course of events this + would not be a problem, because |\end{sectionbox}| would eventually expand to +|\end{minipage}|. During memoization, however, this \emph{is} a problem, +because, as we said, Memoize needs to grab the environment body: upon +encountering |\begin{minipage}|, it looks in the input stream for + |\end{minipage}| --- but there is no |\end{minipage}| in the input stream, +there is only |\end{sectionbox}|, and this results in the |Environment +"minipage" ended as "sectionbox"| error.} + +\begingroup +\ExampleName{sectionbox} +\makeexample{\examplename.tex.c1} +\mmzset{disable} +\begin{tcbraster}[raster equal height] +\begin{tcblisting}{listing only, bad, valign=center} +\newenvironment{foo}% ... args +{% the begin-code of foo + % ... + \begin{tikzpicture} + % ... +} +{% the end-code of foo + % ... + \end{tikzpicture} + % ... +} +\end{tcblisting} +\tcbinputexample{ + bad, + comment={\small\tt + ! Package collargs Error: Environment\\"minipage" ended as "sectionbox". + }, + } +\end{tcbraster} +\endgroup + +What are your options in this kind of a situation? +\begin{enumerate} +\item The only way to perform any memoization here is to memoize the + \emph{outer} environment --- if that makes sense.\footnote{This avoids the + error because Memoize grabs and memoizes the outer environment, and while + it is memoizing it, further memoization is switched off.} You can do this + either on a case-by-case basis, by enclosing it in the \refenv{memoize} + environment, or automemoize it: \code{\refmmz{auto}=\marg{outer + environment}\braces{\refmmzauto{memoize}}}. +\item But what if memoizing the outer environment is out of the question? + Then, the only way to avoid the error is to prevent the automemoization of + the inner environment. + \begin{enumerate} + \item If you are facing a single occurrence of the problem, it is perhaps + easiest to use key \refmmz{disable} just before the start of the outer + environment. + \item Otherwise, you can automatically disable memoization for the span of + each occurrence of the outer environment: \code{\refmmz{auto}=\marg{outer + environment}% + \braces{\refmmzauto{nomemoize}}}. + \item To deactivate the automemoization of the inner environment for the span + of the outer environment, but otherwise allow for memoization inside the + outer environment: \code{\refmmz{auto}=\marg{outer environment}% + \braces{\refmmzauto{noop}, \refmmz{deactivate}=\meta{inner + environment}}}. Key \refmmzauto{noop} does nothing but apply the + given options (in this case, \refmmz{deactivate}=\meta{inner environment}) + to the advised command or environment. + \end{enumerate} +\end{enumerate} + +\subsection{Extraction methods and modes} +\label{sec:extraction} + +Remember that in Memoize, externalization is a two-step process. First, +externs are typeset on separate pages of the main document, called extern +pages. Then, these extern pages are extracted out of the main document PDF +into extern files. The process is illustrated on the title page. + +Memoize is flexible in terms of which piece of software is used to perform +extern extraction. It ships with three \emph{extraction methods}: + +\begin{description} +\item[\refmmz{extract=perl}] A Perl script, \refscript{memoize-extract.pl}. + This method is the default because it is fast and because Perl is usually + already installed on a system running \hologo{TeX}. However, you will most + likely still need to install two pieces of software that the script depends + on: Perl libraries \hreftt{https://metacpan.org/pod/PDF::API2}{PDF::API2} and + the \hreftt{https://metacpan.org/pod/Path::Class}{Path::Class}, the + installation guidelines can be found in section~\ref{sec:install}. +\item[\refmmz{extract=python}] A Python script, \refscript{memoize-extract.py}. + This method is even faster than the Perl script, though not by much. Try it + out if you have problems installing Perl or the required libraries, or if the + Perl script chokes on your document (see section~\ref{sec:troubleshooting} + for the list of known issues). Besides Python ($\geq 3.8$), you will also + need the Python library \hreftt{https://pypi.org/project/pdfrw/}{pdfrw} or + \hreftt{https://pypi.org/project/pdfrw2/}{pdfrw2}. For the installation + guidelines, see section~\ref{sec:install}. +\item[\refmmz{extract=tex}] \hologo{TeX}-based extraction requires no + additional software, but it is much slower than the scripts. As \hologo{TeX} + can only produce a single PDF per compilation, an instance of \hologo{TeX} + (loading the entire document PDF) has to be invoked for each extern, and this + takes time (although the entire process is still much faster than the + venerable \TikZ externalization library). +\end{description} + +Memoize is also flexible in terms of how extern extraction is triggered, +providing two \emph{extraction modes}: + +\begin{description} +\item[internal]\label{item:setup:who} By default, extern extraction is + triggered internally, i.e.\ by Memoize during the compilation of the + document; more precisely, any externs produced in a compilation are extracted + during the next compilation. To choose an extraction method other than the + default Perl script, load Memoize with the package option + \EmphTT{\refmmz{extract}=\meta{extraction method}}. + +\item[external] Loading Memoize with with package option + \EmphTT{\refmmz{extract}=\refmmz{extract=no}} instructs Memoize to \emph{not} + trigger the (internal) extraction. When instructed to use extraction + ``method'' \refmmz{extract=no}, Memoize expects you to trigger the extraction + yourself, in any way that is convenient to you: manually from the command + line, or automatically through your editor, a Makefile, etc. --- all Memoize + cares about is that the extraction takes place before the next compilation of + the document. +\end{description} + +Summing up, the extraction mode and method are selected by providing the +appropriate value to package option key \refmmz{extract}; the possible values +are listed in the table below. Note that this key can only be used as a +package option, or in \refcmd{mmzset} within \reffile{memoize.cfg}. In +particular, it is disabled in the document preamble, because Memoize performs +extraction while it is loaded. + +\medskip +\begin{center} + \begin{tabular}{lll} + \toprule + extraction method&external program&Memoize invocation\\ + \midrule + \refmmz{extract=perl}&\refscript{memoize-extract.pl}&\refcmd{usepackage}\braces{\refpkg{memoize}}\footnotemark\\ + \refmmz{extract=python}&\refscript{memoize-extract.py}&\refcmd{usepackage}\bracketstt{\refmmz{extract}=\refmmz{extract=python}}\braces{\refpkg{memoize}}\\ + \refmmz{extract=tex}&|pdftex|&\refcmd{usepackage}\bracketstt{\refmmz{extract}=\refmmz{extract=tex}}\braces{\refpkg{memoize}}\\ + \refmmz{extract=no}&none (external extraction)&\refcmd{usepackage}\bracketstt{\refmmz{extract}=\refmmz{extract=no}}\braces{\refpkg{memoize}}\\ + \bottomrule + \end{tabular}% + \footnotetext{Or + \refcmd{usepackage}\bracketstt{\refmmz{extract}=\refmmz{extract=perl}}% + \braces{\refpkg{memoize}}. This is useful if you have changed the default + using \reffile{memoize.cfg}.}% +\end{center} +\smallskip + +For internal extraction, \hologo{TeX} must be allowed to execute the external +program implementing the chosen extraction method. Both |memoize-extract| +scripts should be listed among restricted shell escape mode commands in your +\hologo{TeX} distribution; their execution should therefore be allowed under +the default, restricted shell escape mode. However, the |pdftex| program, +executed by extraction method \refmmz{extract=tex}, is not listed there, nor +should it be. If you are forced to use this fallback method, I suggest you +compile documents loading Memoize under the full shell escape mode, by adding +command-line option \docref{-shell-escape} (on \hologo{TeX}Live) or +\docref{--enable-write18} (on MiK\hologo{TeX}) to the invocation of the +\hologo{TeX} program. The answers linked from question +``\href{https://tex.stackexchange.com/q/598818/16819}{How can I enable + shell-escape?}'' on \href{https://tex.stackexchange.com}{\hologo{TeX} + StackExchange} will tell you how you can ask your editor to do this for you. + +You may use any extraction method to perform external extraction. The simplest +option is to use the Perl or the Python script. Supposing you are doing this +manually from the command line, change into the directory containing your +document, which should contain the auxiliary \dmmz file produced by Memoize, +and execute: + +\begin{enumerate}[(a)] +\item \refscript{memoize-extract.pl} \meta{document name}\dmmz \hfill (for + the Perl script) +\item \refscript{memoize-extract.py} \meta{document name}\dmmz \hfill (for + the Python script) +\end{enumerate} + +See sections~\ref{sec:.mmz} and \ref{sec:ref:extraction-perl-python} for +further details on the \dmmz file and the extraction scripts. + +Things are a bit more complicated if you want to use the \hologo{TeX}-based +extraction method externally, because an instance of |pdftex| needs to be +invoked for each extern (and these have unwieldy names and can be many in +number), but Memoize can help you here by producing a shell script or a +makefile, executing which will extract all the externs at once. To have Memoize +produce a shell script, use package option +\code{\refmmz{record}=\refmmz{record=sh}} (or +\code{\refmmz{record}=\refmmz{record=bat}} on Windows); package option +\code{\refmmz{record}=\refmmz{record=makefile}} will make a makefile. By +default, these files are named |memoize-extract.|\meta{document name} plus the +|.sh|, |.bat| or |.makefile| suffix. If neither a shell script nor a makefile +works for you, you can also define your own kind of \emph{record file}, to be +processed by the external tool of your choice (and implementation) in order to +extract the externs; see section~\ref{sec:new-record-file} to learn how to do +this. + + +\subsection{From cross-references to the context} +\label{sec:cross-referencing} + +Cross-referencing presents a challenge to externalization, because without +special provisions, the ``communication channel'' between the \cs{label} and the +\cs{ref} is broken once we start utilizing the extern. + +One direction of the issue occurs when a \refcmd{label} within the memoized +code is referenced by a \cs{ref} on the outside. Without the (built-in) +workaround, the \cs{label} command would only be executed when the extern is +being produced, but not on subsequent compilations of the document, when it is +merely included. Memoize addresses this problem by generalizing +externalization (which can only produce a picture, the extern) to memoization +(which can additionally produce arbitrary code). When Memoize is externalizing +code which contains a \cs{label}, it automatically replicates it into the +\emph{memo}, which is input into the document on subsequent compilations. In +effect, the memo--extern team will continue to produce the label even when it +is utilized rather than compiled. As far as the author is concerned, \cs{label}s +in memoized code ``just work,'' without any observable differences to the +situation without memoization. This is why we will not discuss this direction +of the issue here; a reader interested in how precisely the system works is +invited to read section~\ref{sec:memos}. + +The other direction of the issue occurs when a \refcmd{ref} within the memoized +code references \cs{label} on the outside. In this situation, the extern +should be recompiled when the value of the label it refers to changes. Again, +Memoize addresses this problem in full generality, by associating with each +extern a \emph{context}, and recompiling the extern whenever the value of the +context changes.\footnote{The dependency of an extern upon prior definitions + and such can also be addressed in a more \emph{ad hoc} manner, by recompiling + manually; we have already touched upon this subject in + section~\ref{sec:tut:working-on-a-picture}, and will revisit it in + section~\ref{sec:tut:redefinitions}.} All that needs to be done for \cs{ref} +and friends, specifically, is to advise them to add their reference keys to the +context. + +As we shall see presently, for the author, the only difference between a +non-memoized and a memoized \cs{ref} is that the latter will take one more +compilation cycle to ``stabilize'' the resulting document. (More precisely, +the memoized situation will take one more cycle if the reference is undefined +on the first compilation.) Then, we will show how we can teach Memoize about +cross-referencing commands other than \cs{ref} and \cs{pageref}. Finally, we will +learn about key \refmmz{context}, the backbone of the cross-referencing support +in Memoize. (The inner workings of the context are further explained in +section~\ref{sec:c-memos}.) + +\ExampleName{ref} +\makeexample{\examplename.pdf N=7} + +When the memoized code contains a \cs{ref} referring to a label given in another +part of the document, the code is recompiled when (and only when) the reference +changes. Let us look at the following example, jumping in at the point where +it was already compiled enough times that the resulting PDF had stabilized into +a single (document) page with correct references. (Environment +\refenv{nomemoize} disables memoization +of \href{https://ctan.org/pkg/tikzlings}{Ti\emph{k}Zlings}, so that their +externs don't disturb us, and we can focus on the \cs{tikz} command, which does +get externalized and contains a \cs{ref}.) + +\tcbinputexample[.tex][.c3]{ + after title pre={\ (with stable output after three compilations)}, + middle=1mm, + comment={\centering + \includeexamplepdf[document page]{page=1} + } +} + +Let us add an owl in front of the penguin. In the next compilation, neither +the ``normal'' nor the memoized reference is yet updated, as expected --- in +this compilation, the new value of the penguin label only makes it into the +|.aux| file. + +\tcbinputexample[.tex][.c4]{ + after title pre={\ (after the first compilation with the added owl)}, + no attachment, + comment={\centering + \includeexamplepdf[document page]{page=1} + } +} + +During the following compilation, the \refcmd{ref}s pick up the new value of the +penguin label, and the \cs{ref} inside the automemoized \cs{tikz} command +forces recompilation of the extern (\emph{how} this is done will be explained +later). + +\tcbinputexample[.tex][.c5]{ + after title pre={\ (after the second compilation with the added owl)}, + comment only, no attachment, + comment={\centering + \includeexamplepdf[extern page]{page=1,trim=1in 1in 1in 1in}\quad + \includeexamplepdf[document page]{page=2} + } +} + +In the next compilation, the resulting PDF is finally stabilized, as the +updated extern is (extracted and) included into the document. + +\tcbinputexample[.tex][.c6]{ + after title pre={\ (after the third compilation with the added owl)}, + comment only, no attachment, + comment={\centering + \includeexamplepdf[document page]{page=1} + } +} + +The message to take home? When some memoized code contains a reference and +that reference changes, it will take three compilation cycles (so, one more +cycle than without memoization) for the resulting document to ``stabilize.'' + +Out of the box, Memoize supports the standard \hologo{LaTeX} cross-referencing +commands \cs{ref} and \refcmd{pageref}. To automatically recompile code +containing some other cross-referencing command, like \refcmd{vref} of package +\pkg{varioref}, we use the advising framework implemented by package Advice. +This framework is a generalization of automemoization: we use the familiar +\refmmz{auto}, but with advice offered by \Emph{\refmmzauto{ref}} rather than +\refmmzauto{memoize}. + +{\ExampleName{vref} +\makeexample{\examplename.tex.c1} +\tcbinputexample{listing only}} + +Key \refmmzauto{ref} only works for commands which operate on a single +reference key. However, that single key (which must be enclosed in braces) may +be preceded by optional argumen(s) of any kind. Extensions to \refcmd{ref}, +e.g.\ the \pkg{hyperref}'s variant, which accepts an optional |*|, work out of +the box. Furthermore, Memoize offers support for cross-referencing commands +which work on multireferences and reference ranges, such as \pkg{cleveref}'s +\refcmd{cref} and \refcmd{crefrange}. Those commands should be advised by +\refmmz{auto} keys \refmmzauto{multiref} and \refmmzauto{refrange}, +respectively. + +We have jumped into first example of this section with the assumption that it +had already been compiled several times, allowing the resulting PDF to +stabilize. Let us now take a look at what happens at the very first, fresh +compilation of our original example (the one without the owl). (Removing the +|.aux| file before compiling the example again will start afresh.) The curious +thing is that we don't get the extern page containing +\tikz[baseline]\node[draw=red,thick,fill=yellow,anchor=base,font=\bf]{??};. +This is so because by default, Memoize aborts a memoization containing an +undefined reference. + +\tcbinputexample[.tex][.c1]{ + after title pre={\ (after the fresh compilation of the original example)}, + comment only, no attachment, + comment={\centering + \includeexamplepdf[document page]{page=2} + } +} + +Now sometimes you might want to produce an extern even if it contains an +undefined reference --- for example, you might intend to write the code +containing the \cs{label} much later but enjoy the speed-up offered by Memoize +until then. In that case, apply the \refmmz{auto} key \Emph{\refmmzauto{force + ref}} to \cs{ref}. + +\tcbinputexample[.tex][.c7]{ + after title pre={\ (after the fresh compilation with \refmmzauto{force ref})}, + attachment name=\examplename-force.tex, + comment={\centering + \includeexamplepdf[extern page][\examplepath.c1.pdf]{page=1,trim=1in 1in 1in 1in}\quad + \includeexamplepdf[document page][\examplepath.c1.pdf]{page=2} + } +} + +However, note that when you use \refmmzauto{force ref}, \hologo{LaTeX} will +\emph{not} complain about the undefined reference once the extern containing it +is included (unless that reference also occurs in some non-memoized piece of +code). Using \refmmzauto{force ref} is therefore a tiny bit dangerous, and +this is why \refmmzauto{ref}, with the abortion mechanism, is the default +handler for \cs{ref} and \refcmd{pageref}. + +As already noted in the previous section, \refcmd{ref} works by appending the +cross-reference to the \emph{context}, the change of which triggers +recompilation. Memoize initializes the context to contain the four +\refmmz{padding}s --- as a result, an extern recompiles if we change the +padding --- but we can append stuff to the context by ourselves, as well. +Below, we use key \Emph{\refmmz{context}} to append the font size (we'll talk +about the value given to this key a bit later); as a result, the picture is +recompiled whenever the font size changes. Below, we change the font size +using command |\small|; changing the default size with a class option such as +|12pt| works as well. + +\ExampleName{fontsize} +\makeexample{\examplename.pdf N=2} +\tcbinputexample{ + sidebyside, lefthand ratio=0.7, after title pre={\ (the first version)}, + comment={ + \includeexamplepdf[extern page]{page=1,trim=1in 1in 1in 1in} + } +} + +\tcbinputexample[.tex][.c2]{ + sidebyside, lefthand ratio=0.7, after title pre={\ (the second version)}, + no attachment, + comment={ + \includeexamplepdf[extern page]{page=1,trim=1in 1in 1in 1in} + } +} + +How does this work? Key \refmmz{context} appends the given tokens to the +\emph{context expression}. When creating an extern or trying to use it, +Memoize (fully) expands this expression and computes the MD5 sum of the +expansion. This \emph{context MD5 sum} then serves as a part of the extern's +filename (see sections~\ref{sec:tut:memodir} and~\ref{sec:memos}). In effect, +Memoize will only find (and utilize) the extern if \emph{the context MD5 sum + computed during (attempted) utilization matches the one computed during + memoization}. + +As revealed by looking at the \hologo{LaTeX} source code, \hologo{LaTeX} holds +the current font size in macro |\f@size|, and above, we have effectively added +the contents of this macro to the context. Now, why didn't we simply write +\refmmz{context}|=\f@size|? First, we used |\csname ... \endcsname| because we +were under the normal \hologo{LaTeX} catcode regime, where |@| cannot be a part +of the command name. Of course, we could have temporarily changed the catcode +of |@| using |\makeatletter| and |\makeatother|, but I would advise against +that, because the approach does not work in general: it fails when key +\refmmz{context} is used \emph{within} memoized code (we will explain why in +section~\ref{sec:memos}). Another reason why I recommend the |\csname +... \endcsname| approach is that it does not result in an error when the +control sequence is not defined (|\csname ... \endcsname| will expand to +|\relax| then); this behaviour is handy for undefined cross-references, for +example. Second, why did I write |fsize={...}| around the control sequence? +Well, because I'm being paranoid, really. Writing \refmmz{context}|={\csname + f@size\endcsname}| would work just as well, but I like to explicitly +``announce'' the value to prevent any possibility of a conflict with an +alternative context. Imagine that we don't use the ``announcements'' and we +decide to add some other dimension instead of the font size to the context. +Now if that dimension happened to have the same value as the font size, Memoize +would incorrectly pick up the ``font size extern'' instead of producing a new +one. + +It bears emphasizing that whatever you add to the context expression must be +fully expandable, and also not merely declared as robust. So writing +\code{\refmmz{context}=\cs{ref}\marg{key}}, for example, would be unwise, since +it would not work as intended when package \pkg{hyperref} is loaded. (This +package declares \refcmd{ref} as robust, so it won't expand to the +cross-reference value.) You have to look up where the cross-references are +stored internally; the cross-reference for \meta{key} turns out to be stored in +the internal control sequence |\r@|\meta{key}, so it is |\csname +r@|\meta{key}|\endcsname| that the \refmmzauto{ref} handler actually appends to +the context. + +The padding and font-size contexts are useful quite generally. However, the +context can be pretty command-specific, as well. Consider the \pkg{skak} +package used for typesetting chess games. The board is drawn using command +|\showboard|, but this command has no arguments, because it draws the state of +the board that is reached by the moves given by command |\mainline|. Memoizing +|\showboard| as such will therefore yield the wrong result --- all the boards +will be one and the same board! The solution is to provide the correct +context: we dig into the \pkg{skak} sources and realize that the current +board is stored in macro |\csname chessgame.skak.mainline\endcsname|. + +\ExampleName{skak} +\makeexample{\examplename.pdf} +\tcbinputexample{ + sidebyside, lefthand ratio=0.48, + comment={ + \includeexamplepdf[extern page]{page=1,trim=1in 1in 1in 1in,scale=0.45}\quad + \includeexamplepdf[extern page]{page=2,trim=1in 1in 1in 1in,scale=0.45} + } +} + +If you remove \code{\refmmz{context}=\bracestt{...}} from the code above, you +will end up with a document where the final board drawn takes place of all the +boards. This is so because in that case, all externs are written into the same +file, so the final extern overwrites the previous ones, but note that you will +only observe this after the second compilation, when the externs are actually +used. + + + +\subsection{More on redefinitions and stale externs} +\label{sec:tut:redefinitions} + +In this subsection, we elaborate on an issue touched upon at the beginning of +section~\ref{sec:tut:working-on-a-picture}: what happens if the memoized code +depends on some macro or style which gets redefined? The answer was +``nothing,'' and one solution was to \refmmz{recompile} the code. Let us take +the example from that section a bit further. We will propose no new solution +or workaround, but deepen our understanding of the issue. + +\ExampleName{redefinitions} +\makeexample{\examplename.pdf N=6} +\tcbset{ + recompile step/.style={ + sidebyside, lefthand ratio=0.74, title=, + comment={\includeexamplepdf[blankest][\examplepath.c\theenumi.pdf]{#1}}, + /utils/exec={\addtocounter{enumi}{1}}, + enhanced, overlay app={\node[at=(frame.north west), font=\scriptsize, circle, fill=black!80!white, text=white, inner sep=1pt]{\theenumi};}, + }, +} +\begin{tcboxedraster}[raster columns=1]{% + title=Working on \texttt{\examplename.tex}, enhanced, breakable} + \setcounter{enumi}{0}% + \tcbox[blankest]{I like red. My emphasized nodes will have red background.} + \tcbinputexample[.tex][.c1]{recompile step={page=2}} + \tcbox[blankest]{Hmm, this particular node is really important, + let me put the text in italics as well!} + \tcbinputexample[.tex][.c2]{recompile step={page=2}, no attachment} + \tcbox[blankest]{You know what? Perhaps yellow background would work better + --- in general.} + \tcbinputexample[.tex][.c3]{recompile step={page=1}, bad, no attachment} + \tcbox[blankest]{How come my node is still red?! Oh yes, I changed the + style, so I have to recompile the extern!} + \tcbinputexample[.tex][.c4]{recompile step={page=2}, no attachment} + \tcbox[blankest]{Ahh, yellow background, that's much better. But you know + what, this double emphasis won't do after all, let me go back to the + upright shape.} + \tcbinputexample[.tex][.c5]{recompile step={page=1}, bad, no attachment} + \tcbox[blankest]{Red???!???!? Ok, I know that recompiling will help, but what + happened here?} + \tcbinputexample[.tex][.c6]{recompile step={page=2}, no attachment} +\end{tcboxedraster} + +What happened is that the externs from steps~1 and~5 share the very same code. +In step~1, this code was compiled when the red |emph| style was in effect, and +that extern lingered and was eventually picked up again in step~5, Memoize +having no idea that it is including an extern produced with the obsolete +definition of the style. + +There are two points to this story. First (and forgetting for a moment about +the context, which we started discussing in +section~\ref{sec:cross-referencing}), Memoize identifies externs (and memos) by +the code that produced them --- or more precisely, by the MD5 sum of the code, +as each piece of code has a unique (well, unique enough) MD5 sum. Each extern +is saved in a file whose name contains this MD5 sum; see +section~\ref{sec:tut:memodir} for illustration. Generally, this is a very +useful feature. You can move your picture to another location in the document, +insert some other (externalized) picture in front of it, and so on, all without +triggering recompilation of the extern(s). (None of this is possible with +\TikZ's externalization library, which identifies the externs by the order in +which they appear in the document.) + +The downside of the MD5 sum approach is the potential pitfall illustrated +above, and the downside comes about because of the second point of the story: +Memoize does not attempt to delete the ``old'' externs. (However, as described +in section~\ref{sec:tut:memodir}, stale memos and externs can be easily removed +using the \refscript{memoize-clean.pl} script.) That would be not only +dangerous (as any deletion inherently is) but also potentially wasteful: what +if you have only temporarily removed some code, or compiled only a portion of +the document --- you surely wouldn't want your hard-won externs to disappear in +such a situation! + +The pitfall described above applies to any command which depends on parameters +which can be set prior to the invocation of the command, like \TikZ pictures, +which depend on the settings given in \cs{tikzset}. After customizing the +settings, you will have to recompile the externs: +\refcmd{mmznext}\braces{\refmmz{recompile}} is useful when you only have to +recompile a single extern; use \refcmd{mmzset}\braces{\refmmz{recompile}}, or +the package option \refmmz{recompile}, to recompile all externs in the +document; and there is also the middle road: if you have changed only Forest's +settings, you can write +\code{\refmmz{auto}=\braces{forest}\braces{\refmmz{recompile}}} to recompile +all and only the Forest trees. + +Above, we have seen the ``same code, same extern'' issue manifested ``through +time,'' i.e.\ Memoize was (incorrectly) reusing externs produced in previous +compilations, but the issue can also manifest ``through space.'' This can +happen if the same code appears twice in the same document --- but, crucially, +with some parameters which it depends on changed from one occurrence to the +next. Observe what happens in the following example, where the settings for +|\progressbar| are changed by |\progressbarchange|. After the first +compilation, everything looks fine. But as both extern pages were produced by +the same code, they will be stored into the same file, the second one +overwriting the first one. The second compilation pulls in the externs --- or +rather, the single extern --- resulting in the document containing the second +progressbar in the place of the first one as well. + +\ExampleName{progressbar} +\makeexample{\examplename.pdf N=2} +\tcbinputexample{ + bad, float, + comment={ + \begin{tcbitemize}[raster columns=3,raster equal height,halign=center,valign=center] + \tcbitem[title=After 1\textsuperscript{st} compilation] + \includeexamplepdf[extern page,remember as=extern1]{page=1,trim=1in 1in 1in 1in}\\ + \includeexamplepdf[extern page,remember as=extern2]{page=2,trim=1in 1in 1in 1in}\\ + \includeexamplepdf[document page]{page=3} + \tcbitem[title=After extern extraction\vphantom{p\textsuperscript{nd}}] + \includeexamplepdf[my boxed title=the extern file,attach shifted boxed title to top right,remember as=externfile]{page=2,trim=1in 1in 1in 1in} + \tcbitem[title=After 2\textsuperscript{nd} compilation] + \includeexamplepdf[document page,remember as=doc][\examplepath.c2.pdf]{page=1} + \end{tcbitemize} + \begin{tikzpicture}[remember picture,overlay] + \draw[->, thick, red] (extern1.east) to[out=east, in=west] ([yshift=0.5ex]externfile.west); + \draw[->, thick, red] (extern2.east) to[out=east, in=west] ([yshift=-0.5ex]externfile.west); + \draw[->, thick, blue] (externfile.east) to[out=east, in=west] (doc.west); + \end{tikzpicture} + }, +} + +The same ``extern duplication'' can arise due to how a particular command is +implemented. Say we deactivate automemoization of Forest trees +(\code{\refmmz{deactivate}=forest}), but keep on automemoizing \TikZ pictures. +Forest uses \refenv{tikzpicture}\noprint{\refenv{forest}} under the hood (a +lot); in particular, the tree itself is typeset as a \env{tikzpicture} +environment. But the code that typesets it is the same for all trees, +regardless of their content (the actual content of the tree is hidden in +various macros and boxes, rather than ``pasted'' into the \env{tikzpicture}). +Consequently, the final tree of the document will overwrite all other trees in +the document, just as the second (and thus final) progress bar overwrote the +first one above.\footnote{That is assuming that \hologo{TeX} doesn't simply + spew a bunch of errors. This can happen as well. In the interest of full + disclosure, compiling a Forest tree in the situation described above would + actually also produce --- but only in the first compilation --- a number of + small empty extern pages, one for each node of the tree. A promise: Forest + will soon fully support Memoize and (among other things) avoid this pitfall. + But the principle will remain.} Ouch! + +Generally speaking, this final sort of extern duplication issue can arise +whenever we have an ``outer'' command that we don't want to (auto)memoize which +uses an ``inner'' command that we \emph{do} want to automemoize. The solution +is to use the \refmmz{auto} key \refmmzauto{nomemoize} on the outer command; +remember that this key disables memoization for the space of the command or +environment. For example, the correct way to ``deactivate'' automemoization of +\env{forest} environments (but keep automemoizing \TikZ pictures) is +\code{\refmmz{auto}=\bracestt{forest}\braces{\refmmzauto{nomemoize}}}. + + + + +\subsection{Supporting Memoize in your package} +\label{sec:tut:package} + +\subsubsection{Loading Memoize?} +\label{sec:loading-memoize} + +So you want to support Memoize in your package? That's great! + +What form precisely this support will take of course depends on your package. +For some commands, a simple \refmmz{auto} declaration will suffice; for other +commands, you might need to write a dedicated \emph{memoization driver}, as +explained in section~\ref{sec:memoization-drivers}. However, one thing is +clear: you \emph{don't} want to require Memoize's presence by +\cs{RequirePackage}\braces{\refpkg{memoize}} in your package. That would +trigger memoization, but triggering memoization should be left at the sole +discretion of the author. The question is, if you're not allowed to load +Memoize, how can you even issue the \refmmz{auto} declaration? + +Well, it's not that you really want to \emph{memoize} anything; you want to +make the commands of your package \emph{memoizable}. So: +\cs{RequirePackage}\braces{\Emph{\refpkg{memoizable}}} --- and note that in +`memoizable', the final `e' of `memoize' is dropped, apparently this is the +correct way to spell it. + +Loading \refpkg{memoizable} does nothing if Memoize is already loaded, and +behaves like package \refpkg{nomemoize} otherwise --- remember from +section~\ref{sec:tut:final} that \refpkg{nomemoize} is a dummy package which +accepts all the commands that Memoize does, but does nothing. + +I have decided to require that Memoize must be loaded before any package that +supports it. Allowing for an arbitrary loading order would complicate the +implementation (and possibly even turn out to be problematic), and furthermore, +Memoize likes to be loaded early anyaway, because it needs to be loaded before +the document PDF is opened if it is to perform the embedded extern extraction. +I don't think the ordering requirement will cause any problems --- let me know +if it does! --- but perhaps it is wise to inform the author about it in the +documentation of your package (I did so at the end of +section~\ref{sec:tut:automemoization}). Anyway, I have enforced the +requirement by raising an error and refusing to load the package in case +Memoize detects \refpkg{memoizable} to be loaded. + +Note that the loading order requirement implies that you can use +|\@ifpackageloaded{memoize}| to specifically react to the presence Memoize, if +necessary. + + +\subsubsection{Memoizable design} +\label{sec:memoizable-design} + +Many commands and environments can be submitted to externalization with a +single-line \refmmz{auto} declaration, as illustrated in +section~\ref{sec:tut:automemoization}, perhaps requiring an addition to the +context (section~\ref{sec:cross-referencing}), or some pre- or post-memoization +code (section~\ref{sec:tut:beamer}). In some situations, however, these simple +approaches won't work. Most often, this will happen when the extern must be +integrated into the document in some special way. For example, a command might +internally create floating material, or surround the core typeset material with +some stretchable space.\footnote{Commands and environments of package + \pkg{tcolorbox} exhibit both these issues (see \pkg{tcolorbox} options |float|, + |before| and |after|), and were in fact the inspiration for several technical + details of Memoize.} None of these behaviours can be replicated by merely +including the extern; with respect to the stretchable space, remember that an +extern, being a picture, has fixed size, so if our extra space ended up in the +extern, it would lose the stretchability. + +The key to successful memoization of problematic commands is their design. In +a nutshell, the idea is to \emph{break up the command's definition into two + parts, the outer command and the inner command, and only submit the inner + command to automemoization}. We will illustrate this with a simple +environment --- |poormansbox| --- which produces a potentially framed box of +the given width, and surrounds this box with some configurable material --- by +default, this material will be stretchable vertical space, and this will be the +source of our memoization problem. (In terms of user experience, the solution +in this section will leave something to be desired, but we will revisit the +example in section~\ref{sec:memoization-complex-single-driver} and make things +right.) + +Let us first take a look at a document using our to-be-developed box +environment. The |poormansbox| environment takes one optional argument, a +keylist of options, which can also be set with the |\pmbset| command. This +being a \textbf{p}oor \textbf{m}an's \textbf{b}ox, it doesn't recognize many +options. One can set the |width| of the box, or request that it occurs in a +|frame|, and the surrounding material can be configured using keys |before| and +|after|. As we will see later in the listing of the package, the box is +|\linewidth| wide by default, has no frame, and is surrounded by vertical glue +|\vskip 2ex plus 1ex minus 1ex| (|2ex| of natural vertical space which may both +stretch and shrink for |1ex|); furthermore, the default value of |before| +contains |\centering| to center the box horizontally (centering is of course +only observable when we change the width of the box). + +\ExampleName{poormansbox} +\makeexample{\examplename.sty} +\makeexample{\examplename.pdf} +\tcbinputexample{ + sidebyside, lefthand ratio=0.482, + comment={\centering + \includeexamplepdf[document page,left=0pt,right=0pt] + {page=1,scale=0.5,trim=0mm 3mm 0mm 3mm} + }, +} + +You might want to play with the example to see that the surrounding vertical +space is indeed stretchable. The example is set up so that the surrounding +space is shrunk a bit to fit all the material onto one page. But if you remove +the final |\lipsum[144]|, the natural amount of all vertical space can be +accommodated on the page, so you should observe an increase of vertical +spacing. + +You might have noticed that the example contains nested |poormansbox|es: the +second box (the one which contains |\lipsum[66]|) is nested within the first +one (between |\lipsum[101]| and |\lipsum[75]|). This is intentional: when we +will revisit the |poormansbox| example in +section~\ref{sec:memoization-complex-single-driver}, the implementation will +have to pay special attention to nesting (which presents no problem to the +implementation in this section). + +As you can see in the package listing below (\texttt{\examplename.sty}), the +implementation of our environment is straightforward. We first define the +configuration command |\pmbset| and the option keys (we're using +\pkg{pgfkeys}), and set the option defaults. Then, we move on to the +environment itself: we apply the given options, execute the pre-code, typeset +the box (which is a |minipage| of the given width, potentially wrapped in a +|\fbox|), and execute the post-code. + +\tcbinputexample[.sty]{listing only, float} + +\ExampleName{poormansbox-memoizable} +Now let's make our |poormansbox| externalizable (\texttt{\examplename.sty}). As +announced above, the idea is to split the definition of the environment into +the outer part (below, the user-level environment |poormansbox|), which +(applies the options and) executes the pre- and the post-code, and the inner +part (below, the macro |\@poormansbox|), which typesets the actual box. If we +then then submit |\@poormansbox|, rather than |poormansbox|, to +automemoization, the outer part will be executed at every compilation (giving +us stretchable space if we request it), while the inner command will be +executed (and memoized) at the first compilation, and substituted for the +extern (the fixed-size box) at subsequent compilations.\cprotect\footnote{You + might have wondered why our definition of the |poormansbox| environment grabs + the body into an argument (|+b|, yielding |#2|), necessitating the use of + |\NewDocumentEnvironment| over the venerable |\newenvironment|. One reason + was that having the environment body as an argument simplifies wrapping the + |\fbox| around the |minipage|, but there is a more important reason. If we + did not grab the environment body, we would have to implement the internal + part of the definition as an environment (|@poormansbox|) as well, and embed + it into the user-level environment using the following idiom: + |\newenvironment{poormansbox}[2][]{...\begin{@poormansbox}}{\end{@poormansbox}...}|. + However, as illustrated in section~\ref{sec:tut:good-to-know}, automemoizing + an environment indirectly embedded in such a way produces an error, because + Memoize is prevented from collecting the environment body.} + +\makeexample{\examplename.sty} +\makeexample{\examplename.pdf}% just to know if it compiles fine +\tcbinputexample[.sty]{listing only, float} + +Looking at the definition of the internal |\@poormansbox| command, it might +strike you as weird that we have equipped this command with an optional +argument (|#1|) it never uses. However, this optional argument is crucial for +memoization. It will become a part of the memoized code (note +\code{\refmmzauto{args}=om} in the \refmmz{auto} declaration) and thereby +ensure that Memoize will produce separate externs for invocations of +|\@poormansbox| with the same environment body but different options; or in +other words, it will ensure that changing the options recompiles the +extern.\cprotect\footnote{Of course, this only holds for options given in the + optional arguments; if the user changes an option value using a prior + \cs{pmbset} (and that option does not occur in the optional argument), + Memoize won't detect the change. But the end-user knows about this issue, as + it was addressed in sections~\ref{sec:tut:working-on-a-picture} + and~\ref{sec:tut:redefinitions}, and she is also aware of two workarounds: + manual recompilation, or setting the context + (section~\ref{sec:cross-referencing}). + + While we're on the subject of the context, note that it is also possible to + deploy context to trigger recompilation of the inner command upon change of + parameters it depends on. We could simply omit the optional argument of + \cs{@poormansbox} and add + \refmmz{context}|={width=\pmb@width,frame=\ifpmb@frame true\else false\fi}|, + to the \refmmz{auto} declaration. The advantage of such an approach is that + Memoize reacts to the change of parameters regardless of whether they are set + using the optional argument or \cs{pmbset}. However, the approach is + unfeasible for commands depending on many parameters: can you imagine listing + all the \TikZ options in the context? Not to mention that a particular + picture usually only depends on a small subset of these options --- by and + large, \TikZ externs would get recompiled too often if the context contained + all \TikZ options.}\footnote{I have toyed with the idea of + splitting (using \pkg{pgfkeys} key filtering) the given options into outer + options, relevant for the outer command, and inner options, relevant for the + inner command, and only passing the inner options to the inner command. The + thought was that would (i) avoid recompiling the extern when only outer + options change, as these options don't affect the inner command, and (ii) + avoid applying the inner options when utilizing the extern, as these options + don't affect the outer command. However, it then hit me that the end-user + might define a style which incorporated both inner and outer options --- I + know I do this with my |tcolorbox|es.} + +The downside to automemoizing an internal command is that this might be +counter-intuitive for the author. For example, to deactivate automemoization +of |poormansbox|, the author will have to write +\refcmd{mmzset}\bracestt{\refmmz{deactivate}=\cs{@poormansbox}} (note the +|\@|), but they will have no clue they have to do this unless they have +carefully read |poormansbox|'s documentation. Even worse, the above +\refcmd{mmzset} command will not work unless surrounded by |\makeatletter| and +|\makeatother|, as it refers to an internal control sequence containing |@|. +Well, Memoize offers \refmmz{auto csname}, \refmmz{activate csname} and +\refmmz{deactivate csname}, so that |@| catagory code manipulations can be +omitted by writing \refcmd{mmzset}\bracestt{\refmmz{deactivate + csname}|=@poormansbox|}, but still. + +Another downside could occur when you use the same (automemoized) internal +command in service of several user interface commands. For the sake of +illustration, assume we have also defined an UI-macro |\pmb| which again relies +on |\@poormansbox|. How is the author to deactivate automemoization of |\pmb| +but leave the |poormansbox| environment intact? This is how: +\refcmd{mmzset}\bracestt{\refmmz{auto}=\cs{pmb}\bracestt{\refmmzauto{args}=m, + \refmmzauto{nomemoize}}}. Again, counter-intuitive; the author expects +\refcmd{mmzset}\bracestt{\refmmz{deactivate}=\cs{pmb}} to work. + +One other consequence of this approach is that the code included in the c-memo +(if \refmmz{include source in cmemo} is in effect) will not faithfully reflect +the source: as shown in the c-memo listing below, it will contain +|\@poormansbox{...}| instead of |\begin{poormansbox}...\end{poormansbox}| --- +even if this might actually count as an advantage, as the discrepancy will at +least inform the author who refuses to read the fine material accompanying our +|poormansbox| that something funky is going on. + +\begingroup +\relaxmmzcommands +\def\mmzNewCMemo#1{% fetch the last c-memo filename + \def\mycmemo{#1}% +} +\input{\examplepath.mmz.c1} +\sed{% + s/[]]\lbrace/]\n\space\space\space\space\space\space\space + \space\space\space\space\space\space\space\lbrace/; +}{\exampledir\mycmemo} +\tcbinputlisting{ + listing only, + listing file=\exampledir\mycmemo, + example title, + title=the c-memo of the last \texttt{poormansbox} environment, + left=0.5em, right=0em, +} +\endgroup + +In a nutshell, automemoizing an internal command might be counter-intuitive for +the author. But the core idea --- to support memoization of a resistant +command by splitting its definition into the outer and the inner command --- is +sound, and we will elaborate on this idea in +section~\ref{sec:memoization-complex-single-driver}, where we will revisit our +|poormansbox| example and develop a variant of this environment which is both +memoizable and user-friendly. + + +\section{Under the hood} +\label{sec:under-the-hood} + +This chapter is written for three audiences: a curious user who wants to +know how Memoize does what it does; a package writer who wants to support +Memoize in a tricky situation; and myself, lest I forget why I made the design +decisions that I made. + +\subsection{The entry point} +\label{sec:Memoize} + + +From the author's perspective, the functionality of this package is entered +either through the manual memoization commands (macro \refcmd{mmz} and +environment \refenv{memoize}), or via automemoization. And while that is +correct, those user interface entry points merely determine what code is +submitted to memoization, and set any options specific to the upcoming +memoization. The real fun starts with command \Emph{\refcmd{Memoize}}, which +is eventually executed by both manual and automatic memoization. + +Not every call to \refcmd{Memoize} results in memoization. Calling this macro +has three possible outcomes. It can result in \emph{memoization}, which +produces the memos and externs; in \emph{utilization} of the result of an +earlier memoization (which boils down to inputting the memos); or in +\emph{regular compilation}, whereby the code is compiled +(almost)\footnote{\label{fn:regular-compilation-almost}This is absolutely true + for memoized code which is ``contained'' in the sense of not peeking into the + input stream following the memoized code. In general, code which fails to + satisfy this containment requirement is most likely simply not memoizable; + but there are borderline cases. For example, \refcmd{ignorespaces} at the + end of some code will have the expected effect in the absence of Memoize, but + no effect when executed either during memoization or regular compilation + under Memoize, simply because it will hit some code belonging to Memoize + rather than the continuation of the document. Memoize offers the + \refmmz{ignore spaces} provision to work around this specific problem.} as if +Memoize was not there. Which outcome obtains depends on several factors. The +decision logic is depicted below, and note that you can \refmmz{trace} the +action on the terminal. + +\begin{center} + \begin{forest} + yes/.style={edge=->,edge label={node[font=\scriptsize,pos=0.4,left,#1]{yes}}}, + no/.style={edge=->,edge label={node[font=\scriptsize,pos=0.4,right,#1]{no}}}, + use/.style={content=utilization\\(of the cc-memo)\vphantom{dj},fill=orange,align=center}, + memoize/.style={content=memoization\vphantom{dj},fill=green}, + compile/.style={content=regular compilation\vphantom{dj},fill=emphcolor}, + for tree={ + l sep*=1.5, + if n children={0}{}{align=center}, + child anchor=north, + }, + [Is Memoize enabled?\\(\refcmd{ifmemoize}), + [Are we currently undergoing memoization?\\(\refcmd{ifmemoizing}), yes + [,compile, yes] + [Is the\\\refmmz{recompile}\\mode in effect?, no + [,memoize, yes] + [Do the relevant\\memos and externs exist?, no + [,use, yes] + [Is the\\\refmmz{readonly}\\mode in effect?,no + [,compile, yes] + [,memoize, no] + ] + ] + ] + ] + [,compile, no] + ] + \end{forest} +\end{center} + +As the memoization options were already set by the user interface entry points, +you might expect, quite reasonably, that \refcmd{Memoize} takes a single +argument, the code submitted to memoization. After all, what more does it +need? Clearly, executing this code is what produces the typeset material, and +to detect whether the code has ``changed'' (in order to recompile the memos and +externs), we compute the MD5 sum of this very code, don't we? Well, the +reality is a bit more complicated. When it comes to automemoized commands, the +code which the MD5 sum is computed off of (and which is displayed in the c-memo +if \refmmz{include source in cmemo} is in effect) is not exactly the same as +the code we compile (during either memoization or regular compilation). We'll +see what the difference is in section~\ref{sec:tut:automemoization-details}; +what matters here is that we must provide \refcmd{Memoize} with both and that +this macro therefore takes two arguments: the \emph{identification code}, which +the MD5 sum is computed off of, and the \emph{executable code}, which, well, is +the code that gets executed during memoization (or regular compilation). + +Let's illustrate this with an example which is probably entirely useless (but +don't worry, we'll get to a realistic example in +section~\ref{sec:tut:automemoization-details}). We first memoize some text +manually, using command \refcmd{mmz}, and then do something very stupid: we use +this very text as the identification code for the following \refcmd{Memoize}, +even if the executable code of that command is completely different. The +second line of the typeset output should convince you that the first argument +to that command was really used to produce the extern; and one further +compilation should convince you that the first argument was indeed used to +identify the extern: the extern produced by \refcmd{mmz} was overwritten by the +extern produced by \refcmd{Memoize}, in the fashion of the |progressbar| +example from section~\ref{sec:tut:redefinitions}. + +\ExampleName{memoize-internal} +\makeexample{\examplename.pdf N=2} +\tcbinputexample{ + comment={% + \includeexamplepdf[document page, + after title pre={\ (after the first compilation)} + ]{page=3}\hfill + \includeexamplepdf[document page, + after title pre={\ (after the second compilation)} + ][\examplepath.c2.pdf]{page=1} + }, +} + +The example above also illustrates a(nother) peculiar feature of +\refcmd{Memoize}. \refcmd{Memoize} does not open a new \hologo{TeX} group, but +it \emph{expects a group to be opened prior to calling it}, as it will issue an +|\endgroup| at some point. Specifically, the memoization group will be closed +before regular compilation or utilization, but after memoization. If you want +to know why, read the boxed text below. + +\begin{tcolorbox}[title=\string\Memoize\ and grouping, enhanced, breakable] + One important desideratum behind the design of Memoize was that using the + package should disrupt the original, Memoize-less compilation as little as + possible. In particular, if the memoized code contains local assignments + whose effect (in the original compilation) persists into the rest of the + document (until the end of the surrounding \hologo{TeX} group, of course), + wouldn't one want these local effects to persist when Memoize is around, as + well? Fortunately, most memoized code does not have persistent local effects + (at least for me, it is usually environments, like \env{tikzpicture}s, that I + want to memoize, and environments introduce a group anyway) --- fortunately, + because there are design reasons for enclosing memoization in a \hologo{TeX} + group (or two), and this enclosure will of course cancel the effect of local + assignments in the memoized code. + + For one, the user interface memoization commands, such as \refcmd{mmz} and + automemoized commands, allow for options specific to a particular piece of + memoized code (the options given as the optional argument to manual + memoization, the next-options and the auto-options), and to delimit their + effect, it makes most sense to apply them in a group. I have toyed with the + idea of working around the introduction of a group by manually saving and + restoring all the options, but I quickly gave up on this line of thought. + For one, manually saving and restoring the options would be cumbersome and + error-prone, and probably also slower than using the group. But even worse, + all that work would not really solve the problem of the persistence of local + effects, because memoization itself introduces a group, as well: during + memoization, the typeset material is collected into a box, and opening a box + introduces a group. In some particular situations, this could be avoided by + typesetting the memoized code as-is and collecting the resulting material + using |\lastbox|, but this approach cannot work in general. In general, + memoization will take place in a group, so the issue of local effects must be + addressed in some other way. Memoize offers the following workaround: during + memoization, the memoized code can (globally) add code to the \refmmz{after + memoization} hook, which gets executed immediately after closing the + memoization group. + + Does this mean it would be best if the user interface memoization commands + straightforwardly surrounded \refcmd{Memoize} by |\begingroup| and + |\endgroup|? For example, \refcmd{mmz} would open the memoization group, let + \refcmd{Memoize} do its work, and then close the group. Not really. + Remember that memoization is not the only possible outcome of calling + \refcmd{Memoize}. Perhaps we can at least retain the local effects of a + regular compilation, and of utilization? + + We can, by finely tuning the timing of the memoization group closure within + \refcmd{Memoize}. This command is designed to close the memoization group + after memoization, but before regular compilation and utilization. Closing + the group after memoization makes sure that the given options are in effect + during this process. By closing the group prior to regular compilation, + regular compilation of the memoized code (which takes place when Memoize is + disabled, for example) is guaranteed to have (almost, see + footnote~\ref{fn:regular-compilation-almost}) exactly the same effect as the + compilation of that code in absence of Memoize; in particular, the effect of + any local assignments will persist into the rest of the document. Finally, + closing the group before utilization simplifies the construction of the memo + in the cases where we need to replicate local effects of the memoized code + --- the group closed, there is no need to smuggle local assignments out of a + memo. +\end{tcolorbox} + +\subsection{Memos} +\label{sec:memos} + +Up until now, we have pretended that there is a single kind of a memo file. In +truth, there's two kinds: \emph{code memos}, or \emph{c-memos} for short; and +\emph{code--context memos}, or \emph{cc-memos} for short. In this section, we +will learn what they are for, and how they look like --- and also a bit on how +they are produced, even if the details on that will have to wait until +section~\ref{sec:memoization-drivers}. + +We will see that when Memoize utilizes memos, c-memos are processed first. But +conceptually, cc-memos are more important, so we will start the discussion with +these. + +\subsubsection{Cc-memos (and extern inclusion)} +\label{sec:cc-memos} + +When it is input, a cc-memo replicates the effect of the memoized code. This +includes the reproduction of its visual output, which takes the form of +inclusion of any externs produced by memoization. And yes, you got the +implication right: a cc-memo can have any number of associated externs, +including zero, even if the most common case is that of exactly one extern per +cc-memo. The number of externs mostly depends on the memoization driver (see +section~\ref{sec:memoization-drivers}); the default driver always produces +exactly one extern. + +A cc-memo is located in the directory given via subkey \refmmzpath{dir} (and +\refmmzpath{relative}) of key \refmmz{path}. You can recognize it by its filename, +which has the following form (\meta{prefix} is set via subkey \refmmzpath{prefix}): +\begin{center} + \code{\meta{prefix}\meta{code md5sum}-\meta{context md5sum}.memo} +\end{center} + +In fact, this is how Memoize recognizes --- or rather, searches for --- a +cc-memo as well: Memoize will utilize a cc-memo when the code and the context +MD5 sum computed during an attempted utilization match the code and the context +MD5 sum computed during some previous memoization (for details on the context +MD5 sum, see section~\ref{sec:c-memos}). In detail, a cc-memo is created at +the end of memoization, at which point Memoize computes the MD5 sum of the +memoized code and the MD5 sum of the context, and writes the results of +memoization into the cc-memo identified by (the prefix and) these two MD5 sums. +And when Memoize, on a subsequent compilation, encounters a piece of memoized +code, it again computes the MD5 sum of that code and the MD5 sum of the +context, and tries to input the cc-memo identified by (the prefix and) these +two MD5 sums. If the inputting is successful, we have utilized the cc-memo +(which in the typical case amounts to including the one associated extern); if +the cc-memo cannot be found, Memoize starts the memoization process, which +creates the memos and the externs. + +Let us take a look at the contents of a cc-memo in detail. Here's a typical +cc-memo (it belongs to the titlepage penguin): + +\begingroup +\relaxmmzcommands +\def\mmzNewCCMemo#1{% fetch the first cc-memo filename + \def\myccmemo{#1}% + \endinput +} +\input{\exampledir titlepage.mmz.c1} +\sed{% + s/\cmd{quitvmode} \cmd{mmzIncludeExtern}/\cmd{quitvmode}\n\cmd{mmzIncludeExtern}/; + s/\(\marg\marg\marg\)\(\marg\marg\marg\marg\marg\)/\1\space\2/; +}{\exampledir\myccmemo} +\tcbinputlisting{ + listing only, + listing file=\exampledir\myccmemo, + example title=\myccmemo, +} +\endgroup + +A cc-memo begins by listing the externs which the memo will (actually, might) +attempt to include into the document. When the cc-memo is input, each +\refcmd{mmzResource} command checks if the given extern exists. If some +existence check fails, Memoize enters the memoization mode, same as if the +cc-memo itself did not exist. If all the resources pass the existence check, +Memoize inputs the core of the cc-memo, i.e.\ everything following the +\refcmd{mmzMemo} marker. + +The core might contain arbitrary code, but most often, it will consist of only +two commands. The first one is \refcmd{quitvmode} and it is included if the +extern was \refmmz{capture}d into a horizontal box (which is the usual +situation). The second one is \refcmd{mmzIncludeExtern}, and it is this +command which actually includes the extern into the document upon inputting the +cc-memo. The core code is executed without introducing any groups, i.e.\ the +effect of any local assignments in the cc-memo will persist into the code +following the memoized code. + +Command \refcmd{mmzIncludeExtern} takes nine parameters. The first is the +sequential number of the extern associated with the cc-memo, starting with 0; +usually, this is simply 0 as most memos are associated with a single extern. +The second one is a \refcmd{hbox} or \refcmd{vbox}, noting the type of the box +the memoized code was externalized into. The next three numbers are the +expected width, height and the depth of the extern. Finally, we have the four +\refmmz{padding} amounts (left, bottom, right and top). We should arrive at +the expected size after trimming the extern PDF by the +padding amounts; Memoize will complain if we don't. + +Let's look at a more interesting cc-memo. Using the advising framework, +described in section~\ref{sec:tut:automemoization-details}, Memoize hacks +\refcmd{label} to support \cs{label}s inside memoized code --- the following +code ``just works.'' + +\ExampleName{label} +\makeexample{\examplename.pdf N=3} +\tcbinputexample{ + comment={\centering + \includeexamplepdf[document page, after title pre={\ (compilation 1)}, + left=1.5mm, right=1mm]{page=2}\quad + \includeexamplepdf[document page, after title pre={\ (compilation 2)}, + left=1.5mm, right=1mm][\examplepath.c2.pdf]{page=1}\quad + \includeexamplepdf[document page, after title pre={\ (compilation 3)}, + left=1.5mm, right=1mm][\examplepath.c3.pdf]{page=1} + }, +} + +Everything seems normal --- after the first compilation, we get ``\textbf{??}'' +because the label has not made it into the |.aux| file yet, but in subsequent +compilations, we learn where the penguin lives --- but it is far from normal +under the hood. If we de-hacked \cs{label} by writing +\refcmd{mmzset}\bracestt{\refmmz{deactivate}=\cs{label}}, the third compilation +(and subsequent compilations) would revert to ``\textbf{??}''. Why would that +happen? The memoized code containing the \cs{label}s is only executed in the +first compilation; in the subsequent compilations, we're simply inputting the +cc-memo, so the memoized code, including any \cs{label}s in contains, is not +compiled, and the labels don't get into the |.aux| file anymore. + +The \cs{label} hack deploys Memoize's ability to put arbitrary code into the +cc-memo. During memoization, the memoized code may add arbitrary code to +register \refcmd{mmzCCMemo}, and the contents of this register at the end of +the memoization form the free-form part of the cc-memo.\footnote{This is also + how the above-described code containing \refcmd{mmzIncludeExtern} gets into + the cc-memo. The code is produced by \refcmd{mmzExternalizeBox} and appended + to \refcmd{mmzCCMemo} by the default memoization driver + \refcmd{mmzSingleExternDriver}; see section~\ref{sec:memoization-drivers} for + details.} When the hacked \cs{label} is encountered during memoization, it +appends \refcmd{mmzLabel}\marg{label name}\marg{current label value} to +\refcmd{mmzCCMemo}, so this command winds up in the cc-memo. It is then a +simple job for \refcmd{mmzLabel}, executed when the cc-memo is input at +subsequent compilations, to temporarily store \meta{current label value} (i.e.\ +the contents of \refcmd{@currentlabel} at the time the \cs{label} was invoked) back +into \cs{@currentlabel} and to execute \cs{label}\marg{label name}. In effect, any +\cs{label} command contained within the memoized code is executed at every +compilation, even if the memoized code itself is not compiled. + +\begingroup +\relaxmmzcommands +\def\mmzNewCCMemo#1{% fetch the first cc-memo filename + \def\myccmemo{#1}% + \endinput +} +\input{\examplepath.mmz.c1} +\sed{% + s/~//g; + s/\cmd{quitvmode} \cmd{mmzLabel}/\cmd{quitvmode}\n\cmd{mmzLabel}/; + s/\(\marg\)\cmd{mmzIncludeExtern}/\1\n\cmd{mmzIncludeExtern}/; + s/\(\cmd{mmzLabel}\) *\(\marg\marg\) */~\1\2~\space/g; + s/\(\marg\marg\marg\)\(\marg\marg\marg\marg\marg\)/\1\space\2/; +}{\exampledir\myccmemo} +\tcbinputlisting{float, + listing only, right=1.5mm, + listing file=\exampledir\myccmemo, + example title=\myccmemo, +} +\endgroup + +We will continue the discussion of \refcmd{label} in section~\ref{sec:label+} using +a funkier example. + + +\subsubsection{C-memos (and context)} +\label{sec:c-memos} + +As explained in the previous section, a cc-memo belonging to a piece of +memoized code is identified by two MD5 sums: the MD5 sum of the memoized code, +and the MD5 sum of the associated context. However, when Memoize encounters +some code submitted to memoization, the context expression is not yet fully +known, as it may be adjusted by the memoized code itself during memoization --- +and this potential adjustment is crucial for \cs{ref} and friends to work as +advertised (see section~\ref{sec:cross-referencing}). Upon being invoked, +Memoize therefore cannot immediately attempt to input the cc-memo; it needs to +first learn about the context adjustments. Here's where c-memos enter the +picture: \emph{the primary job of a c-memo is to store the context adjustments + made by the memoized code}. Let's see how this works in detail. + +Same as cc-memos, c-memos are located in the directory given via subkey +\refmmzpath{dir} (and \refmmzpath{relative}) of key \refmmz{path}. However, a c-memo +belonging to some memoized code is identified by the MD5 sum of that code +alone; its filename has the following form, where \meta{prefix} is again set +via subkey \refmmzpath{prefix}: +\begin{center} + \code{\meta{prefix}\meta{code md5sum}.memo} +\end{center} + +The c-memo is created at the end of the memoization process. At that time, the +context expression is fully known, as the memoized code was already processed. +Even more, Memoize keeps track of both the state of the context expression +prior to memoization, stored in token register \refcmd{mmzContext}, and of the +\emph{additions} to the context expression made by the memoized code, which are +stored in token register \refcmd{mmzContextExtra}. (Incidentally, key +\refmmz{context} automatically adapts to the situation by appending to +\refcmd{mmzContext} outside memoization and to \refcmd{mmzContextExtra} during +memoization.) The complete context expression is the concatenation of the +contents of these two registers, but it is only the context expression +additions, i.e.\ the contents of \refcmd{mmzContextExtra}, which Memoize stores +into the c-memo, with the idea that during subsequent compilations, the initial +context (\refcmd{mmzContext}) will be set up again via ``normal'' compilation, +while inputting the c-memo will restore the additions, jointly reconstructing +the complete context expression associated with a piece of memoized code to +what it was at the end of memoization. + +We can now complete the picture of a utilization attempt started in +section~\ref{sec:cc-memos}. Memoize begins by trying to input the c-memo; this +can be done as the c-memo can be identified based solely on (the MD5 sum of) +the memoized code. If the c-memo does not exist, Memoize starts the +memoization process, which will produce the memos and the externs. But if it +does exist, inputting it reconstructs the context expression to the state at +the end of memoization. Therefore, as the MD5 sum of the \emph{expansion} of +the context expression \emph{at the end of memoization} is baked into the +cc-memo filename, trying to load the cc-memo identified by (the prefix, the +code MD5 sum and) the MD5 sum of the \emph{expansion} of the context expression +\emph{at attempted utilization} will succeed precisely when the context +remained unchanged from memoization to attempted utilization. + +All this might have sounded very complicated, but in the end, most c-memos are +quite boring, the titlepage penguin's c-memo shown below being no exception. A +c-memo starts with the \refcmd{mmzMemo} marker, which is always followed by a +(global) assignment to token register \refcmd{mmzContextExtra}, holding the +context expression additions. As promised, the c-memo below is boring: it +assigns an empty token list to this register, leaving the context expression +as-is. Next comes the free-form part of the memo. Below, it is boringly empty +as well (just the percent sign), but in principle, it will contain any code +gathered in register \refcmd{mmzCMemo} during memoization; see +\ref{sec:per-overlay} for an example. A c-memo is concluded by an optional +part consisting of the \refcmd{mmzSource} marker, followed by the memoized +code. The source code section is not used by Memoize in any way and can be +switched off by \code{\refmmz{include source in cmemo}=false}; it is included +by default so that an interested user can know which code produced which memo, +which can be useful if one wants to trigger recompilation of an extern by +deleting the corresponding memo. Incidentally, any newlines in the source code +are lost in the c-memo replica (unless \refmmz{verbatim} is in effect), but we +will only see this once we arrive at the \pkg{beamer} example below. + +\begingroup +\relaxmmzcommands +\def\mmzNewCMemo#1{% fetch the first c-memo filename + \def\mycmemo{#1}% + \endinput +} +\input{\exampledir titlepage.mmz.c1} +\tcbinputlisting{ + listing only, + listing file=\exampledir\mycmemo, + example title=\mycmemo, +} +\endgroup + +A c-memo of code containing a cross-reference will prove more interesting. The +following c-memo was produced by the |ref| example from section +\ref{sec:cross-referencing}. As we know from that section, when a \refcmd{ref} (as +hacked by Memoize's \refmmzauto{ref} key) occurs in some memoized code, it +appends the cross-reference to the context. In the c-memo below, this is +reflected by the (global) assignment of an expression containing the +cross-reference macro to token register \refcmd{mmzContextExtra}, holding +the context expression additions. + +\begingroup +\relaxmmzcommands +\def\mmzNewCMemo#1{% fetch the first c-memo filename + \def\mycmemo{#1}% + \endinput +} +\input{\exampledir ref.mmz.c1} +\sed{% + s/~//g; + s/\cmd{global}.*\marg/~\0~/; +}{\exampledir\mycmemo} +\tcbinputlisting{ + listing only, + listing file=\exampledir\mycmemo, + example title=\mycmemo, +} +\endgroup + + +\subsubsection{More on \texorpdfstring{\cs{label}}{\textbackslash label}} +\label{sec:label+} + +A \refcmd{label} inside memoized code works out of the box in the usual situation +when label value is fully determined by the memoized code, as in the example in +section~\ref{sec:cc-memos}, where the memoized code contained the outermost +(and only) \env{enumerate} environment. However, the out of the box approach does +not work if the label value is (fully or partially) determined outside the +memoized code. To illustrate the problem, and some potential solutions, we +define two very simple enumeration environments, |listi| and |listii|, which +use counters |counti| and |countii|, and which are intended as the outer and +the inner environment, respectively. Our interest here is in the inner +environment, |listii|. While it prefixes each item by an indented +|\thecountii)|, the label is a composite of both counters: +|\thecounti\thecountii|. The label is stored into \refcmd{@currentlabel}, so +referencing works as usual. However, problems arise when we automemoize the +inner environment. + +\ExampleName{label+} +\makeexample{\examplename.tex.c1 N=6} +\makeexample{\examplename.pdf N=2} +\tcbinputexample{ + sidebyside, lefthand ratio=0.53, + comment={\centering\includeexamplepdf[document page][\examplepath.c2.pdf]{page=1}}, +} + +While the result looks fine at first, changing the order of |listii| +environments, for example by moving ``pets'' below ``domestic'', will result in a +problem: the reference at the bottom will remain unchanged. This is so because +the reference text is baked into the cc-memo, as shown below. + +\begingroup +\relaxmmzcommands +\def\mmzNewCCMemo#1{% fetch the first cc-memo filename + \def\myccmemo{#1}% + \endinput +} +\input{\examplepath.mmz.c1} +\sed{% + s/~//g; + s/\cmd{mmzLabel} \marg\marg/~\0~/; + s/\(\marg\)\(\cmd{mmzIncludeExtern}\)/\1 \2/; + s/\(\marg\marg\marg\)\(\marg\marg\marg\marg\marg\)/\1\space\2/; +}{\exampledir\myccmemo} +\tcbinputlisting{ + listing only, + listing file=\exampledir\myccmemo, + example title=\myccmemo, +} +\endgroup + +How can we remedy this? The manual option is to force the recompilation of the +extern by putting an (invisible) reference to the outer item into the inner +item: add |\label{item:pets}| to item ``pets'' and refer to it at ``dog'' by +\refcmd{mmzNoRef}|{item:pets}|.% +\attachexample[\examplename mmzNoRef.tex][\examplepath.tex.c3.attachment] + +An automatic variant of the recompilation solution is to add \cs{@currentlabel} +to the context upon memoizing |listii|. This can be achieved by adding +\refmmz{context}|={@currentlabel={\csuse{@currentlabel}}}| to the \refmmz{auto} +declaration for |listii|. The downside of this approach is that every |listii| +will get reexternalized upon movement, whether it actually contains a label or +not.\attachexample[\examplename context.tex][\examplepath.tex.c4.attachment] + +In fact, given that the externs produced by the inner environment do not +contain the value of the outer counter, it seems wasteful to recompile any +extern just to change the reference. And indeed, it is possible to avoid this, +but the approach unfortunately requires adapting the inner environment code +(and this is why I have not illustrated the problem using an environment of an +elaborate package like \pkg{enumitem}). The idea is to ``unbake'' the +reference to the outer item in the cc-memo. We can achieve this by changing +|listii| to define \cs{@currentlabel} to be |\unexpanded{\thecounti}\thecountii|. +Under this definition, the cc-memo will contain |\mmzLabel +{item:dog}{\thecounti a}|, and rearranging |listii| environments will produce +(upon two compilations, of course) the correct reference without recompiling +the extern. Note again, however, that this solution can only work when the +value of the outer counter does not appear in the extern, i.e.\ it would not +work the ``dog'' item was prefixed by |1a)| rather than simply |a)|. In those +cases, one should deploy one of the other solutions.% +\attachexample[\examplename listii.tex][\examplepath.tex.c5.attachment] + +The final solution, presented below, is an elaboration on the second one. +Rather than append \cs{@currentlabel} to the context immediately upon beginning +to memoize environment |listii|, we will at that point redefine \cs{label} to do +that. In effect, changing the location of |listii| will only recompile it if +it contains a \cs{label}. + +At announced, we redefine \cs{label} once the memoization of |listii| begins, +so within \refmmz{at begin memoization}.\footnote{Another generally good + location for such redefinitions is among the auto-options of |listii|. We + could include an \refmmz{auto}\cs{label}\marg{...} there, or a |/utils/exec| + with \refcmd{AdviceSetup}. However, in this particular case this would be + wasteful, as it would be applied regardless of whether memoization will take + place or not, whereas we only need the redefined \cs{label} when memoizing.} +However, we do not redefine \cs{label} directly, as Memoize advises this +control sequence out of the box (see +section~\ref{sec:tut:automemoization-details} for details). What we redefine +is the command which the advising framework executes instead of \cs{label} --- +its so-called \refmmzauto{outer handler} --- and we do this by calling +\refcmd{AdviceSetup}, the low-level variant of the familiar key +\refmmz{auto}.\footnote{We could have also used \refmmz{auto}, but we don't, + because (a) \refcmd{AdviceSetup} is faster, (b) it is easier to prepend + material to a handler using the low-level interface, and (c) I wanted to + showcase \refcmd{AdviceSetup}.} The first argument of \refcmd{AdviceSetup} +is the installation path (|/mmz|), the second one the command or environment we +are submitting to the framework (\cs{label}), and the third one the setup +\emph{code} --- here lies the biggest difference between \refmmz{auto} and +\refcmd{AdviceSetup}: the former expects a keylist, and the latter \hologo{TeX} +code which directly manipulates settings macros like +\refcmd{AdviceOuterHandler} (for the full list, see +section~\ref{sec:tut:automemoization-details} or~\ref{sec:ref:advice}). + +\tcbinputexample[.tex][.c6]{listing only, float, + attachment name=\examplename auto.tex, +} + +Within \refcmd{AdviceSetup}, we prefix (using macro \cs{preto} of package +\pkg{etoolbox}) the original outer handler \refcmd{AdviceOuterHandler} by code +which causes \cs{outerlabeltocontext} to be executed at the end of memoization +(by globally appending this macro to \refcmd{mmzAtEndMemoizationExtra}; +``Extra'' because we're appending during memoization). It is +\cs{outerlabeltocontext} which then appends \refcmd{@currentlabel} to the +context (\refcmd{mmzContextExtra}; again, ``Extra'' because we're appending +during memoization),\footnote{As a courtesy, we clear out macro + \cs{outerlabeltocontext} once it did it's job, so that multiple \cs{label}s + do not include multiple \cs{@currentlabel}s into the context. But the code + would work even without this addendum, can you see why?} and it is crucial +that this happens at the end of memoization rather than when \cs{label} is +executed. When \cs{label} is executed, we're inside an inner item, and +\cs{@currentlabel} refers to that item, while at the end of memoization, the +value of this macro equals the value at the beginning of memoization, namely +the label of the outer item. While the outer list remains unshuffled, the +value of \refcmd{@currentlabel} that contributes to the context MD5 sum during +the utilization attempt will therefore match the value which contributed to the +context MD5 sum during memoization, resulting in matching MD5 sums and +therefore in actual utilization of the extern; once the outer list is shuffled, +this will cease to be the case and the extern will be recompiled. + + +\subsubsection{The Beamer support explained} +\label{sec:per-overlay} + +The implementation of \refmmz{per overlay}, which makes memoization sensitive +to Beamer overlays, provides an example of a complex interaction between +various components of memoization. At the core, the Beamer support works by +adjusting the context, but we will also have the occasion to observe the +free-form part of the c-memo, add a bit of extra code to the cc-memo, and +deploy several memoization hooks. We will show the complete Beamer support +code later on; let us build our understanding of that code step by step. +(Before you read on, you might want to refresh your memory about the \pkg{beamer} +example from section~\ref{sec:tut:beamer}, as we will refer to it in the +present section.) + +The core idea behind \refmmz{per overlay} is to append the current beamer +overlay number to the context:\cprotect\footnote{As we saw in + section~\ref{sec:tut:beamer}, it is convenient to execute \refmmz{per + overlay} inside memoized code. But remember, from + section~\ref{sec:cross-referencing}, that when \refmmz{context} is executed + from within the memoized code, its argument winds up in the c-memo. As the + c-memo is processed under the normal category code regime, where |@| is not a + letter, we have to access |\beamer@overlaynumber| using the |\csname + ... \endcsname| construct.} \refmmz{context}|={overlay=\csname + beamer@overlaynumber\endcsname}|. This makes Memoize produce a separate +extern for each overlay. However, only the first of these externs will get +utilized on subsequent compilations, in general at least. Even worse, we will +lose each frame whose creation is driven solely by the memoized code. We will +lose the second overlay in our example (i.e.\ in the \pkg{beamer} example from +section~\ref{sec:tut:beamer}) as the second overlay was only created because +Beamer encountered |only={2}{...}| (resolving to |\only<2>{...}| under the +hood) inside the picture code; once we utilize the extern instead of compiling +the picture on the first overlay, the |\only| command is not executed anymore, +so Beamer thinks it is done with the frame. + +As the compilation of our picture is substituted by utilization of its +cc-memos, we have to somehow drive the creation of the necessary overlays from +these files. An easy way to achieve this is to furnish them with a dummy +|\only<|\meta{final overlay number}|>{}|,\footnote{`The final overlay' here + should be understood as relative to our memoized picture, i.e.\ as the final + overlay containing the memoized picture.}\footnote{Actually, putting this + \cs{only} command only into the first cc-memo would suffice, but would be + harder to implement.} but there is a problem: the final overlay number is +unknown when we're memoizing our picture --- it is unknown even when we're +memoizing the picture on final overlay itself (we simply don't know yet that +this overlay will end up being the final one), let alone during the memoization +on the first overlay. + +The solution exploits the fact that \emph{the c-memo is rewritten at each + memoization}: at each memoization of our picture, we store the the +\emph{current} overlay number to the c-memo; after all memoizations, the c-memo +will thus contain the number of the \emph{final} overlay containing our +memoized picture. To access this number from the cc-memo, we store it as a +macro definition, and then use the defined macro, |\mmzBeamerOverlays|, in the +overlay specification of the dummy |\only|. Below, you can see all this in +code, as the argument to \refmmz{at begin memoization}. + +\ExampleName{per-overlay-v1} +\makeexample{\examplename.sty} +\tcbinputexample[.sty]{ + listing only, + title={The implementation of \refmmz{per overlay} (first attempt)}, +} + +A couple of remarks are in order here. First, the definition of +|\mmzBeamerOverlays| in the c-memo is global, because it will be accessed from +the cc-memo, but the cc-memo is input after closing the memoize group (which +the c-memo \emph{is} processed in). Second, using \refmmz{at begin + memoization} makes it possible to use \refmmz{per overlay} both outside and +during memoization: if \refmmz{at begin memoization} is executed outside +memoization, its argument is (locally) stored into hook +\refcmd{mmzAtBeginMemoization}, to be called at the beginning of each +memoization; if the key executed outside memoization, the argument is executed +immediately. Third, the Memoize keys in the definition of \refmmz{per overlay} +are prefixed with \code{\refkeypath{/mmz}/}, so that this key can be called from +\pkg{pgfkeys} option lists of other packages, for example the option list of +the \env{tikzpicture} environment, as shown in the example in +section~\ref{sec:tut:beamer}. + +I used the above version of \refmmz{per overlay} for quite a while. In +general, it worked as I expected, but there were glitches. Occasionally, the +picture would appear on the wrong overlay, or I would get an extra overlay, or +perhaps lose an overlay. Eventually, I figured out this happens when I play +with the overlay structure of the frame: when I add or remove a \cs{pause} or +similar. In hindsight, it is easy to see what was happening. Once the picture +is memoized, it is fixed, forever, which extern will appear on which overlay. +I cannot expect the extern--overlay correlation to change just because I added +a \cs{pause} in front of the picture. Furthermore, the number of overlays the +memos will drive to be created is fixed as well. If I memoize the picture +while it follows a \cs{pause}, and the picture creates 10 overlays, the c-memo +will define |\mmzBeamerOverlays| to 11. So what, if I then remove that +\cs{pause}! The c-memo will still define |\mmzBeamerOverlays| to 11, and drive +the creation of 11 overlays --- one too many. + +By now, the road ahead is probably clear --- we put the |beamerpauses| counter +into the context --- but we will see there are still obstacles on the way. The +issue is that the context is evaluated at the \emph{end} of memoization (so +that those cross-references from section~\ref{sec:cross-referencing} actually +get into it). However, the memoized code might contain a \cs{pause} or similar +itself, and change the value of |beamerpauses|. For one, this means that we +have to write down the changed value of |beamerpauses| into the cc-memo; below, +we do this using key \refmmz{at end memoization} (the code given code to this +key is executed after the driver but before Memoize writes down the memos and +ships out the extern pages; the key itself may be executed either before or +during memoization). Furthermore, if the memoized code changes the value of +|beamerpauses|, the value of |beamerpauses| at the attempted utilization, which +would nicely match the value from the start of memoization, will never match +the changed, final value from the end of memoization, in effect preventing our +hard-won externs from ever getting utilized. + +We therefore have to invent a way to get the memoization-\emph{initial} value +of |beamerpauses| into the context.\footnote{This imposes a requirement on the + in-code usage of \refmmz{per overlay}, namely, that it should be executed + prior to any changes of |beamerpauses|.} Fine, we store it in a +macro,\footnote{We must define \cs{mmzBeamerPauses} globally, because + \refmmz{per overlay} can be arbitrarily deeply embedded in the memoized + code.} |\mmzBeamerPauses|, when we start the memoization (\refmmz{at begin + memoization}), and put |\mmzBeamerPauses| rather than |beamerpauses| into the +context. Will this work? Not yet, because |\mmzBeamerPauses| is undefined at +utilization. We need to set up the context expression so that it will expand +to the value of |\mmzBeamerPauses| at (the end of) memoization, and to the +value of |beamerpauses| at utilization. This leads to the +|pauses=\ifmemoizing...| part of the context expression in (the final version + of) the Beamer support code below. + +\medskip % manual, to avoid an orphan + +\ExampleName{per-overlay} +\makeexcerpt{per-overlay} +\tcbinputexample[.tex][.excerpt]{% + listing only, one file, + left=0pt, % manual indentation gobble + title={The implementation of \refmmz{per overlay}}, +} + +Are we done? Almost. The final issue is that once we have introduced support +for pauses, we have to relativize |\mmzBeamerOverlays| (the final overlay +number) to |beamerpause|. So instead of a simple |\gdef\mmzBeamerOverlays| in +the first version, we define |\mmzSetBeamerOverlays|\nohyphen\marg{beamer + pauses}\marg{final overlay number}, which sets |\mmzBeamerOverlays| only if +\meta{beamer pauses} argument matches the value of |beamerpauses| (at its +invocation in the c-memo). Well, the macro has some other housekeeping to do +as well: it is self-replicating, so that during potential memoization, the +|\mmzBeamerOverlays| values belonging to non-current |beamerpauses| values get +rewritten into the c-memo.\footnote{As replication should only occur during + memoization (actually, it \emph{can} only occur then, anyway), the + instruction to append to the c-memo is appended to the + \cs{mmzAtBeginMemoization} hook (the low-level interface to \refmmz{at begin + memoization}). Note that the assignment to this hook must be local (once + local, always local), and it \emph{can} be local because of a little + implementation detail: while the c-memo is processed in the memoize + \hologo{TeX} group, we don't open an additional group to process it; so the + local effects from c-memo will persist into memoization (but not into + utilization, because remember that the memoize group is closed before + inputting the cc-memo).} (Fine, there is another fine detail, regarding +anti-pollution: the macro also ensures that, relative to \marg{beamer pauses}, +only the instance with the greatest \meta{final overlay number} is replicated.) + +We are now truly done, and we can look at the final result, the c-memo and the +cc-memo belonging to the extern on the first overlay of the example from +section~\ref{sec:tut:beamer}. Specifically, look at the |\mmzSetBeamerOverlays +{1}{2}|, which says that the extern chain started when |beamerpauses| equals 1 +should continue up to overlay 2, and at the (expanded) context included at the +end of the cc-memo, courtesy of \refmmz{include context in ccmemo}, where you +can see that the cc-memo will be used when on the first overlay (|overlay=1|) +when preceded by no \cs{pause} command (|pauses=1|). + +\begingroup +\relaxmmzcommands +\def\mmzNewCMemo#1{% fetch the first c-memo filename + \def\mycmemo{#1}% +} +\def\mmzNewCCMemo#1{% fetch the first cc-memo filename + \def\myccmemo{#1}% + \endinput +} +\input{\exampledir beamer.mmz.c1} +\sed{% + s/~//g; + s/\cmd{mmzSetBeamerOverlays} \marg\marg/~\0~/; +}{\exampledir\mycmemo} +\sed{% + s/~//g; + s/overlay=[0-9]*/~\0~/; + s/pauses=[0-9]*/~\0~/; + s/\(\cmd{quitvmode}\) \(\cmd{only}\)/\1\n\2/; + s/\rbrace\(\cmd{mmzIncludeExtern}\)/\rbrace\percentchar\n\1/; + s/\rbrace\(\cmd{setcounter}\)/\rbrace\percentchar\n\1/; + s/\(\marg\marg\marg\)\(\marg\marg\marg\marg\marg\)/\1\space\2/; +}{\exampledir\myccmemo} + +\tcbinputlisting{ + listing only, + listing file=\exampledir\mycmemo, + example title=\mycmemo, +} + +\tcbinputlisting{ + listing only, + listing file=\exampledir\myccmemo, + example title=\myccmemo, +} +\endgroup + + +\subsection{Record files} +\label{sec:record-files} + +We have seen that externalization is a two-step process in Memoize: as it is +impossible for \hologo{TeX} to create multiple PDFs during a single +compilation, the externs are first dumped into the document PDF as special +extern pages, and only later extracted from the main document into separate PDF +files. But extraction requires a complete PDF, which is unavailable even at +the very end of the compilation which produces the externs. The externs can +therefore only be extracted \emph{after} that compilation (either before or at +the beginning of the next one), and this necessitates some form of +communication whereby the memoization step informs the extraction step which +pages should be extracted from the document PDF and into which (PDF) files they +should be stored. This communication is implemented through auxiliary files +called \emph{record files}. + +\subsubsection{The \texttt{.mmz} file} +\label{sec:.mmz} + +By default, Memoize records the information needed for the extraction in a file +named \meta{document name}\dmmz,\footnote{For \hologo{TeX}perts: the + \meta{document name} is of course the expansion of \cs{jobname}.} henceforth +a \dmmz file. In fact, this file contains more than information on externs +created during the last compilation: it records which memos and externs were +either used or created during the compilation. The full information contained +in the \dmmz file is used by the clean-up script \refscript{memoize-clean.pl} +to safely remove stale memos and externs. Let us take a look at the \dmmz file +produced by the titlepage illustration. In fact, we have two versions of this +file, as it changes upon the second compilation. + +\ExampleName{titlepage} +\makeexample{\examplename.pdf N=2} +\sed{% + s/~//g; + s/\cmd{mmzNewExtern}/~\0~/; +}{\examplepath.mmz.c1} +\tcbinputexample[.mmz][.c1]{ + listing only, one file, no attachment, float, + after title pre={\ (after the first compilation)}, + listing options app={breakatwhitespace=false,prebreak=\coloredpercentchar}, +} +\tcbinputexample[.mmz][.c2]{ + listing only, one file, no attachment, float, + after title pre={\ (after subsequent compilations)}, + listing options app={breakatwhitespace=false,prebreak=\coloredpercentchar}, +} + +As you can see, the \dmmz file takes the form of a \hologo{TeX} script (the +format was chosen because it facilitated the implementation of the internally +triggered \hologo{TeX}-based extraction). The crucial lines in this file, and +the only lines used by the extraction script, occur in the first version of the +file: they contain command \refcmd{mmzNewExtern}, which informs the extraction +script that it should extract the document page given by the second argument +into the extern file given by the first argument.\footnote{If you look at the + \dmmz file after extracting the externs using \refscript{memoize-extract.pl} + without the \refscript{memoize-extract.pl--keep} option, you will find that + the \refcmd{mmzNewExtern} commands are commented out; this is to prevent + multiple extractions (even if they are harmless).} (The following two +arguments provide the expected width and height of the extern; the +extraction script may check whether the extern size conforms to these +expectations, but this is not crucial, as the extern size is checked every time +it is included anyway.) + +A \dmmz file also contains a record of the memos (both c-memos and cc-memos) +created in the last compilation; this information is provided by the sole +argument of commands \refcmd{mmzNewCMemo} and \refcmd{mmzNewCCMemo}. And once +memos and externs get used in subsequent compilations, the \dmmz file will +reflect this with \refcmd{mmzUsedCMemo}, \refcmd{mmzUsedCCMemo} and +\refcmd{mmzUsedExtern}, as shown in the second version of the file above. + +Finally, both versions illustrate that a \dmmz file always begins with command +\refcmd{mmzPrefix} and ends with the |\endinput| marker. The argument of +\refcmd{mmzPrefix} is the path prefix to the memo and extern files, as +determined by the invocation of key \refmmz{path} (and its subkeys +\refmmzpath{relative}, \refmmzpath{dir} and \refmmzpath{prefix}). The initial +\refcmd{mmzPrefix} line is written to the \dmmz file at the beginning of the +document, but an additional \refcmd{mmzPrefix} line will occur for every +invocation of \refmmz{path} in the document body. Finally, the |\endinput| +marker signals that the \dmmz file is complete. + +As mentioned above, the full contingent of \dmmz file commands is only used by +the clean-up script \refscript{memoize-clean.pl}. By default, this script +removes all memos and externs with the prefix given by \refcmd{mmzPrefix} +(relative to the directory hosting the \dmmz file) but those listed by any of +\refcmd{mmzNewCMemo}, \refcmd{mmzNewCCMemo}, \refcmd{mmzNewExtern}, +\refcmd{mmzUsedCMemo}, \refcmd{mmzUsedCCMemo} and \refcmd{mmzUsedExtern}. +Furthermore, the clean-up script will cowardly refuse to delete anything if the +\dmmz file does not end with |\endinput|, as this means that the compilation +ended prematurely and that the \dmmz file might not mention all memos and +externs actually used in the document. If given option +\refscript{memoize-clean.pl--all}, the clean-up script removes even the memos +and externs mentioned in the \dmmz file, and as this option is intended to +bring Memoize to a clean slate after any ``disasters,'' the +\refscript{memoize-clean.pl--all} mode also ignores the potential absence of +the |\endinput| marker. Incidentally, the \refscript{memoize-clean.pl--all} +mode is also the raison d'être for \refcmd{mmzPrefix}: while the prefix is +usually recognizable from \refcmd{mmzNewCMemo} and friends, these commands +might not make it into the \dmmz file in a fatally failed compilation, but it +is precisely such compilations that could occasionally require the full +clean-up. + +\subsubsection{Defining a new record type} +\label{sec:new-record-file} + +The \dmmz file is not the only kind of a record file that can be produced by +Memoize. Out of the box, it can also write down the extraction instructions +into a makefile or a shell script. These are useful on systems which have to +employ the \hologo{TeX}-based extraction but cannot trigger it internally. +Running the \hologo{TeX}-based extraction manually would be painful, as it must +be done on extern-by-extern basis, so Memoize offers to automate the extraction +by a makefile or a shell script; here, the record file is named +\code{memoize-extract.\meta{document name}.\meta{record type}} by default, +where \meta{record type} is either \refmmz{record=makefile}, +\refmmz{record=sh} (for shell scripts on Linux), or \refmmz{record=bat} (for +shell scripts on Windows). + +To turn on recording of an alternate record type, use key +\refmmz{record}|=|\meta{record type}. Memoize can record any number of files +simultaneously, so saying \code{\refmmz{record}=\refmmz{record=sh}} will +produce the shell script alongside \dmmz (Memoize internally executes +\code{\refmmz{record}=\refmmz{record=mmz}} to start recording the \dmmz file); +this should not be a problem, but if you really want to disable the \dmmz file +production, you can say \refmmz{no record}. + +The predefined record types are defined through a generic system open to the +user. To define an additional record type, one needs to define, using +\pkg{pgfkeys}, the relevant hooks of the form \refkeypath{/mmz/record/record + type}|/|\meta{hook}. The following \meta{hook}s can be defined (the hooks +not needed for the record file type may be left undefined): +\begin{itemize} +\item Key \refmmz{record/record type/begin} will be executed at the beginning + of the document; it will receive no argument. Use it to open the record + file. +\item Key \refmmz{record/record type/end} will be executed at the end of the + document; it will receive no argument. Use it to close the record file. +\item Key \refmmz{record/record type/prefix} will be executed at the end of the + document and at every invocation of key \refmmz{path} in the document body; + it will receive a single argument, the path prefix determined by key + \refmmz{path}. +\item Keys \refmmz{record/record type/new cmemo}, \refmmz{record/record + type/used cmemo}, \refmmz{record/record type/new ccmemo} and + \refmmz{record/record type/used ccmemo} will be executed after creating or + inputting a memo; they will receive a single argument, the full path to the + memo. +\item Key \refmmz{record/record type/used extern} will be executed after an + extern was included into the document; it will receive a single argument, the + full path to the extern. +\item Key \refmmz{record/record type/new extern} will be executed after + creating creating an extern, more precisely at the end of memoization, right + after shipping out the extern page. It will receive a single argument, the + full path to the extern, but additionally, Memoize prepares the following + macros: + \begin{itemize} + \item \refcmd{ne:externbasepath} holds the full path to the extern, but (unlike |#1|) + without the |.pdf| suffix; + \item \refcmd{ne:pagenumber} holds the ``physical'' page number of the extern page in + the document (the numbering starts by $1$); + \item \refcmd{ne:expectedwidth} and \refcmd{ne:expectedheight} hold the width and + the height (total height, i.e.\ the sum of \hologo{TeX}'s height and depth) + of the extern page. + \end{itemize} +\end{itemize} + +Below, we present two simple examples of a record file. The first type simply +records the names of all memos and externs used or created by Memoize; the +resulting file could be included by |.gitignore| to have +\hreftt{https://git-scm.com}{git} automatically ignore all files produced by +Memoize. The second type lists the new externs, each preceded by its page +number in the |.pdf|; this file could be fed to a custom extern extraction +tool. + +\ExampleName{record-files} +\makeexample{\examplename.tex.c1} +\tcbinputexample{listing only} + +\ExampleName{record-extern-pages} +\makeexample{\examplename.tex.c1} +\tcbinputexample{listing only} + +Finally, note that (unlike memos and externs) record files are auxiliary files +and may be deleted at any time after the extraction of the externs produced in +the final compilation --- actually, even if these externs were not yet +extracted, deleting the record file(s) will merely force their recompilation. + + +\subsection{The memoization process} +\label{sec:memoization-drivers} + +We now turn to the memoization process itself. The job of memoization is to, +while compiling the given code in a regular fashion, prepare the cc-memo +(which, when it is input, will replicate the effect of the given code), +alongside any externs that the cc-memo will include (these hold the typeset +material to be replicated). Clearly, merely compiling the code cannot have +this effect (unless that code was written specifically to support memoization; +more on this later), and this is why the memoized code is typically wrapped by +a \emph{memoization driver}, which can be set using key \Emph{\refmmz{driver}}. +We'll inspect the default memoization driver, \refcmd{mmzSingleExternDriver}, +in the first subsection, and we will learn how to write specialized drivers in +the remaining subsections. But first, let us say some words about a +grouping-related \hologo{TeX}nical detail we need to take care about during +memoization. + +During memoization, we have to collect certain information, like build the +contents of the cc-memo. Some of that information might be contributed by the +memoized code itself. For example, a \cs{label} ``adds itself'' to the cc-memo +(by appending to token register \refcmd{mmzCCMemo}); a +\refkey{/tikz/remember picture} aborts memoization (by issuing +\refcmd{mmzAbort}); etc. The issue is that the memoized code might open any +number of \hologo{TeX} groups; we have no idea how deeply embedded the +\cs{label} or \refkey{/tikz/remember picture} might be. Therefore, we have +to collect all the information about the ongoing memoization \emph{globally}: +all assignments to \refcmd{mmzCCMemo} must be global; \refcmd{mmzAbort} sets +the underlying conditional globally; etc. (Clearly, all these global variables +are initialized at the start of memoization.) + +This was the easy part. An additional complication arises with some options +which may be set either outside memoization, or during this process. For +example, you can append the font size to the context expression in the preamble +(see section~\ref{sec:cross-referencing}), so that the externs will be +automatically recompiled when the font size changes, and clearly, this context +adjustment should respect \hologo{TeX} grouping; but a \cs{ref} or some other +cross-referencing command in the memoized code needs to append to the context +as well, and as this \cs{ref} occurs \emph{within} the memoized code, the +assignment must be global, as explained above. + +Mixing the local and global assignments to the token register +\refcmd{mmzContext}, which holds the (in the actual implementation, local) +context expression, will not do. For one, we \emph{do} want to restore the +pre-memoization context expression after we have memoized the code, and +furthermore, mixing local and global assignments to the same variable is not +recommended for save stack reasons anyway. + +Memoize addresses this issue by having \emph{two} context registers, +\refcmd{mmzContext} and \refcmd{mmzContextExtra} --- when computing the context +MD5 sum (which happens at the end of memoization), the two registers are +concatenated (the local one comes first). A package writer should know when to +use which register, and how. Outside memoization, one should assign to +\refcmd{mmzContext} --- \emph{locally}. During memoization, one should assign +to \refcmd{mmzContextExtra} --- \emph{globally}. The user interface key +\refmmz{context} respects this requirement automatically: it locally appends to +\refcmd{mmzContext} outside memoization, and it globally appends to +\refcmd{mmzContextExtra} during memoization. (The same idea is applied to the +post-memoization hooks \refmmz{at end memoization} and \refmmz{after + memoization}.) + + +\subsubsection{The default memoization driver} +\label{sec:memoization-driver-default} + +The default memoization driver, \refcmd{mmzSingleExternDriver}, produces +exactly one extern, which contains whatever is typeset by the code submitted to +memoization. The driver compiles the code into a horizontal or vertical box +depending on the value of key \refmmz{capture}. Let us look at the definition +of the driver line by line: + +\makeexcerpt{single-extern-driver} +\tcbinputexample[.tex][.excerpt]{% + listing only, one file, + listing options app={numbers=left, numberstyle=\tiny, numbersep=0.5em}, + title=The default memoization driver, + float, +} + +\begin{enumerate} +\item Macro \refcmd{mmzSingleExternDriver} (and in fact any memoization + driver) takes a single argument, the code to compile. Memoize will call the + driver with the code given as the second argument to \refcmd{Memoize}, but + wrapped in a macro which re-reads it using \refcmd{scantokens} when + \refmmz{verbatim} is in effect. +\item If we're capturing into a horizontal box + (\refmmz{capture}|=|\refmmz{capture=hbox}), we put \refcmd{quitvmode} into + the cc-memo --- putting it to the very beginning should make sure that any + replicated \cs{label} and \cs{index} commands refer to the correct page. +\item We compile the given code, storing the typeset material into a box + (above, a temporary box called |\mmz@box|). |\mmz@capture| resolves into a + box construction command, depending on the value \refmmz{capture}. +\item Macro \refcmd{mmzExternalizeBox} instructs Memoize to externalize the box + given as its first argument. However, this macro does not directly produce + an extern page or write any instructions into the cc-memo; the road to this + final destination is indirect. \refcmd{mmzExternalizeBox} has two effects. + First, it adds the contents of the given box (above, |\mmz@box|) to an + internal box dedicated to holding all the externs produced in this + memoization (the contents of |\mmz@box| remain as they are) --- it is only at + the end of memoization that the contents of this internal box are shipped off + to extern pages. Second, \refcmd{mmzExternalizeBox} produces the code which + will include the extern into the document on subsequent compilations (this + will be a call to \refcmd{mmzIncludeExtern}, potentially prefixed by + \refcmd{quitvmode}; see section~\ref{sec:cc-memos} for details). This code + is stored into the token register gives as the second argument (above, + |\mmz@temptoks|), and it is the responsibility of the driver to include it + into the cc-memo. (In the interest of full disclosure, + \refcmd{mmzExternalizeBox} also updates the list of externs produced in this + memoization. At the end of memoization, this list is written to the + beginning of the cc-memo, resulting in the \refcmd{mmzResource} lines + preceding the \refcmd{mmzMemo} marker.) +\item The construction of the cc-memo is indirect as well. In the third line + of the definition, we globally append the extern-inclusion code residing in + |\mmz@temptoks| to token register \refcmd{mmzCCMemo}. At the end of + memoization, the contents of \refcmd{mmzCCMemo} are written into the cc-memo, + preceded by the \refcmd{mmzMemo} marker. +\item We put the typeset material into the document, again preceded by + \refcmd{quitvmode} when capturing in a horizontal box. +\end{enumerate} + +You might wonder why the construction of the extern pages and the cc-memo (and +actually, of the c-memo as well) is indirect, as described above. +\begin{itemize} +\item For one, the indirect construction facilitates potential abortion of + memoization (see section~\ref{sec:tut:good-to-know}). With the indirect route, + aborting is easy --- as nothing was permanently written anywhere yet, Memoize + simply skips the final part of the process, where extern boxes are shipped + into extern pages and the memo registers written into memo files --- and also + clean: if \refcmd{mmzExternalizeBox} immediately shipped out the extern + pages, these pages would remain in the document even in the case of abortion. +\item Even more importantly, the cc-memo filename contains the \meta{context + md5sum} (see section~\ref{sec:cc-memos}), but the context expression is not + yet fully known when memoization starts --- remember (from + section~\ref{sec:cross-referencing}) that a \cs{ref} in the memoized code will + update the context! The cc-memo can therefore only be opened at the end of + memoization, which necessitates a buffer (i.e.\ the \refcmd{mmzCCMemo} + register) for storing its contents during memoization. +\end{itemize} + + +\subsubsection{Pure memoization} +\label{sec:pure-memoization} + +The default memoization driver discussed above is really an externalization +driver: it produces a single extern. We now move to examples of drivers with +other functions, starting with a pure memoization driver, which does not +externalize any typeset output --- simply because it does not call +\refcmd{mmzExternalizeBox} at any point --- but rather remembers the result +of a (pgfmath) computation (let's pretend that the computation is +time-consuming). + +\ExampleName{pgfmathparse} +\makeexample{\examplename.pdf} +\begingroup +\relaxmmzcommands +\def\mmzNewCCMemo#1{% fetch the first cc-memo filename + \def\myccmemo{#1}% + \endinput +} +\input{\examplepath.mmz.c1} +\tcbinputexample{% + comment={% + \tcbinputlisting{ + listing only, width=.4\linewidth, nobeforeafter, + listing file=\exampledir\myccmemo, + example title, title=the cc-memo, + }\quad + \includeexamplepdf[document page]{page=1} + }, +} +\endgroup + +Command \refcmd{mmz} above memoizes its mandatory argument with the memoization +\refmmz{driver} set to the previously defined macro \cs{mmzPgfmathDriver}. +Just as the default driver above, \cs{mmzPgfmathDriver} first executes the +given code. However, there is no need to do this in the context of a +|\setbox|, as the memoized code, which is obviously expected to consist of a +single \cs{pgfmathparse} call, does not typeset anything: \cs{pgfmathparse} +evaluates the given expression and stores the result into macro +\cs{pgfmathresult}. The driver has two jobs: first, it must store this result +into the cc-memo, to be utilized in subsequent compilations; second, because +the assignment to \cs{pgfmathresult} (within \cs{pgfmathparse}) is local, the +driver also needs to somehow smuggle the result out of the \cs{endgroup} issued +by \refcmd{Memoize} and thereby make it into the following code (the final line +of the example, which typesets the equation). Both jobs are easy enough: the +expansion of |\def\noexpand\pgfmathresult{\pgfmathresult}| (in this case, +|\def\pgfmathresult{42.0}|) is (globally) appended both to the token register +\refcmd{mmzCCMemo}, which Memoize later writes into the cc-memo, and to the +macro underlying the \refmmz{after memoization} hook, whose contents are +executed \emph{after} closing the memoization group. + +Let us consider an alternative implementation of the same goal of memoizing the +result of a pgfmath computation, showcasing a couple of useful tricks. + +\ExampleName{pgfmathparse-embellished} +\makeexample{\examplename.pdf} +\tcbinputexample{listing only} + +For one, this ``embellished'' example reminds us that we can list the +\refmmz{driver} key among the auto-options (even if I don't really recommend +automemoizing \cs{pgfmathparse}). But even more importantly, the example shows +that the driver consist of more than a single control sequence; the only +requirement is that the given driver code will consume the memoized code. In +this example, we have developed a generic smuggling driver and applied it to +\cs{pgfmathresult} in particular --- \cs{pgfmathresult} will become the first +argument of \cs{mmzSmuggleOneDriver}, and the memoized code will become its +second argument. + +In the first version of the example, we have appended the same code to macro +\refcmd{mmzCCMemo} and to macro \refcmd{mmzAfterMemoizationExtra} --- no +surprise here, as we want the effect of memoization and utilization to be the +same. In the embellished version, we advertise another way to achieve the same +effect, a way which might be useful for complicated drivers: we simply smuggle +out the entire cc-memo. The idea works even when memoization procudes externs; +in that case, however, the driver also has to say \cs{mmzkeepexternstrue} --- +conditional \refcmd{ifmmzkeepexterns} decides whether Memoize keeps the externs +around, in memory, even after shipping them out (but they are always gone at +the start of the next memoization). + +Finally, remember that the default context expression contains the +\refmmz{padding} values. However, these really have no place in the context +expression of some purely memoized code. We have therefore emptied out the +context expression using \refmmz{clear context}. + + +\subsubsection{Multiple externs per memo} +\label{sec:memoization-multiple-externs} + +In the next example, we show how to produce multiple externs for a single piece +of memoized code. The usage case I find most appealing is breaking the typeset +material, like a table, across pages --- but of course, table-breaking is too +complicated an example, so we illustrate the idea by defining command +|\countdown|, which counts down from the given number, typesetting each number +into its own line. Clearly, if we were to externalize a call to this command +using the default memoization driver, page breaking would stop working, as the +entire countdown would be seen as a single, unbreakable box. To externalize it +properly, the chunks of the countdown that should appear on separate pages must +be externalized into separate externs, as shown below. + +\ExampleName{countdown} +\makeexample{\examplename.sty N=2} +\makeexample{\examplename.pdf} +\tcbinputexample{% + listing options app={ + basicstyle=\ttfamily\footnotesize, + }, + middle=1mm, bottom=0mm, + comment={\centering + \relaxmmzcommands + \def\mmzUsedExtern##1{% + \saveexpandmode\fullexpandarg + \StrGobbleRight{##1}{4}[\mytemp]% + \StrRight{\mytemp}{5}[\mytemp]% + \restoreexpandmode + \relaxmmzcommands + }% + \def\mmzNewExtern##1##2##3##4##5{\mmzUsedExtern{##1}} + \def\myexternname##1{extern \dots\texttt{\mytemp##1.pdf}}% + \input{\examplepath.mmz}% + \begin{tabular}{ccc} + \includeexamplepdf[extern page,title/.expand once=\myexternname{}] + {page=2,trim=1in 1in 1in 1in,scale=0.333}& + \includeexamplepdf[extern page,title/.expand once=\myexternname{-1}] + {page=3,trim=1in 1in 1in 1in,scale=0.333}& + \includeexamplepdf[extern page,title/.expand once=\myexternname{-2}] + {page=4,trim=1in 1in 1in 1in,scale=0.333}\\ + \includeexamplepdf[document page]{page=1,scale=0.333}& + \includeexamplepdf[document page]{page=5,scale=0.333}& + \includeexamplepdf[document page]{page=6,scale=0.333} + \end{tabular} + }, +} + +To achieve this, we will have to integrate the memoization driver into the very +code of |\countdown|. This approach contrasts sharply with the standard +memoization driver, which is simply wrapped around the memoized code. Let's +say we have implemented a non-memoization-aware variant of |\countdown| as a +loop which gathers the countdown lines into a vertical box, and periodically, +when this box holds all the material that will fit onto the page, places it +into the main vertical list (i.e.\ on the page).\footnote{Of course, + implementing \cs{countdown} this way would be idiotic; a sane implementation + would simply spit out the countdown lines one by one, and let \hologo{TeX} + deal with page-breaking. However, remember that we are pretending that we + are typesetting (and page-breaking) some complex material, like a table; in + such a case, the loop outlined in the main text would make perfect sense.} +To have this command support memoization, we have to externalize our box every +time we're placing it into the main vertical list. This is precisely what we +do in the definition of |\countdowntypeset| below:\footnote{We omit the + definition of the core algorithm of \cs{countdown} in the listing, because it + is mostly irrelevant for our discussion, and only show the + memoization-related code.} the final line of this macro adds the material to +the main vertical list, and the preceding lines externalize it (the two lines +inside \cs{ifmemoizingcountdown} should be familiar from the definition of the +standard memoization driver; we'll explain about the conditional below); note +that Memoize automatically deals with the fact that our box is vertical. As a +result of having our memoization driver integrated into the loop of the core +command, we can create as many externs as necessary, complete with the code in +the cc-memo for including each and every one of them on subsequent +compilations. Each extern eventually makes it into its own extern file, and +note that the filenames of the non-first externs have their sequential number +(we start counting at $0$) appended to the basename, as shown in the +example.\cprotect\footnote{The \refmmz{auto} declaration of |\countdown| adds + some relevant parameters to the context (see + section~\ref{sec:cross-referencing}). The countdown will be recompiled upon + change of either the font size (|f@size|), the text height (|\textheight|), + or the height of the material in the main vertical list collected so far + (|\pagetotal|). The |\pagetotal| parameter is especially important; + including it makes sure that the countdown will be recompiled when it is + pushed up or down the page. Also note that we want the context to record the + value of |\pagetotal| when the (automemoized) |\countdown| is encountered + (rather than at the end of memoization), so we expand it when applying the + auto-options.} + +\tcbinputexample[.sty]{listing only, after title pre={\ (version 1)}} + +Of course, the chunks of the countdown should only be externalized when the +code is actually being memoized, and not, say, when Memoize is disabled or +performing regular compilation. (Note that this is a problem that only affects +integrated drivers and not wrapped drivers such as the default driver.) The +first thought is to detect whether we're undergoing memoization using +conditional \refcmd{ifmemoizing}, which Memoize sets to true at the start of +every memoization. This conditional is used in the run conditions of advice +for \cs{ref} and \cs{label}, the idea being that they should add stuff to the +context (\cs{ref}) and the cc-memo (\cs{label}) only when undergoing +memoization. However, deploying \refcmd{ifmemoizing} in the current example +would not be exactly right. It would work well with the main document as it +is, but it would fail if |\countdown| was called from a piece of code that was +independently submitted to memoization, e.g.\ +\refcmd{mmz}|{\countdown{30}}|.\footnote{Such embedding occurs more often than + you might think. For example, \env{forest} calls \env{tikzpicture} under the + hood, and both environments are automemoized.} In that case, both the +\refcmd{mmz} driver and the |\countdown| integrated driver would get executed, +resulting in the creation (and in subsequent compilations, utilization) of four +externs: first, the |\countdown| driver would externalize each countdown chunk +separately, and then, the \refcmd{mmz} driver would externalize them, all +together, yet again. You can try this out by replacing +\cs{ifmemoizingcountdown} in \cs{countdowntypeset} by \refcmd{ifmemoizing} (and +wrapping the \cs{countdown} call in \cs{mmz}). + +The solution to the \refcmd{ifmemoizing} problem deployed in the example is to +declare a new, \cs{countdown}-specific memoization conditional, and set it to +true in \cs{countdown}'s formal driver, i.e.\ the macro set as the +\refmmz{driver} in the \refmmz{auto} declaration for \cs{countdown}. In fact, +Memoize can do most of this for you: when we write \refmmzauto{integrated + driver}|=countdown|, Memoize creates the countdown-specific memoization +conditional and declares the formal driver which sets this conditional to true; +you only have to access this conditional in your code, and you should do this +using the \hologo{LaTeX}-style conditional \refcmd{IfMemoizing}, as shown +below.\footnote{You shouldn't directly use the plain \hologo{TeX} + countdown-specific conditional created by \refmmzauto{integrated driver} --- + to prevent accidental access, Memoize doesn't actually name it + \cs{ifmemoizingcountdown} --- because this conditional is undefined when + Memoize is not loaded, i.e.\ when only package \refpkg{memoizable} is in + effect. Furthermore, \refcmd{IfMemoizing} addresses a problem faced by + integrated drivers of potentially recursive commands; we will talk about this + in section~\ref{sec:memoization-complex-single-driver}.} + +\tcbinputexample[.sty][.c2]{listing only, after title pre={\ (version 2)}, + attachment name=\examplename-integrated-driver.sty, +} + + +\subsubsection{Driver-based memoizable design} +\label{sec:memoization-complex-single-driver} + +In the previous section, we used the integrated driver approach to produce +memos including multiple externs, but the approach can be useful for one-extern +memos as well, when the extern must be integrated into the document in some +special way. We already discussed such situations in +section~\ref{sec:memoizable-design}, where we suggested to split a +``difficult'' command into the outer command and the inner command, and only +submit the inner command to automemoization. However, the vanilla flavour of +this approach had a negative impact on the user interface to automemoization. +In this section, we will deploy the memoization driver to overcome the issue. + +\ExampleName{poormansbox-driver} +\makeexample{\examplename.sty} +\makeexample{\examplename.pdf N=2} + +Let us revisit the |poormansbox| example from +section~\ref{sec:memoizable-design}. Remember that that environment produced +a potentially framed box of a certain width, surrounded by some pre- and +post-code, and that the issue was that the pre- and the post-code should not be +memoized, but rather executed at every invocation of the command, as it was +primarily intended to put some stretchable vertical space around the box. + +The document source\attachexample\ and the resulting PDF of the example are the +same as in section~\ref{sec:memoizable-design}, so we will not repeat them +here, but jump directly into a revised definition of the environment. We will +retain the core idea from the original implementation: the outer command will +execute the pre- and the post-code, and the inner command will typeset the box. +But unlike in the original implementation, we will not automemoize the inner, +internal command (this was the source of the author's discomfort) but the outer, +user-level command --- and we will equip it with a custom memoization driver. +The major idea here is to have the driver compose a cc-memo which not only +includes the extern, but also executes the outer command. + +\tcbinputexample[.sty]{listing only, float} + +In detail, the implementation (partially shown in the |.sty| listing) is as +follows. The outer command (|\poormansbox@outer|) first applies the options +(|#1|) and then wraps the pre-code (|\pmb@before|) and the post-code +(|\pmb@after|) around some arbitrary code (|#2|). During memoization or +regular compilation, the outer command is invoked through the |poormansbox| +environment, and you can see that in the definition of that environment, the +second argument to |\poormansbox@outer| is a call to the inner command +(|\poormansbox@inner|; this command takes two arguments, the options and the +environment body). During utilization, the outer command is invoked from the +cc-memo,\cprotect\footnote{As you can see, in the cc-memo the outer command is invoked + by |\csuse{poormansbox@outer}|. A straight |\poormansbox@outer| would not + work because we're in the middle of the document where |@| is not a letter, + and including a |\makeatletter| in front of it (and in a group) only works if + \refmmz{direct ccmemo input} was in effect. Under the default, indirect + cc-memo input regime, the core cc-memo is tokenized before |\makeatletter| can + take effect.} and as you can see in the cc-memo listing below, the second +argument to |\poormansbox@outer| there is a call to \refcmd{mmzIncludeExtern}. + +\begingroup +\relaxmmzcommands +\def\mmzNewCCMemo#1{% + \def\mmzNewCCMemo##1{% fetch the second cc-memo filename + \def\myccmemo{##1}% + \endinput + }% +} +\input{\examplepath.mmz.c1} +\sed{% + s/~//g; + s/\(\cmd{csuse} *{poormansbox@outer}\) *\({\nobrace*\marg\nobrace*}\)\({.*}\rbrace\)/~\1~\2~\3~/; + s/\(\marg\marg\marg\)\(\marg\marg\marg\marg\marg\)/\1\space\2/; +}{\exampledir\myccmemo} +\tcbinputlisting{ + listing only, left=1.5mm, right=1mm, + listing options app={basicstyle=\tt\small}, + listing file=\exampledir\myccmemo, + example title, title=the second poor man's box's cc-memo, +}% +\endgroup + +And how does |\poormansbox@outer| get into the cc-memo, which normally only +includes a call to \refcmd{mmzIncludeExtern}, you ask? This is the job of the +memoization driver, which is in this case integrated into the inner command. +The overall shape of the driver is the same as the shape of the standard +driver, discussed in section~\ref{sec:memoization-driver-default}: typeset the +extern material into a box, externalize this box, append the extern-inclusion +code to the cc-memo, and put the extern box into the document. It is the +cc-memo part which interests us right now: unlike the standard driver, we don't +simply append the contents of |\mmz@temptoks|, i.e.\ a +\refcmd{mmzIncludeExtern} call; we rather append a call to +|\poormansbox@outer|, which gets the \refcmd{mmzIncludeExtern} call as its +second argument (and the options as its first argument). + +The core part of the driver, which externalizes the box and appends to the +cc-memo, is embedded inside the true branch of conditional +\refcmd{IfMemoizing}|[1]{pmb}|. We already used this conditional in +section~\ref{sec:memoization-multiple-externs}, but without the optional +argument. Such usage will not work here, because it is not recursion-safe. +Unlike in the \cs{countdown} situation, one |poormansbox| environment can be +embedded in another one (and, in our example, it is). If we deployed +\refcmd{IfMemoizing}|{pmb}| in the inner command, the driver would be executed for +\emph{both} the outer and the inner instance of the environment, whereas it +should really be executed only for the outer instance. + +When used in a recursion-safe way, i.e.\ with the optional argument, +\refcmd{IfMemoizing} first tests whether the auxiliary command-specific +conditional from the previous section is true, and then proceeds to compare the +current group level (\hologo{eTeX}'s \refcmd{currentgrouplevel}) to the group +level at the start of memoization (which Memoize stored in +\refcmd{memoizinggrouplevel}). Only if these group levels match do we know +that we're working on the outer instance of the environment, and that we should +therefore execute the memoization driver. Importantly, though, the two group +levels are compared modulo the offset, given as the optional parameter to +\refcmd{IfMemoizing}: in our example, the offset is 1, because the driver is +located inside the |poormansbox| environment, which opens a group --- note that +0 zero (no offset) is not the default optional parameter; the absence of the +optional parameter indicates that the non-recursion safe method should be +used.\footnote{Even this approach is not completely bullet-proof. It will only + work when the inner instance of the command is guaranteed to occur in an + additional group, i.e.\ when our command opens up a group for any free-form + code. I will assume that situations which require externalization of a + potentially recursive command which, for some reason, cannot open the group + before processing a free-form argument, are rare enough to not warrant a + generic solution here.} + + +\FloatBarrier + + +\subsubsection{Shipout} + +Memoize is a hypocrite: when it is creating extern pages, it uses +\refcmd{primitive}\refcmd{shipout} to bypass the regular shipout routine of the +format, but it is offended if anyone else does that. + +Memoize bypasses the regular shipout because the extern pages should really not +be modified or discarded by a foreign package. But using the primitive +\refcmd{shipout} means that extern shipouts can't be detected by another +package, at all. To facilitate peaceful coexistence with a potential package +which needs to know about our extern pages, we offer public counter +\refcmd{mmzExternPages} holding the number of externs shipped out so far. And +if anyone really needs to \emph{do} something at every extern shipout, they can +always (ab)use \refmmz[show keypath]{record/record type/new extern} as a +post-extern-shipout hook. + +The other side of the story is about Memoize needing to know the ``physical'' +page numbers of its externs in the document PDF --- how else are we to extract +them? Memoize computes these page numbers by adding the values of several +counters: \refcmd{mmzRegularPages}, which holds the number of regular shipouts; +the above-mentioned \refcmd{mmzExternPages}, which holds the number of extern +shipouts; and \refcmd{mmzExtraPages}, which holds the number of other shipouts. +The latter counter should be advanced by a package which, like Memoize, +bypasses the regular shipout routine. + +\hologo{LaTeX} and \hologo{ConTeXt} kindly provide the number of regular +shipouts as publicly a accessible counter, so we define +\refcmd{mmzRegularPages} as synonymous with their +\refcmd{ReadonlyShipoutCounter} and \refcmd{realpageno}. In \hologo{plainTeX}, we +have to hijack the \refcmd{shipout} control sequence and count regular shipouts +ourselves; as we have to hijack it while it still refers to the +\refcmd{shipout} primitive, this format provides another reason for preferring +Memoize to be loaded early. + + +\subsection{Automemoization} +\label{sec:tut:automemoization-details} + +Automemoization is a mechanism that automatically memoizes the result of the +compilation of certain commands and environments. Writing Memoize, I went to +great lengths to make it flexible, yet easy to use. This resulted in +automemoization deploying two specifically developed auxiliary packages: +package Advice, which provides a generic framework for extending the +functionality of selected commands and environments, and package CollArgs, +which provides a command for collection of the arguments conforming to the +given (slightly extended) \pkg{xparse} argument specification. + +This section lists the considerations which went into designing the system, +followed by short tutorials on both auxiliary packages, which include several +examples of how Memoize uses the underlying advising framework. + +\paragraph{(De)activation} +Ideally, all commands and environments where memoization makes sense would +support Memoize (or Memoize would support them) and nothing would ever go +wrong. In this dream world, memoization would be completely transparent to the +author. However, things will go wrong, so at the very least, we need to +offer the author a \emph{simple} way to \emph{selectively} switch automemoization +on and off. This is achieved by keys \refmmz{activate} and +\refmmz{deactivate}. + +\paragraph{Submission} +Of course, there will be commands without official support by either Memoize or +the package which defines them; clearly, at the moment when I write this, all +commands but \cs{tikz}, \env{tikzpicture} and \env{forest} are such. Or, the +author might want to automemoize his or her own command. Ideally, submitting a +new command to automemoization would be as simple as +|memoize=|{\meta{command}}, and for environments, this is in fact achievable, +although the actual interface is +\refmmz{auto}|=|\braces{\meta{environment}}\braces{\refmmzauto{memoize}}. But +simply submitting the name cannot work for commands, because commands are where +we encounter the major \hologo{TeX}nical problem with automemoization: we need +to somehow collect the arguments of the command --- without executing the +command itself. + +\paragraph{Argument collection using CollArgs} +\hologo{TeX} being \hologo{TeX}, automatically determining the scope of a +command in general is just plain impossible. Note that inspecting the +|\meaning| is not enough in general, because the ``real'' and the formal +arguments of a command can, and quite often do, differ wildly. The author (or the +package writer) will need to tell Memoize about the argument structure of the +command. And as there is already a nice and general argument specification on +the market --- I'm obviously referring to the argument specification of package +\pkg{xparse}, which was recently even integrated into the core \hologo{LaTeX} +--- why not use that? Memoize comes with an auxiliary package CollArgs, which +(given the slightly extended \pkg{xparse}-style argument specification) +collects the arguments of a command into a single entity. All the user needs +to write to enable automemoization for a command is thus +\refmmz{auto}|=|\meta{command}\braces{\refmmzauto{memoize}, + \refmmzauto{args}|=|\braces{\meta{argument specification}}}. Even simpler, +when it comes to commands defined by \pkg{xparse}'s \refcmd{NewDocumentCommand} +or friends, writing \refmmz{auto}|=|\meta{command}\braces{\refmmzauto{memoize}} +will suffice, as the argument specification of these commands can be retrieved +by \refcmd{GetDocumentCommandArgSpec}. + +\paragraph{Weird commands} +Not every argument structure can be described using \pkg{xparse}'s argument +specification, a case in point being \refcmd{tikz} with its totally +idiosyncratic syntax --- and if Memoize won't support \cs{tikz}, why have it at +all? The interface to automemoization must be flexible enough to cover even +the craziest commands, and this is why Memoize allows for arbitrary argument +collectors. These are defined by the advanced user or package writer and then +declared to be used for parsing the argument structure of a command by writing +\code{\refmmz{auto}=\marg{command}\bracestt{...,\refmmzauto{collector}=\meta{argument + collector}}}. + +\paragraph{Over and above automemoization: handlers} +The framework facilitating automemoization must cover more than just that. For +one, it sometimes makes sense to automatically \emph{prevent} memoization +during the execution of certain commands (as in the \refmmzauto{nomemoize} +example in section~\ref{sec:tut:redefinitions}). It follows that the action +performed to an invocation of a command should not be fixed. In the advising +framework, implemented by the auxiliary package Advice, we assign each +advised command a handler --- a command which does the real work of +memoizing or whatever. Crucially, the handler and the collector are +independent of each other, allowing a single memoization handler to handle +commands with both standard and non-standard argument structure, and allowing a +single collector to serve either the memoization or the no-memoization handler. + +\paragraph{Over and above automemoization: the outer and the inner handler} +Second, unlike the memoization handler set by \refmmzauto{memoize}, not all +handlers work with the entire argument list of the advised command. Some +handlers don't care about the arguments of the advised command at all: +\refmmzauto{abort} simply aborts memoization whenever the advised command is +executed. Other handlers are only intended to advise a single command or a +small family of commands, and need to inspect specific arguments of the advised +command: for example, \refmmzauto{ref} needs to append the internal +cross-reference macro to the context, and it of course constructs the name of +this macro from the reference key. For all such handlers, it would be plain +wasteful to first collect the arguments and then tear them apart to inspect +them (or not). The advising framework therefore recognizes two kinds of +handlers. The abortion and the cross-reference handler are examples of an +\emph{outer} handler, which is simply placed in front of the arguments of the +handled command as they are, without invoking the collector. The memoization +handler, on the other hand, is an example of an \emph{inner} handler, which +receives the entire argument list from the collector (as a single argument) --- +or more precisely, even \refmmzauto{memoize} sets up an outer handler, but this +outer handler doesn't do much more than invoke the collector, which in turn +invokes the inner handler. + +\paragraph*{Run conditions} are another, if minor, lego piece of the +advising framework. Using key \refmmzauto{run conditions}, the user can set the +conditions under which the (outer) handler is executed; for example, +cross-reference commands are only advised when memoization is underway. And +the same goes for \refcmd{label}, and for the \refmmzauto{replicate}d +\refcmd{index}, and for \refcmd{savepos}, upon which memoization must be +aborted. The bottom-line is that run conditions repeat across handlers, so +it makes sense to separate them out as an independent component of the +framework, with the added bonus that the system can make sure that an +invocation of a advised command which does not satisfy the run conditions +will incur as litte overhead as possible. + +\paragraph{Bailout handler} +An automemoized command applies the next-options (set by \refcmd{mmznext}), but +what happens when the run conditions are not satisfied? If nothing happened, +the existing next-options might apply to the next instance of +(auto)memoization, which would not be what the author intended. This is why +Advice introduces the bailout handler, a piece of code executed before the +original command when the run conditions are not met. Obviously, the bailout +handler for memoization clears out the next-options (and does not process +them). + +\paragraph{The structure of advice} +Together, the components mentioned above form a piece of \emph{advice}: + +\begin{center} + \begin{forest} + /tikz/edge node/.style={midway, font=\scriptsize, yshift=0.5ex}, + for tree={l sep=5ex, % manual, to avoid a page break + child anchor=north, s sep+=2em}, + [activated? + [\refmmzauto{run conditions}?, + edge=dashed, edge label={node[edge node, left]{yes}} + [\refmmzauto{outer handler}\meta{args}, + edge=->, edge label={node[edge node, left]{yes}}, + [default:, + edge=dashed, + [\refmmzauto{collector}\meta{args}, edge=->, + [\refmmzauto{inner handler} + \textcolor{red}{\bracestt{\textcolor{black}{\meta{args}}}}, + edge=-> + ] + ] + ] + [other:\\whatever\rlap\footnotemark, + align=center, edge=dashed] + ] + [\refmmzauto{bailout handler}, + edge=->, edge label={node[edge node, right]{no}}, + [\meta{original command}\meta{args}, edge=->] + ] + ] + [\meta{original command}\meta{args}, + edge=dashed, edge label={node[edge node, right]{no}}, + ] + ] + \end{forest}% + \footnotetext{The handler may do whatever as long as it consumes all and + only the arguments of the original command.} +\end{center} + + + +\paragraph{Deferred activation} +Memoize needs to be loaded early, but activation should take place late, so +that it can surely override the submitted commands; a case in point, +\pkg{hyperref} redefines \refcmd{ref} very late. To address the issue, the +advising framework implements the deferred \refmmz{activation} regime, under +which (de)activation commands are not executed but collected in a special +style, \refmmz{activate deferred}. Memoize deploys the deferred activation +regime throughout the preamble, and executes \refmmz{activate deferred} at the +latest possible \docref{hook:begindocument} hook; as a bonus, it also offers +the author a way to avoid automatic activation completely by invoking key +\refmmz{manual}. + + +\paragraph{Install anywhere} +Once all this machinery is developed, why not offer it to others as well? + +Once I decided to offer Advice (at the time, still called Auto) as a standalone +package (and I freely admit that the framework got much cleaner once I +separated its code out of Memoize) it became immediately clear that if it is +to serve as a generic framework, it should be possible for multiple packages to +use it without interfering with each other. The package thus allows any number +of installations into different namespaces, each namespace a \pkg{pgfkeys} +keypath. The installation is a breeze: +\cs{pgfkeys}\bracestt{\meta{namespace}/\refkey{/handlers/.install advice}}. + + +\subsubsection{Using package Advice} +\label{sec:advice} + +In this section, we will provide some examples of handler declarations, mainly +based on how Memoize deploys the advising framework. + +\paragraph{\refmmzauto[show keypath]{memoize}} + +In section~\ref{sec:tut:automemoization}, the author was instructed to submit a +command to automemoization by writing +\code{\refmmz{auto}=\meta{command}\bracestt{\refmmzauto{memoize},...}}. The +auto-key \refmmzauto{memoize} is a style (defined by Memoize rather than +Advice) which sets the appropriate components of the automemoization advice. +Residing in keypath \refkeypath{/mmz/auto}, it is effectively defined as +follows: + +\begin{tcblisting}{listing only} +\mmzset{ + auto/memoize/.style={ + run if memoization is possible, + bailout handler=\mmz@auto@bailout, + outer handler=\mmz@auto@outer, + inner handler=\mmz@auto@memoize + } +} +\end{tcblisting} + +The heart of this advice is its inner handler, which actually triggers +memoization by executing \refcmd{Memoize}. Remember that the first argument of +\refcmd{Memoize} is the code which the md5sum is computed off of. This +argument must therefore be identical to the author's invocation of the +automemoized command or environment. Given what Advice offers, this is easy to +construct: \refcmd{AdviceReplaced} holds the code replaced by the advice, and +the \meta{arguments} of the automemoized command are waiting for us in |#1|. +The second argument of \refcmd{Memoize} will be similar, but as this is the +code which will get compiled, we have to execute the original definition of the +command, followed by the (unbraced!) \meta{arguments} as |#1|; this is a job +for \refcmd{AdviceOriginal}. (Note that \emph{executing} +\refcmd{AdviceReplaced} would run the auto-handler again, resulting in an +infinite loop! Or at least a pile of errors.) + +However, the overly simplistic approach shown below won't necessarily work. +The issue is that the arguments of \refcmd{Memoize} contain +\refcmd{AdviceReplaced} and \refcmd{AdviceOriginal} themselves, instead of their +contents, i.e.\ (first) expansions. + +\begin{tcblisting}{listing only, bad} +\long\def\mmz@auto@memoize#1{% + \Memoize{\AdviceReplaced#1}{\AdviceOriginal#1}% +} +\end{tcblisting} + +Regarding the first argument, the problem is that the code md5sum will be +computed off of the token list \refcmd{AdviceReplaced}\meta{arguments} --- +exactly as you see it.\footnote{If you inspected a c-memo, you would find + \refcmd{AdviceReplaced}\meta{arguments} in the \refcmd{mmzSource} section.} +This implies that two commands sharing exactly the same \meta{arguments} will +receive the same (c-)memo. For example, if you automemoized first +|\textit{foo}| and then |\textbf{foo}|, both would come out as a bold ``foo'' +upon utilization. + +The second argument illustrates a general issue about the lifespan of +\refcmd{AdviceOriginal} and other |\Advice...| commands.\footnote{The list of + all commands available only within the handler can be found in the + documentation of key \refmmzauto{outer handler} in + section~\ref{sec:ref:advice}.} By executing \refcmd{Memoize}, we leave the +advice and thereby cannot be sure that when expanded, \refcmd{AdviceOriginal} +will mean what it means at the moment. In general, another piece of advice might be +triggered until its expansion, or the group might be closed, etc. For example, +the author may have issued \refcmd{mmznext}\bracestt{\refmmz{at begin + memoization}=\cs{label}\marg{key}}, and as the pre-memoization code is +executed before the memoization driver, the \cs{label}, which is submitted to +Advice so that any \cs{label}s inside the memoized code ``just work'', would +execute another piece of advice, redefining \refcmd{AdviceOriginal} and friends. +Effectively, you'd end up memoizing an invocation of the \cs{label} command. + +The bottom line is that while the code following the above template +\emph{might} sometimes work, Advice offers no guarantees that it will, so I +advise against using it. The actual definition of the memoization inner +handler is shown below. In this definition, we expand \refcmd{AdviceReplaced} +and \refcmd{AdviceOriginal} --- exactly once! --- into the respective arguments +of \refcmd{Memoize}; of course, as the entire invocation of \refcmd{Memoize} is +expanded, we have to guard against expanding the collected arguments (|#1|) and +\refcmd{Memoize} itself.\footnote{As the final touch, the handler also contains + \refcmd{ignorespaces} after the invocation of \refcmd{Memoize}, if this was + requested using \refmmz{ignore spaces}. Note that this could not be done + without pre-expanding the \cs{ifmmz@ignorespaces} conditional, as + \refcmd{Memoize} closes the group in which the auto- and the next-options are + applied.} The result of the expansion is shown under the code: the first and +the second line assume we are automemoizing command |\foo| and environment +|bar|, respectively. Note that \refcmd{AdviceOriginal} expands into an +invocation of \refcmd{AdviceGetOriginal}, a command which may be safely used +outside the advice; the first argument of this command is the auto-namespace +(in our case, \refkeypath{/mmz}), and the second argument is the advised +command. For a \hologo{LaTeX} environment, the advised command is actually +\refcmd{begin}, and this is why the call of \refcmd{AdviceGetOriginal} is of +course followed by the environment name. + +\makeexcerpt{_auto-memoize-inner} +\tcbinputexample[.tex][.excerpt]{% + listing and comment, one file, no attachment, + title={The implementation of the \refmmzauto{inner handler} + for automemoization}, + comment={% + $\rightarrow$ \refcmd{Memoize}\braces{\cs{foo}\Arg1}% + \braces{\refcmd{AdviceGetOriginal}\bracestt{/mmz}\bracestt{\cs{foo}}\Arg1}\\ + $\rightarrow$ \refcmd{Memoize}\braces{\cs{begin}\bracestt{bar}\Arg1}% + \braces{\refcmd{AdviceGetOriginal}% + \bracestt{/mmz}\bracestt{\cs{begin}}\bracestt{bar}\Arg1} + } +} + +Let us now move backwards in time and look at the outer handler installed by +\refmmzauto{memoize}. It is very simple, but performs an important function of +applying the auto- and the next-options (in this order), which also +necessitates opening a group (closed by \refcmd{Memoize}). The final line +invokes the argument collector, which then calls the inner handler; remember +that the invocation of \refcmd{AdviceCollector} is the sole function of the +default outer handler. + +\makeexcerpt{_auto-memoize-outer} +\tcbinputexample[.tex][.excerpt]{% + listing only, one file, no attachment, + title={The implementation of the \refmmzauto{outer handler} + for automemoization}, +} + +Moving even further back in time, we arrive at the run conditions. The +\refmmzauto{memoize} style invokes \refmmzauto{run if memoization is possible}, +defined as \code{run conditions=\cs{mmz@auto@rc@if@memoization@possible}}, with +the installed macro as shown below. Indeed, memoization only makes when +Memoize is \refmmz{enable}d (which we test using \refcmd{ifmemoize}), but we're +not already ``inside \refcmd{Memoize}'' (which we test using +\refcmd{ifinmemoize}). The latter condition is true when we're either +memoizing or regularly compiling some code submitted to memoization (see the +diagram in section~\ref{sec:Memoize}). Note that it is not necessary to invoke +\refcmd{AdviceRunfalse} in branches where the run conditions are not satisfied. + +\makeexcerpt{_auto-run-if-memoization-is-possible} +\tcbinputexample[.tex][.excerpt]{% + listing only, one file, no attachment, + title={The implementation of + \refmmzauto{run if memoization is possible}}, +} + +While it is clear that double memoization is a no-no, why should we avoid +memoizing inside a regular compilation? Imagine that Memoize decides not to +memoize a Forest tree, perhaps because \refmmz{readonly} is in effect. Under +the hood, Forest creates many \env{tikzpicture}s. Should all of them be +(auto)memoized now? Certainly not. + +Finally, what happens when the run conditions are not met? Not much, but +something important nevertheless: by consuming the next-options, the +\refmmzauto{bailout handler} makes sure they will not erroneously apply to the +next instance of (auto)memoization. + +\makeexcerpt{_auto-memoize-bailout} +\tcbinputexample[.tex][.excerpt]{% + listing only, one file, no attachment, + title={The implementation of the \refmmzauto{bailout handler} + for automemoization}, +} + +The only component of the automemoization advice not determined by style +\refmmzauto{memoize} is the argument collector, which allows the user to submit +a command with a weird argument structure to automemoization simply by setting +key \refmmzauto{collector} in addition to executing \refmmzauto{memoize}. For +example, Memoize submits \refcmd{tikz} to automemoization by loading +\reffile{advice-tikz.code.tex}, which contains Advice's definition of the +\cs{tikz} collector \refcmd{AdviceCollectTikZArguments}, and issuing the +following declaration. + +\makeexcerpt{_auto-tikz-collector} +\tcbinputexample[.tex][.excerpt]{% + listing only, one file, no attachment, + title={Declaring automemoization of command \cs{tikz}}, +} + +\paragraph{\refmmzauto[show keypath]{ref}} + +The cross-reference advice presents an example of an outer handler radically +different from the default outer handler. This outer handler does not invoke +the collector at all. As shown below, it grabs the argument of \refcmd{ref} (or +whichever cross-referencing command) on its own --- remember that the outer +handler receives the arguments of the handled command ``as they are,'' i.e.\ +uncollected. It then asks \refcmd{mmzNoRef} to do the real job of getting the +reference key into the context, and finally executes the original \cs{ref}. + +\ExampleName{_auto-ref} +\makeexample{\examplename.tex.c1} +\tcbinputexample{% + listing only, no attachment, + title={\hypercolor{link}{white}A simplified\footnotemark{} definition + of \refmmzauto{ref}}, + % This only works if there is a single footnote in the memoized code. + /mmz/context/.expanded={footnote=\numexpr\thefootnote-1}, +}\footnotetext{The real outer handler allows for arbitrary optional arguments of + the cross-referencing command, and shares code with \refmmzauto{force + ref}.} + +The run conditions of this style are agonizingly simple: \refmmzauto{run if + memoizing} sets \refmmzauto{run conditions} to a macro defined as +\refcmd{ifmemoizing}\hyp\refcmd{AdviceRuntrue}\hyp\cs{fi}. + +\paragraph{\refmmzauto[show keypath]{abort}} + +The advice for aborting memoization is very simple --- it merely executes +\refcmd{mmzAbort} --- but also very sneaky. Here, the \refmmzauto{run + conditions} do the real work of aborting memoization, while the ``real,'' +i.e.\ outer handler, never even gets executed; note the absence of +\refcmd{AdviceRuntrue}, which implies \refcmd{AdviceRunfalse}, which triggers +the execution of the original command after the run conditions are ``checked.'' + +\makeexcerpt{_auto-abort} +\tcbinputexample[.tex][.excerpt]{% + listing only, one file, no attachment, + title={The definition of \refmmzauto{abort}}, +} + +The point here is that executing \refcmd{mmzAbort} (itself a single-liner +setting an internal conditional) is cheaper than testing for the real run +conditions (\refmmzauto{run if memoizing}) and aborting only if they are +satisfied. Of course, the trick only works because (i) the advice doesn't +need to inspect any arguments of advised command, and because (ii) setting the +internal abortion conditional outside memoization does no damage. + +\paragraph{Advice in chains} + +A command may be submitted to several instances of the advising framework, +i.e.\ instances installed under different keypaths. In the example below, we +submit |\foo| both to the instance of Advice installed in keypath |/one| and to +the one installed in keypath |/two|. Under |/one|, the result of |\foo{...}| +will be boxed (|\fboxWrap|); under |/two|, in will be parenthesized +(|\parenWrap|). The order in which this happens depends on the order in which +|\foo| was activated under different keypaths. If we first activate it under +|/one| with the boxing effect and then under two with the parenthesizing +effect, the box will appear within parenthesis; if we reverse the activation +order, the parenthesis will appear inside the box. + +\ExampleName{chained-advice} +\makeexample{\examplename.tex.c1 N=2} +\tcbinputexample{comment=\input{\examplepath.tex.c2}} + +First of all, looking at the code above, you have probably noticed the absence +of key \refmmz{auto}. This is because by default, \refkey{/handlers/.install + advice} defines the \emph{setup key} to be |advice| --- Memoize overrides +this default by installing the framework with \refkey{/handlers/.install + advice}|=|\bracestt{\refkey{/advice/install/setup key}=auto, ...}. + +Next, |advice'| is a variant of |advice| which prevents automatic activation +upon setup (and the same holds for |auto'| vs.\ |auto| in Memoize). We have +used the bar variant above to make it clear that it is the order of activation, +rather than declaration by |advice|\slash \refmmz{auto}, which matters in +determining which handler is applied first. + +Finally, note that the deactivation order must be the reverse of the activation +order. So if we activate |\foo| first in |/one| and then in |/two|, we should +deactivate it in |/two| first and in |/one| next, otherwise Advice will +complain. + + +\paragraph{A simple collector} + +Let us implement a collector for a command which accepts one (standard +\hologo{LaTeX}) optional argument and one mandatory argument; in \pkg{xparse} +terms, a command with argument specification |om|. + +Using \cs{NewDocumentCommand}, such a collector is very easy to implement. We +simply define a command with signature |om| and distinguish two possibilities +regarding the presence of the optional argument, which we test using +\cs{IfValueTF}. If the true branch, we pass a \emph{braced} |[#1]{#2}| to the +inner handler, which we invoke by \refcmd{AdviceInnerHandler}; in the false +branch, we omit the optional argument, passing it an (additionally) braced +|{#2}|. + +\ExampleName{om-collector-NewDocumentCommand} +\makeexample{\examplename.tex.c1} +\tcbinputexample{listing only} + +Defining a functionally equivalent collector using \cs{newcommand} would be a +bit more involved, as \hologo{LaTeX2e} does not offer a standardized way to +test for the \emph{presence} of the optional argument. Consider the following +collector, whose optional argument has the same default value as the advised +command. Is it functionally equivalent to the one above? + +\ExampleName{om-collector-newcommand} +\makeexample{\examplename.tex.c1} +\tcbinputexample{listing only} + +While there will be no visual difference, there is a difference under the hood. +If you compile both documents, you will see that the first one creates three +memos\slash externs, while the second one only creates two: |\foo{...}| does +not have its own memo anymore, but creates and uses the same memo as +|\foo[green]{...}|. + +While the second version might sometimes be preferred, perhaps even in the +context of memoization, the initial collector, which deploys command +\refcmd{CollectArguments}, behaves like the \cs{NewDocumentCommand}-defined +collector above,\footnote{In fact, there is a slight difference after all. + While the above-defined collector won't distinguish between the single-token + mandatory argument given with or without braces, \refcmd{CollectArguments} + will again faithfully replicate the original argument tokens.} as it attempts +to perfectly replicate the command invocation. Furthermore, this behaviour +makes it unnecessary for the author to provide the default values of optional +arguments (and even allows them to replace |O{default}| in the argument +specification by \docref{xparse:o}). + +We now turn to the package CollArgs, which implements the actual argument +collection; we'll revisit the initial collector of Advice at the end of the +following subsection. + + + +\subsubsection{Using package CollArgs} +\label{sec:collargs} + +Automemoization is implemented on top of the framework offered by package +Advice, and that package in turn couldn't really work as intended without +package CollArgs. A regular user of Memoize shouldn't need to know anything +about CollArgs, but a package writer wanting to support Memoize might have to. + +The package provides two public commands, \refcmd{CollectArguments} and +\refcmd{CollectArgumentsRaw}; we'll focus on the former first. +\refcmd{CollectArguments} takes three arguments: optional \meta{options} in the +form of a \pkg{pgfkeys} keylist; a mandatory \meta{argument specification} in a +(slightly extended) \pkg{xparse} format; and the \meta{next-code}: +\begin{center} + \refcmd{CollectArguments}\oarg{options}\marg{argument specification}\marg{next-code}% + \textcolor{gray}{\meta{tokens}} +\end{center} +Following the three formal arguments of \refcmd{CollectArguments} are some +\meta{tokens} --- the rest of the document, really --- and the job of +\refcmd{CollectArguments} is to figure out the extent to which these +\meta{tokens} conform to the given \meta{argument specification}. In other +words, \refcmd{CollectArguments} will consume as many of the \meta{tokens} as a +\meta{command} defined by \refcmd{NewDocumentCommand}\meta{command}\marg{argument + specification}\bracestt{...} would. Once these \meta{argument tokens} are +collected, \refcmd{CollectArguments} executes the \meta{next-code} with the +\meta{argument tokens} given as a single, braced argument (clearly, the +\meta{rest} of the \meta{tokens}, i.e.\ the non-consumed tokens, will follow): +\begin{center} + \meta{next-code}\marg{argument tokens}\textcolor{gray}{\meta{rest}} +\end{center} + +In the example below, we define macro |\PrintAndDo|, which takes two arguments, +a command and the collected arguments of that command, prints out which command +we're about to execute and with what arguments, and then executes that command +with those arguments --- |#1#2| at the end of the definition. Note that |#2| +immediately following |#1| is not braced, so +|\PrintAndDo\makebox{[5em][r]{text}}| executes |\makebox[5em][r]{text}|. + +{\tolerance 1000 Executing |\PrintAndDo\makebox{[5em][r]{text}}| directly would + thus yield the first line of the result below --- and in fact, this is + precisely what gets executed to yield that line, but in a roundabout fashion. + Given the argument specification |oom| (two optional arguments followed by a + mandatory argument), \refcmd{CollectArguments} figures out how many tokens + following its formal arguments conform to this argument specification --- + below, these would be |[5em][r]{text}| following + \refcmd{CollectArguments}\hyp|{oom}{\PrintAndDo\makebox}|--- and puts them, + braced, behind its \meta{next-code} argument, |\PrintAndDo\makebox|, yielding + |\PrintAndDo|\hyp|\makebox{[5em][r]{text}}|.\par} + +\ExampleName{collargs-makebox} +\makeexample{\examplename.tex.c1} +\tcbinputexample{listing and compile} + +Seeing the arguments of |\makebox| without the immediately preceding |\makebox| +might seem strange, but remember that \refcmd{CollectArguments} is about the +arguments of a command, not about the command's control sequence. It doesn't +know or care which command the argument tokens ``belong'' to, as long as they +conform to the given specification. In the example above, it is only in |#1#2| +of |\PrintAndDo| that |\makebox| is ``reunited'' with its arguments, but note +that the reunion is far from obligatory. + +CollArgs supports all the argument types (and modifiers) that \pkg{xparse} +does, including the environment type \docref{xparse:b}, as exemplified below. +Again, the code below might seem strange, as it features an |\end{minipage}| +without the matching |\begin{minipage}|, but the logic is similar as for + commands: just as \refcmd{CollectArguments} occurs in front of the command + arguments, without the command itself, so it occurs in front of the + environment body, without the opening of that body. However, while + \refcmd{CollectArguments} never needs to know the command name, we need to + inform it of the environment name, so that it can find the end of the + environment. This can be achieved as shown below, using key + \refcollargs{environment} in the optional argument of the command, or by our + extension to the \pkg{xparse} argument specification, where the environment + argument type \docref{xparse:b} may be followed by a braced environment name. + In the example below, we could therefore also invoke argument collection by + \refcmd{CollectArguments}\braces + {\docref{xparse:+}\docref{xparse:b}\braces{\env{minipage}}} (we have preceded + \docref{xparse:b} with a \docref{xparse:+} to allow for an environment body + containing paragraph tokens).\footnote{CollArgs automatically adapts to the + format, i.e.\ it knows that environments are tagged by \cs{}\meta{name} and + \cs{end}\meta{name} in \hologo{plainTeX} and by \cs{start}\meta{name} and + \cs{stop}\meta{name} in \hologo{ConTeXt}.} + +\ExampleName{collargs-minipage} +\makeexample{\examplename.tex.c1} +\tcbinputexample{comment=\input{\examplepath.tex.c1}} + +You might wonder why didn't we provide \refcmd{CollectArguments} in the +previous example with argument specification |omb| --- after all, the +\env{minipage} environment takes an optional and a mandatory argument. While that +would work, and produce the same result,\footnote{Unless argument processing + was in effect; see section~\ref{sec:ref:collargs} for details.} note that +\refcmd{CollectArguments} is only interested in finding the scope of the +arguments, and grabbing everything until |\end{minipage}| is the same as first +grabbing the optional argument, maybe, then the mandatory argument, and finally +the argument body. + +\refcmd{CollectArguments} not only supports \pkg{xparse}'s verbatim argument +type \docref{xparse:v}, it can grab an argument of \emph{any} type in the +verbatim mode, triggered by option \refcollargs{verbatim}.\footnote{We refer to + the verbatim mode triggered by \refcollargs{verbatim} as the full verbatim + mode, where all characters are of category ``other''. There is also the + partial verbatim mode, triggered by \refcollargs{verb}, where braces retain + their normal category codes.} We illustrate this key below, where we also use +option \refcollargs{tags}, which makes CollArgs automatically surround the +grabbed environment body with the begin tag \refcmd{begin}\marg{environment + name} and the end tag \cs{end}\marg{environment name}, and use +\refcmd{scantokens} to execute the grabbed environment. Consult +section~\ref{sec:ref:collargs} for the full reference on the verbatim mode and +its limitations. + +% A harmless warning: +% Missing character: There is no ^M (U+000D) in font [lmmono10-regular]:! +% because \texttt in the example is fed a (verbatim) carriage return +\ExampleName{collargs-verbatim} +\makeexample{\examplename.tex.c1} +\tcbinputexample{listing and compile} + +Finally, CollArgs extends the \pkg{xparse} specification by modifier +\docref{xparse:amp}, which allows the user to specify options which apply only +to the following argument, as opposed to the options given as the optional +argument of \refcmd{CollectArguments}, which apply to all the arguments. A +third way to invoke the environment body collection in the above example is +thus \refcmd{CollectArguments}% +\bracestt{\docref{xparse:amp}\bracestt{\refcollargs{environment}=minipage}+b}. + +Both the single-argument and the common options can be given not only as +\pkg{pgfkeys} keys, but also in the raw, ``programmer's interface'' format. +Every option key has a corresponding macro; for example, key +\refcollargs{environment} is matched by macro \refcmd{collargsEnvironment}. +The macros are listed alongside their corresponding keys in the reference +section~\ref{sec:ref:collargs}; here, we merely learn how to use them. + +To use raw options for a single argument, double the ampersand in the argument +specification. Therefore, the fourth way to specify the environment name is +\code{\docref{xparse:amp}\docref{xparse:amp}\bracestt{% + \refcmd{collargsEnvironment}\bracestt{minipage}}\docref{xparse:+}\docref{xparse:b}}. + +To set the raw options for all arguments, use \refcmd{CollectArgumentsRaw}, the +second public command of the package. This command is exactly like +\refcmd{CollectArguments}, excepts that it expects the options in the raw +format and as a \emph{mandatory} argument: + +\begin{center} + \refcmd{CollectArgumentsRaw}\marg{raw options}\marg{argument specification}% + \marg{next-code}\textcolor{gray}{\meta{tokens}} +\end{center} + +This leads us to the fifth way to set the environment name (an overkill, I +know): \refcmd{CollectArgumentsRaw}\hyp +\bracestt{\refcmd{collargsEnvironment}\bracestt{minipage}}\bracestt{+b}\marg{next-code}. +Furthermore, you can use a mixture of raw and key--value options: the raw +option commands include \refcmd{collargsSet}, which applies the given option +keylist. The idea here (incarnated by both Auto and Memoize) is that the +package will provide CollArgs with the raw options, for speed, while the +author can supplement them in the friendly keylist format --- and this leads +us to the sixth, and thankfully final way to set the environment name: +\refcmd{CollectArgumentsRaw}\bracestt{\refcmd{collargsSet}% + \bracestt{\refcollargs{environment}=minipage}}\bracestt{+b}\marg{next-code}. + + +\paragraph{The initial collector} + +As the final example, let us study Advice's initial collector; this is a macro +which is used as the collector when key \refmmzauto{collector} is not given. +This macro is not really \refcmd{CollectArguments}, as we sometimes state to +simplify matters, but a macro which acts as the ``bridge'' between Advice and +CollArgs, by compiling an invocation of \refcmd{CollectArgumentsRaw} from the +given advice setup, and executing it. + +The bridge macro is shown below in its full glory, but it is really less +complicated than it might appear at first sight. In line~2, we use +\refcmd{AdviceIfArgs} to see whether the argument structure of the handled +command was given by the user. If it wasn't, we assume that the handled +command was defined using \cs{NewDocumentCommand} or similar, and use +\refcmd{GetDocumentCommandArgSpec} to retrieve it (line~3; note that +\refcmd{AdviceName} holds the handled control sequence) and store it into +\refcmd{AdviceArgs} (line~4), which also receives the argument specification +when given by the user via key \refmmzauto{args}. + +\makeexcerpt{_advice-CollectArgumentsRaw}(../../advice.edtx) +\tcbinputexample[.tex][.excerpt]{% + listing only, one file, no attachment, + listing options app={numbers=left, numberstyle=\tiny, numbersep=0.5em}, + title={The definition of the initial collector}, +} + +Lines~6--17 are somewhat of an expansion mess, because we have to construct the +invocation of the CollArgs' collector from the advice setup stored in various +macros. But once we think away all the (non-)expansion commands, we're left +with \refcmd{CollectArgumentsRaw} plus the following three arguments: +\begin{enumerate} +\item The raw options (lines 8--11): + \begin{enumerate} + \item In line 8, the advised command's control sequence is designated as the + \refcollargs{caller}. The effect is that if the given arguments don't + conform to the specification, the error thrown seems to come from the + advised command rather than some internal CollArgs macro. The author will + be grateful for this little detail. + \item In line 9, we add any \refmmzauto{raw collector options} set by Advice + (plus the package deploying Advice, like Memoize); user-given options are of + course possible, but not really expected here, because: + \item In lines 10--11, we add the user-given \refmmzauto{collector options}, + if there are any, embedded under \refcmd{collargsSet}. + \end{enumerate} +\item The argument specification (line 14). +\item The \refmmzauto{inner handler} (line 15). +\end{enumerate} + + + +\section{Reference} +\label{sec:reference} + +In this section, the extra information about keys, offered at the right of a +key in parenthesis, may contain the initial value of a key, and also a default +value of a key. In this context, the terms ``initial'' and ``default'' have +the meaning employed by the \pkg{pgfkeys} utility. Initial value refers to the +value that is set by the package. More often, we would call this the default +or the package-default value, but in the \pkg{pgfkeys} parlance, the default value +refers to the value that is passed to the key in the absence of the argument. +Honestly, I only kept to this convention in the reference section; elsewhere, +I often say ``default'' or ``package-default'' and mean the initial value. + +Another convention I kept to in this section is the color-coding of the keys +and commands. Green background indicates a basic key or command, which any +user might want to know about. Red background indicates other, more or less +advanced keys and commands. + + +\subsection{Loading} +\label{sec:ref:loading} +\paragraph{\hologo{LaTeX}} + +Load Memoize by \cs{usepackage}\braces{\docaux{package}{memoize}} or +\cs{usepackage}\oarg{options}\bracestt{memoize}. The latter form functions +almost identically to the former followed by +\refcmd{mmzset}\bracestt{\meta{options}}, with two exceptions. + +First, the \meta{options} as package options may not contain spaces. This is a +constraint imposed by \hologo{LaTeX} itself. To avoid it for a particular key, +define a spaceless variant of the key in \reffile{memoize.cfg}. For example, +to use \refmmz{memo dir} as a package option, put +\refcmd{mmzset}|{memodir/.style={|\refmmz{memo dir}|={#1}}}| into that file. + +Second, key \refmmz{extract} only make senses as a package option, or within +\reffile{memoize.cfg}. Internal extern extraction can only be performed before +the output PDF is opened, and to maximize chances of that, Memoize is designed +to extract as soon as it can: while it is being loaded. Consequently, changing +the extraction method in the document preamble has no effect. + +The necessity of performing extraction before the output PDF is opened is also +why Memoize prefers to be loaded early with respect to other packages. In +particular, it must be loaded before \PGF library |fadings| (see +section~\ref{sec:fadings-problem}) and before the \pkg{beamer} class (see +section~\ref{sec:tut:beamer}). + +If you're familiar with the \TikZ externalization library, you might wonder +whether Memoize has an equivalent of the \refcmd{tikzexternalize} command. It +doesn't. Memoize assumes that if you loaded it, you want to use it --- but you +can always disable it using key \refmmz{disable}, or even by loading package +\refpkg{nomemoize} in its stead. + +In \hologo{LaTeX}, initialization and finalization are completely automatic. +Memoize defines several initialization and finalization styles --- +\docaux{key}{begindocument/before}, \docaux{key}{begindocument}, +\docaux{key}{begindocument/end} and \docaux{key}{enddocument/afterlastpage} +---and executes them at the cognominal \hologo{LaTeX} hooks. + +\paragraph{\hologo{plainTeX}}{} + +Load Memoize by |\input memoize|. As package options cannot be provided in +\hologo{plainTeX}, the author must trigger extraction from \refcmd{mmzset} +using key \refmmz{extract}; I recommend doing this immediately after loading +the package. This key may be invoked with or without a value. In the latter +case, Memoize will extract using the package default method +\refmmz{extract=perl}, unless its has been overriden from +\reffile{memoize.cfg}. + +Furthermore, as \hologo{plainTeX} has no concept of a document body, the text +must be manually enclosed in \refcmd{mmzset}\bracestt{\docaux{key}{begin + document}} and \refcmd{mmzset}\bracestt{\docaux{key}{end document}}; this +is where the initialization and finalization hooks described above will be +executed. Note that \refmmz{extract}, when used, must precede this enclosure. + +Finally, \hologo{plainTeX} has another reason for preferring the early loading +of the package. In this format, Memoize must redefine \refcmd{shipout}, at a +time the meaning of this control sequence is still primitive. In particular, +this means that Memoize must be loaded before \pkg{atbegshi}. + +\begin{tcboxedraster}[raster column skip=1cm]{blankest} + \ExampleName{plainTeX}% + \makeexample{\examplename.tex.c1}% + \tcbinputexample{listing only, title=A minimal \hologo{plainTeX} example} + % + \ExampleName{ConTeXt}% + \makeexample{\examplename.tex.c1}% + \tcbinputexample{listing only, title=A minimal \hologo{ConTeXt} example} +\end{tcboxedraster} + +\paragraph{\hologo{ConTeXt}}{} + +Load Memoize by \cs{usemodule}\bracketstt{memoize} or +\cs{usemodule}\bracketstt{memoize}\oarg{options}. Unlike in \hologo{LaTeX}, +spaces are allowed in \meta{options}; the remarks on key \refmmz{extract} and +loading order are the same as for \hologo{LaTeX}. + +In \hologo{LaTeX}, Memoize automatically executes its initialization and +finalization code when at the beginning and the end of the document body. Due +to my very limited experience with \hologo{ConTeXt}, and its project structure +in particular, I don't know what the appropriate place for initialization and +finalization would be in \hologo{ConTeXt}. I therefore provisionally leave it +to the author to execute \refcmd{mmzset}\bracestt{\refmmz{begin document}} and +\refcmd{mmzset}\bracestt{\refmmz{end document}} manually, and hope for advice +on how to handle this properly. + + +\paragraph{Auxiliary packages} + +\begin{doc}{package={name=nomemoize}} + Loading this package instead of Memoize completely disables memoization, but + does not require removing any Memoize commands from the document (they all + become no-ops). +\end{doc} + +\begin{doc}{package={name=memoizable}} + This package is a programmer's stub: if Memoize is loaded, it does nothing; + otherwise, it provides the no-op variants Memoize commands. See + section~\ref{sec:loading-memoize} for details. +\end{doc} + + +\subsection{Configuration} +\label{sec:ref:configuration} + + +\begin{doc}{easy,cmd={name=mmzset, par=\marg{options}}} + + Update the Memoize configuration. + + The \meta{options} are a comma-separated list of \meta{key}|=|\meta{value} + pairs. They are processed using the \pkg{pgfkeys} utility of PGF/\TikZ (see + \PGFmanual{87}), with the default path set to \docaux{key path}{mmz}. + + The changes are local to the current \hologo{TeX} group, except for keys + where explicitly noted otherwise. +\end{doc} + +\begin{doc}{easy, + cmd={name=mmznext, par=\marg{options}}, + } + + This command accepts the same \meta{options} as \refcmd{mmzset}, but + interprets them as \emph{next-options} --- options which will be applied to + the next, and only the next, \emph{auto}memoized command or environment. + (Remember that a command or environment is submitted to automemoization by + \refmmz{auto}|=|\marg{command or + environment}\bracestt{\refmmzauto{memoize},...}; see + sections~\ref{sec:tut:automemoization}, \ref{sec:tut:working-on-a-picture} + and \ref{sec:ref:advicememoization} for details.) + + Remarks for the author: + + \begin{itemize} + \item Key \refmmz{enable} has no effect inside \refcmd{mmznext}. + \item If \refcmd{mmznext} is used more than once preceding an automemoized + command, only the final invocation takes effect. + \item The next-options also apply to commands and environments for which + memoization is autodisabled via \refmmz{auto}|=|\marg{command or + environment}\bracestt{\refmmzauto{nomemoize},...}. + \item It is safe to set the next-options in front of a command submitted to + automemoization which does not actually undergo memoization in this + particular instance. In other words, the absence of memoization will not + cause the next-options to ``leak'' to the next automemoized command. + \item Only the linear (execution) order of \refcmd{mmznext} and the + automemoized command matters. Key \refcmd{mmznext} will correctly apply to + a single following automemoized command even if it occurs outside the group + which that command is executed from; and it will apply to the following + automemoized command even if it is called within a group closed before that + command is executed. + \end{itemize} + + Remarks for the programmer: + \begin{itemize} + \item The next-options are set globally. + \item The effect of \refcmd{mmznext} is \emph{not} cumulative. Consequently, + \refcmd{mmznext}\braces{} clears the next-options. + \item The next-options are applied by executing \refcmd{mmzAutoInit} within + the advice. Any piece of advice applying the next options should also clear them + when the \refmmzauto{run conditions} are not met. This is streamlined by + style \refmmzauto{apply options}, intended for use within \refmmz{auto} + declarations. Out of the box, this style is deployed by + \refmmzauto{memoize}, \refmmzauto{nomemoize} and \refmmzauto{noop}, but it + may be used by any piece of advice. Note that the \refmmzauto{outer handler} + declared by this style opens a group (to apply the options in) but leaves + it to the (undeclared) \refmmzauto{inner handler} to close that group. + \item Key \refmmz{enable} has no effect inside \refcmd{mmznext} because when + Memoize is disabled upon encountering an automemoized command, the advice + bails out without ever applying the next-options. More generally, this + applies to any advised command whose run conditions require + \refcmd{ifmemoize} to be true. Key \refmmz{disable}, on the other hand, + takes effect, because \refcmd{ifmemoize} is checked within \refcmd{Memoize} + as well. + \end{itemize} +\end{doc} + +\begin{doc}{easy,file={name=memoize.cfg, desc=file}} + The configuration file, loaded just before processing the package options. + It will typically contain a \refcmd{mmzset} command, but it may contain any + \hologo{TeX} code. + + As for any other file loaded by \hologo{TeX}, the location of the file + determines whether it applies system-wide, user-wide or directory-wide. + + This file is also loaded by package \refpkg{nomemoize}, on the off chance it + defines some commands other than \refkeypath{/mmz} keys. (It is not loaded + by package \refpkg{memoizable}, though). +\end{doc} + +\begin{doc}{cmd={name=nommzkeys, par=\marg{key}}} + In package \refpkg{memoize}, this command is a no-op; in packages + \refpkg{nomemoize} and \refpkg{memoizable}, it defines key + \refkeypath{/mmz}|/|\meta{key} as a no-op. + + As explained in section~\ref{sec:tut:final}, use this command to declare any + \refkeypath{/mmz} keys you have used outside \refcmd{mmzset} when switching + to package \refpkg{nomemoize}. +\end{doc} + + +\subsection{Memoization} +\label{sec:ref:memoization} + +\subsubsection{Manual memoization commands} +\label{sec:ref:memoization:manual} + +\begin{doc}{easy, + cmd={name=mmz, par=\oarg{options}\marg{code}}, + env={name=memoize, par=\oarg{options}}, + } + Submit \meta{code} or \meta{environment body} to memoization. + + Prior to memoization, the configuration is locally updated by executing + \meta{options} given as the optional argument to this command, i.e.\ the + given options take precedence to options previously set via \refcmd{mmzset}. + Note that next-options, set by \refcmd{mmznext}, are not applied. + + The effect of the macro and of the environment version of this command is the + same, except that the command version memoizes \meta{code} exactly as-is, + while the environment version trims away any spaces at the beginning and the + end of the code.\footnote{What actually happens is that at the beginning of + the environment body, all space tokens will be discarded. At the end of + the body, no spaces are actually discarded; Memoize simply issues an + \refcmd{unskip}. This should not matter to a regular user who simply + writes down the environment.} The space-trimming feature of the environment + ensures that you can write |\begin{memoize}| and |\end{memoize}| in separate + lines (as shown above), but no extra space will creep into the extern. + + The space-trimming feature of the environment, which trims spaces at the + beginning and at the end of the \meta{environment body}, should not be + confused with the effect of \refmmz{ignore spaces}, which ignores spaces + following the environment end-tag (in \hologo{LaTeX}, |\end{...}|) --- and + which does not apply to manual memoization at all! + + The argument of \refcmd{mmz} \emph{must} be enclosed in braces. +\end{doc} + +\begin{doc}{easy, + cmd={name=nommz, par=\oarg{options}\marg{code}}, + env={name=nomemoize, par=\oarg{options}}, + } + Disable Memoize for the span of the compilation of \meta{code} or + \meta{environment body}. + + This command consumes the \meta{options} in the same way as + \refcmd{mmz}\slash \refenv{memoize} described above. The macro and the + environment version of the command exhibit the same space-trimming behaviour + as their \refcmd{mmz}\slash \refenv{memoize} counterparts, and the argument + of \refcmd{nommz} \emph{must} be enclosed in braces. +\end{doc} + + +\subsubsection{Basic configuration} +\label{sec:ref:memoization:basic} + +\begin{doc}{easy, + key={name=enable}, + key={name=disable}, + } + Enable or disable the functionality of the package. + + What happens when Memoize is enabled depends on the memoization mode + (\refmmz{normal}, \refmmz{readonly}, \refmmz{recompile}) and many other + factors. When the package is disabled, it neither creates new memos and + externs, nor uses the existing ones; this applies to both manual and + automatic memoization. The effect is close to not having Memoize loaded, or + to loading NoMemoize, but it is not completely the same; for example, the + record file (\dmmz) \emph{is} updated while Memoize is disabled, reflecting + the fact that nothing was memoized (or utilized) in the disabled + state.\footnote{Cleaning the folder (\S\ref{sec:cleanup-scripts}) after + disabling the package for the entire document is thus a bad idea.} + + If these keys are used in the preamble, their effect is delayed until the + beginning of the document, to ensure that Memoize is never enabled in the + preamble. Other than that, all these keys do is set the \hologo{TeX} + conditional \refcmd{ifmemoize}, which you may use in your code to test + whether Memoize is enabled. You may also use \refcmd{memoizetrue} and + \refcmd{memoizefalse}, as long as you never enable the package in the + preamble. + + Key \refmmz{enable} cannot be applied to automemoized commands via + \refcmd{mmznext}. It will take effect for manual memoization, though, and + key \refmmz{disable} will work for both, as expected. +\end{doc} + +\begin{doc}{easy, + key={name=normal, desc=the default mode}, + key={name=readonly}, + key={name=recompile}, + } + + Select the memoization mode. + + Each piece of code submitted to (either manual or automatic) memoization is + associated to several files: one c-memo, one cc-memo, and some externs (zero + or more, typically one). When Memoize encounters a piece of code submitted + to memoization, it takes one of the following actions: + + \begin{description} + \item[memoization] The code is compiled in a special way which produces the + associated memos and externs. + \item[utilization] The code is not compiled. Its effect is replicated by + processing the cc-memo; typically, this includes the single extern into the + document. + \item[regular compilation] The code is compiled as if Memoize was absent or + disabled (the memos and externs are neither utilized nor produced). + \end{description} + + \begin{minipage}[t]{0.48\linewidth} + The action taken depends on the memoization mode and on whether all the + memos and externs associated with the code exist, as shown in the table on + the right. Note that a single missing memo or extern implies the ``no'' + column of the table, and that memoization will create \emph{all} the + associated memos and externs, even those which already exist. + \end{minipage} + \hfill + \begin{tabular}[t]{@{}lll@{}} + \toprule + &\multicolumn{2}{c}{Do the memos and externs exist?}\\ + \cmidrule{2-3} + mode&yes&no\\ + \midrule + normal&utilization&memoization\\ + readonly&utilization®ular compilation\\ + recompile&memoization&memoization\\ + \bottomrule + \end{tabular} + + The memoization mode only has effect when Memoize is \refmmz{enable}d. Mode + selection is orthogonal to enabling\slash disabling the package; for example, + if you switch to a new mode while the package is disabled, the new mode will + be in effect once the package is enabled. +\end{doc} + + +\begin{doc}{easy, + desc={style}, + key={name=verbatim}, + key={name=verb}, + key={name=no verbatim}, + } + When \refmmz{verbatim} or \refmmz{verb} is in effect, the code submitted to + memoization is read verbatim; \refmmz{no verbatim} reverts to the normal, + non-verbatim collection of the code. This applies to both manual and + automatic memoization. + + The long version, \refmmz{verbatim}, switches to the full verbatim mode, + where all characters are assigned category code 12 (other). With the short + version, \refmmz{verb}, the braces, |{| and |}|, retain category codes 1 and + 2, which can be useful for verbatim collection of optional arguments. For + details, see the documentation of CollArgs' \refcollargs{verbatim} in + section~\ref{sec:ref:collargs}. + + Under the hood, these keys have two effects. First, they are passed on to the + argument collector (typically, \refcmd{CollectArguments} of the auxiliary + package CollArgs; for details, see section~\ref{sec:ref:advicememoization}), + instructing it to collect the code in the specified fashion, as described + above. Second, if the collected verbatim code is eventually compiled (either + regularly, or memoized), Memoize first rescans it using \refcmd{scantokens}. +\end{doc} + +\begin{doc}{easy, + key={name=padding left, par=\meta{dimension}, + desc={no default, initially |1| |in|}}, + key={name=padding right, par=\meta{dimension}, + desc={no default, initially |1| |in|}}, + key={name=padding top, par=\meta{dimension}, + desc={no default, initially |1| |in|}}, + key={name=padding bottom, par=\meta{dimension}, + desc={no default, initially |1| |in|}}, + } + Set the left/right/top/bottom padding of the extern in the extern PDF. + + Without padding, the (PDF) page holding the extern would tightly fit the + bounding box of the extern. These keys enlarge the extern page by the given + amounts, so that any parts of the extern lying outside the bounding box will + be correctly included when using the extern. See + section~\ref{sec:tut:padding} for details. + + \meta{dimension} is evaluated with \hologo{eTeX}'s |\dimexpr|, and may + contain control sequences \docaux{cmd}{width}, \docaux{cmd}{height} and + \docaux{cmd}{depth}, which will refer to the dimensions of the extern. + \refcmd{width} and friends behave like dimension registers, so it is ok to write + e.g.\ \refmmz{padding right}|=0.5\width|. + + The default padding is what \hologo{pdfTeX} puts around the page anyway, 1 + inch, but we use |1 in| rather than |1 true in|, which is the true default + value of PDF registers \docref{pdfvar:horigin} and \docref{pdfvar:vorigin}, + as we want the padding to adjust with \refcmd{mag}nification. +\end{doc} + +\begin{doc}{easy,key={name=padding, par=\meta{dimension}, desc={style, no default}}} + Set all the above |padding| keys to the given value. +\end{doc} + +\begin{doc}{ + easy, + key={name=context, par=\meta{tokens}, desc={cumulative, no default, + initially set by \refmmz{padding to context}}}, + key={name=clear context}, + } + These keys append the given \meta{tokens} to the \emph{context expression}, + or clear this expression. Memoized code gets recompiled whenever the + expansion of the context expression changes. + + The \meta{tokens} must be fully expandable (modulo protection); in + \hologo{LaTeX}, they will be expanded by |\protected@edef| when calculating + the md5sum of the context. + + The context expression is evaluated at the end of the memoization, and at + utilization attempts after inputting the c-memo (note that the c-memo + contains any additions to the context expression accumulated during + memoization). At evaluation, the given context expression is fully expanded, + yielding the \emph{context value}, whose md5 sum forms the \meta{context md5 + sum} part of the filename of the cc-memo and the extern. + + These keys may be used both prior to the memoization process, or during + memoization. In the former case, their effect is local to the current group; + in the latter case, the effect is global, so that the changes surely survive + until the end of memoization, when the c-memo, where the context expression + is stored, is written into a file. + + Under the hood, these keys manipulate token registers + \docaux{cmd}{mmzContext} and \docaux{cmd}{mmzContextExtra}, changing the + contents of the former while not memoizing, and the contents of the latter + during memoization. These token registers may also be manipulated directly + by the user, as long as one keeps to the convention of adjusting + \refcmd{mmzContext} locally and only while not memoizing, and adjusting + \refcmd{mmzContextExtra} globally and only during memoization. +\end{doc} + +\begin{doc}{ + key={easy, name=meaning to context, + par=\marg{comma-separated list of commands and environment names}}, + key={name=csname meaning to context, par=\marg{control sequence name}}, + key={name=key meaning to context, + par=\marg{full path to a pgfkeys command key}}, + key={name=key value to context, + par=\marg{full path to a pgfkeys value key}}, + } + These keys append the definition of the given construct to the context. + + Essentially, the ``meaning'' keys append \cs{meaning}\meta{control sequence} + to the context, for the appropriate \meta{control sequence}. For example, + \refmmz{meaning to context} appends \cs{meaning}\cs{foo} when given \cs{foo} + as in item in the list, and it appends the internal environment macros + appropriate to the format when given an environment name. Similarly, + \refmmz{key meaning to context} resolves \meta{control sequence} to the + internal macro holding the key command. + + Key \refmmz{key value to context} should be used on keys which store values, + e.g.\ keys initialized by \pkg{pgfkeys} handler \code{.initial}; see + \PGFmanual{87.3.4 and \S87.4.5}. + + All the keys prefix the meaning\slash value by the name of the command\slash + environment\slash key, in order to prevent ambiguous contexts, see + section~\ref{sec:cross-referencing} for details. Furthermore, they all + operate through \code{\cs{csname}...\cs{endcsname}} construct, allowing one + to safely add internal commands to the context. +\end{doc} + +\begin{doc}{ + keypath={/handlers}, + key={easy, name=.meaning to context}, + key={name=.value to context}, + } + These are the handler variants of \refmmz{key meaning to context} and + \refmmz{key value to context}. +\end{doc} + + +\begin{doc}{key={name=padding to context,desc=style}} + This key appends the values of the \refmmz{padding} keys to the context, + causing the memoized code to be recompiled whenever the padding values are + changed. This key is used to initialize \refmmz{context}, so the author + normally shouldn't have to use this key. +\end{doc} + +\begin{doc}{ + cmd={name=mmzNoRef, par=\marg{reference key}, desc=\hologo{LaTeX} only}, + cmd={name=mmzForceNoRef, par=\marg{reference key}, desc=\hologo{LaTeX} only}, + } + These macros append the current value of the \meta{reference key} to the + context,\footnote{More precisely, it is \meta{reference key}|=|\marg{current + value} which is appended.} causing the memoized code to be recompiled + when the reference changes. + + If the reference key is undefined, \refcmd{mmzNoRef} aborts memoization, + while \refcmd{mmzForceNoRef} uses \cs{relax} as the reference string. + + These commands are deployed in the implementation of \refmmzauto[show + keypath]{ref}, \refmmzauto{force ref} and friends. If the + cross-referencing commands you are using are advised by these keys, you most + likely have no need of these macros. +\end{doc} + +\begin{doc}{easy, key={name=per overlay,desc=style}} + This key is only defined in the Beamer class. When in effect, the memoized + code will produce a cc-memo (and extern) for each overlay of the frame. For + implementation, see section~\ref{sec:per-overlay}. +\end{doc} + +\begin{doc}{ + key={name=capture, par=\docAux{of=key:/mmz/capture, value={name=hbox}, text=\alt, + value={name=vbox,of=key:/mmz/capture}}, + desc={no default, initially \refmmz{capture=hbox}}} + } + Select the capture mode. This setting only applies to the default + memoization \refmmz{driver}. + + By default, it is assumed that the memoized code should be executed in the + horizontal mode, so the default memoization driver captures the output of the + memoized code in a \refcmd{hbox} --- and also issues a \refcmd{quitvmode} + (both in the document and in the cc-memo), just in case the memoized code + occurs at the start of the paragraph. + + Use \refmmz{capture}|=|\refmmz{capture=vbox} to execute the memoized code in + the vertical mode: Memoize will capture the output of the memoized code in a + \refcmd{vbox}, and avoid issuing \refcmd{quitvmode}. For example, this + capture mode is necessary to memoize a \refenv{verbatim} environment. +\end{doc} + + +\subsubsection{Inside the memoization process} +\label{sec:ref:memoization:process} + +\begin{doc}{easy, cmd={name=mmzAbort}} + This command aborts the ongoing memoization. + + The memoization will proceed as usual (i.e.\ the extern boxes and the cc-memo + code will be produced), but at the end of this process, no memos will be + produced, no externs shipped out to extern pages, and no record files + updated. +\end{doc} + +\begin{doc}{easy, cmd={name=mmzUnmemoizable}} + This command aborts the ongoing memoization, and marks the submitted code as + unmemoizable. + + The ongoing memoization produces a c-memo setting conditional + \docaux{cmd}{ifmmzUnmemoizable} to true. Upon utilizing this c-memo, the + system switches to regular compilation. + + For example, if you are automemoizing |tcolorbox|es of package + \pkg{tcolorbox}, you will want to refrain from memoizing boxes marked as + |breakable| or |float|ing. Simply aborting the memoization cannot do the + trick here, as memoization compiles the submitted code in a \hologo{TeX} box. + Marking a |breakable| or |float|ing |tcolorbox| as unmemoizable (either + manually using this macro, or automatically using \refmmz{auto key}) makes + sure that after the first compilation when memoization is attempted, the box + will be compiled regularly, and will have the intended ability to break + across pages, or float. +\end{doc} + + +\begin{doc}{ + cmd={name=Memoize, par=\marg{key}\marg{code}}, + } + Depending on various factors, this command either memoizes \meta{code} under + key \meta{key}, utilizes the results of a previous memoization, or performs a + regular compilation of \meta{code}. + + The outcome of executing this command --- memoization, utilization or regular + compilation --- depends upon the Memoize's state (\refcmd{ifmemoize}, + \refcmd{ifmemoizing}) and mode (\refmmz{normal}, \refmmz{readonly}, + \refmmz{recompile}), and the existence of the relevant memos and externs. + The decision process is depicted in section~\ref{sec:Memoize}. + + This command expects to be executed in a dedicated group, which it will close + itself. + + Invoking memoization through \refcmd{Memoize} might be useful for packages + which want to save the results of intensive computations, regardless of + whether the author loads (and enables) memoization or not. However, this + usage is not yet officially allowed, because there is currently no way to + load the core memoization routines without loading the entire package, + thereby forcing the author to use Memoize. +\end{doc} + +\begin{doc}{ + key={ + name=driver, par=\marg{code}, + desc={no default, initially \refcmd{mmzSingleExternDriver}} + }, + } + This key sets \meta{code} as the memoization driver. + + Given some code submitted to memoization, the memoization driver should + produce the memos and externs which will replicate the effect of that code + (while retaining its regular effect). For details, see + section~\ref{sec:memoization-drivers}. + + Typically, the \meta{code} argument of this key will consist of a single + control sequence (the driver control sequence), but any amount of tokens is + allowed. Memoize executes the driver followed by the code which it is + supposed to memoize, in braces, and only cares that the driver consumes that + code. +\end{doc} + + +\begin{doc}{ + key={name=at begin memoization, par=\meta{code}, desc={cumulative, initially empty}}, + key={name=at end memoization, par=\meta{code}, desc={cumulative, initially empty}}, + key={name=after memoization, par=\meta{code}, desc={cumulative, initially empty}}, + } + Use these keys to set up memoization hooks. + + These keys may be used both prior to the memoization process, or during + memoization. In the former case, their effect is local to the current group; + in the latter case, the code given to \refmmz{at begin memoization} is + executed immediately, while the assignment performed by the other two keys is + global, so that the changes surely survive until the end of memoization. + + The code given to hook \refmmz{at begin memoization} is kept in macro + \docaux{cmd}{mmzAtBeginMemoization}, while the content of the other two hooks + resides in two macros per hook: \docaux{cmd}{mmzAtEndMemoization} and + \docaux{cmd}{mmzAtEndMemoizationExtra}, and \docaux{cmd}{mmzAfterMemoization} + and \docaux{cmd}{mmzAfterMemoizationExtra}. All these macros may be + manipulated directly by the user,\footnote{Use \cs{appto}, \cs{eappto}, + \cs{gappto} and \cs{xappto} of package \pkg{etoolbox} (loaded by Memoize) + to easily append code to these macros.} as long as one keeps to the + convention of adjusting the macros without ``\texttt{Extra}'' locally and + only while not memoizing, and adjusting the macros with ``\texttt{Extra}'' + globally and only during memoization. The ``\texttt{Extra}'' macros require + global assignments as they might be manipulated by code residing within a + \hologo{TeX} group of any depth. + + These complications explained, let us take a look at how memoization proceeds + to learn when the hooks are used: + \begin{enumerate} + \item Initialize various conditionals, macros and token registers. (Here is + where the ``\texttt{Extra}'' hooks are cleared.) Remember that at this + point, we're inside a group opened by \refcmd{Memoize}. + \item Execute \refmmz{at begin memoization} hook, i.e.\ the contents of + macro \refcmd{mmzAtBeginMemoization}. + \item Execute the memoization \refmmz{driver}. + \item Execute \refmmz{at end memoization} hook, i.e.\ the contents of macros + \refcmd{mmzAtEndMemoization} and \refcmd{mmzAtEndMemoizationExtra} (in this + order). + \item Write out the memos and ship out the externs to extern pages (unless + memoization was aborted). + \item Close the memoization group. + \item Execute \refmmz{after memoization} hook, i.e.\ the contents of + macros \refcmd{mmzAfterMemoization} and \refcmd{mmzAfterMemoizationExtra} + (in this order). + \end{enumerate} +\end{doc} + +\begin{doc}{ + cmd={name=mmzCMemo, desc={token register, global, empty at the start of memoization}}, + } + This token register mediates the construction of the c-memo. During + memoization (and only during memoization), arbitrary code may be added to + this register; at the end of memoization, Memoize writes out its contents to + the free-form part of the c-memo. + + All assignments to this register should be global. Use \refcmd{gtoksapp} and + \refcmd{xtoksapp} to easily append tokens to the register. +\end{doc} + + +\begin{doc}{ + cmd={name=mmzCCMemo, desc={token register, global, empty at the start of memoization}}, + } + This token register mediates the construction of the cc-memo. During + memoization, the memoization driver should append cc-memo code to + \refcmd{mmzCCMemo}; at the end of memoization, Memoize writes out its + contents to the cc-memo (preceded by the list of produced externs). + + All assignments to this register should be global. Local assignments would + not work, because the memoized code may contain commands, like \cs{label} and + \cs{ref}, which contribute content to cc-memo as well, but these commands may + appear within a \hologo{TeX} group of any depth. + + Use \refcmd{gtoksapp} and \refcmd{xtoksapp} to easily append tokens to the + register. +\end{doc} + +\begin{doc}{ + par=\meta{token register}\marg{tokens}, + cmd={name=toksapp}, + cmd={name=gtoksapp}, + cmd={name=etoksapp}, + cmd={name=xtoksapp}, + } + These commands append the given \meta{tokens} to the \meta{token register}. + \refcmd{etoksapp} and \refcmd{xtoksapp} expand the \meta{tokens} before + appending them; \refcmd{gtoksapp} and \refcmd{xtoksapp} perform a global + assignment. + + These commands are actually provided by CollArgs, and they are defined only + if they don't already exist; in particular, note that \hologo{LuaTeX} + provides them as primitives. + + Unlike the \hologo{LuaTeX} primitive variant, these commands require the + \meta{token register} to be given by a (|\toksdef|fed) control sequence; it + cannot be given as |\toks|\meta{number}. +\end{doc} + +\begin{doc}{ + cmd={name=ifmemoize}, + cmd={name=memoizetrue}, + cmd={name=memoizefalse}, + } + Use the \hologo{TeX}-style conditional \refcmd{ifmemoize} to test whether + Memoize is currently enabled. Within the document body, the conditional may + be set using \refcmd{memoizetrue} and \refcmd{memoizefalse}, which are then + functionally equivalent to \refmmz{enable} and \refmmz{disable}. Do not set + the conditional in the preamble of the document (unless you really know what + you are doing). +\end{doc} + +\begin{doc}{ + cmd={name=ifmemoizing,desc={readonly}}, + } + Use this \hologo{TeX}-style conditional to test whether Memoize is currently + memoizing. It may be only inspected; you should \emph{never} set this + conditional yourself. +\end{doc} + +\begin{doc}{ + cmd={name=ifinmemoize,desc={readonly}}, + } + Use this \hologo{TeX}-style conditional to test whether Memoize is currently + active, in the sense of either memoizing or regularly compiling some code --- + so inside a call to \refcmd{Memoize}. The conditional may be only inspected; + you should \emph{never} set it yourself. +\end{doc} + +\begin{doc}{ + cmd={name=mmzSingleExternDriver, par=\marg{code}}, + } + This is the default memoization \refmmz{driver}, producing exactly one extern + containing whatever is typeset by the submitted \meta{code}. + + The \meta{code} is compiled either within a horizontal or vertical box, + depending on the value of key \refmmz{capture}. In the case of a horizontal + capture, the driver makes sure that the horizontal mode is entered prior to + both typesetting the resulting box in the document, or utilizing the extern. + + For the implementational details, see + section~\ref{sec:memoization-driver-default}. +\end{doc} + +\begin{doc}{ + cmd={name=mmzExternalizeBox, par=\marg{box}\marg{token register}}, + } + This macro is indended to be called by memoization drivers to produce an + extern page. The given \meta{box} is dumped into the document as a separate + extern page, while the \meta{token register} receives the cc-memo extern + inclusion code. + + The \meta{box} may be given either as a control sequence (declared via + |\newbox|), or as box number. The resulting extern page will contain a + \emph{copy} of the given box, padded by the \refmmz{padding} values in effect + at the time of invocation of \refcmd{mmzExternalizeBox}. + + An implementation detail is that \refcmd{mmzExternalizeBox} does not ship out + the extern page immediately. This action is delayed until the end of the + memoization process; more precisely, it is carried out (in tandem with + writing out the c-memo and the cc-memo) between execution of hooks \refmmz{at + end memoization} and \refmmz{after memoization}. This delay guarantees + that no extern pages are produced in the event of aborting memoization, even + if the abortion is triggered after executing \refcmd{mmzExternalizeBox}. + + The \meta{token register} may be given either as a control sequence (declared + via |\newtoks|) or as control sequence |\toks| followed by the register + number. The register will receive the code, which, when executed from the + cc-memo, includes the extern file into the main document. This code consists + of a single invocation of \refcmd{mmzIncludeExtern}. It is the + responsibility of the driver to include the code received by \meta{token + register} in the register \refcmd{mmzMemo}, whose contents are, unless + memoization is aborted, written into the cc-memo. (See + \refcmd{ifmmzkeepexterns} and \refmmz{after memoization} to learn about + another way to use the code received by \meta{token register}.) + + The invocation of \refcmd{mmzIncludeExtern} in the produced extern-inclusion + code is adapted to the type of the box (horizontal or vertical), which is + detected automatically --- the memoization driver does not need to inform + \refcmd{mmzExternalizeBox} about this type explicitly.\footnote{This does not + negate the need for key \refmmz{capture}, which applies to the default --- + and therefore generic --- memoization driver. This driver cannot know + whether the memoized code would prefer to be compiled in a horizontal or + vertical box. It is precisely key \refmmz{capture} which gives the user an + opportunity to inform Memoize about this preference. Only once the + memoized code is compiled into a box of the appropriate type, it is trivial + to detect the type of that box.} +\end{doc} + +\begin{doc}{ + cmd={name=ifmmzkeepexterns, desc={initially \cs{iffalse}}}, + cmd={name=mmzkeepexternstrue}, + cmd={name=mmzkeepexternsfalse}, + } + Setting this conditional to true makes Memoize keep the extern boxes in the + global temporary storage even after shipping them out as extern pages. (The + temporary storage is emptied at the start of the next memoization.) + + The extern inclusion code received by the \refcmd{mmzCCMemo} when executing + \refcmd{mmzExternalizeBox} is primarily meant to be executed by inputting the + cc-memo file; i.e.\ when the cc-memo is input, \refcmd{mmzIncludeExtern} is + defined to include the extern file into the document. However, it sometimes + makes sense to execute the cc-memo contents immediately after memoization; + for example, if memoization produces several externs, intricately integrated + into the surrouding environment, it might be cumbersome to replicate their + typesetting both in the memoizing compilation and in the cc-memo code --- + easier to build up the cc-memo code and execute it right after memoization. + This is why Memoize, just before executing the contents of \refmmz{after + memoization} hook, redefines \refcmd{mmzIncludeExtern} to include externs + from the temporary storage rather than from (at that point still + non-existing) extern files. However, as this mechanism requires Memoize to + keep the externs around even after memoization, it is not enabled by default: + it must be enabled by setting conditional \refcmd{ifmmzkeepexterns} to true. +\end{doc} + +\begin{doc}{ + key={keypath=/mmz/auto, name=integrated driver, par=\marg{name}} + } + Use this key to easily setup a memoization driver which is integrated into + the command itself. + + \begin{tcolorbox}[warning] + This is an auto-key residing in keypath \refkeypath{/mmz/auto}. + \end{tcolorbox} + + An integrated driver must have a way of telling whether it is memoizing or + regularly compiling the code. This key declares a driver-specific + conditional which may be inspected, using \refcmd{IfMemoizing}, to determine + this. The conditional is set to true by the formal driver of the command + (set up by the invocation of this key), executed at the start of memoization; + it should never be set elsewhere. See + section~\ref{sec:memoization-multiple-externs} for details and an example. +\end{doc} + +\begin{doc}{ + cmd={name=IfMemoizing, par=\oarg{offset}\marg{name}\marg{true code}\marg{false code}} + } + This \hologo{LaTeX}-style conditional is meant to be used by the + \refmmzauto{integrated driver} with the given \meta{name}. It tests whether + this particular driver is currently memoizing some code. + + Potentially recursive commands are supported via the optional argument + \meta{offset}. If given, the conditional will only execute the \meta{true + code} when the current \hologo{TeX} group level matches the \hologo{TeX} + group level at the time of the invocation of the formal driver (held in + \refcmd{memoizinggrouplevel}), plus the \meta{offset}. In effect, the inner + invocation of the integrated driver will perform a regular compilation. For + details, see section~\ref{sec:memoization-complex-single-driver}. +\end{doc} + +\begin{doc}{ + cmd={name=memoizinggrouplevel,desc={readonly}}, + } + During memoization, this macro holds the \hologo{TeX} group level in effect + at the start of the memoization. +\end{doc} + +\begin{doc}{ + cmd={name=mmzRegularPages, desc=readonly counter}, + } + This counter holds the number of pages shipped out (so far) by the format's + regular shipout routine. \emph{Do not change its value!} + + In \hologo{LaTeX}, this counter is synonymous with + \refcmd{ReadonlyShipoutCounter}, and in \hologo{ConTeXt}, it is synonymous + with \refcmd{realpageno}. Memoize does not touch its value. + + In \hologo{plainTeX}, Memoize hijacks the \refcmd{shipout} control sequence + to count (and only to count) regular shipouts. In order for its value to be + realistic, Memoize should be loaded before other packages which hack + \cs{shipout} --- in particular, before \pkg{atbegshi}. +\end{doc} + +\begin{doc}{ + cmd={name=mmzExternPages, desc=readonly counter}, + } + This counter holds the number of extern pages Memoize has shipped out (so + far). \emph{Do not change its value!} + + A third-party tool may inspect this counter to have a realistic count of + shipped-out pages. +\end{doc} + +\begin{doc}{ + cmd={name=mmzExtraPages, desc=counter}, + } + This counter holds the number of pages shipped out (so far) in a way not + tracked by either \refcmd{mmzRegularPages} or \refcmd{mmzExternPages}. It + \emph{should} be advanced by any code which performs such shipouts, or + Memoize won't work correctly. +\end{doc} + + +\subsubsection{Tracing} +\label{sec:ref:memoization:tracing} + +\begin{doc}{ + key={name=trace, conditional=false}, + } + When tracing is on, Memoize shows information about its decision processes on + the terminal. You can learn whether the memoized code is being memoized, + utilized or regularly compiled; find out the md5sum of the code and which + input line it comes from; etc. + + This key has the syntax of a conditional, but there is no underlying + \hologo{TeX} conditional. The low-level interface for switching the tracing + on and off consists of macros \docaux{cmd}{mmzTracingOn} and + \docaux{cmd}{mmzTracingOff}. + + To learn about tracing the ``auto'' part of the automemoization process, also + \refcmd{AdviceTracingOn}. +\end{doc} + +\begin{doc}{ + key={name=include source in cmemo, conditional=true}, + } + + As a courtesy towards a curious user and a debugging aid, Memoize can include + a copy of the memo source in the c-memo. This feature is switched on by + default, but as the package itself never uses that information, it can be + safely switched off at any time. +\end{doc} + +\begin{doc}{ + key={name=include context in ccmemo, conditional=false}, + } + When this key is in effect, the expanded context expression is appended to + the cc-memo, behind the \docaux{cmd}{mmzThisContext} marker. + + Memoize never uses the context information from the cc-memo; this information + is only for tracing purposes. +\end{doc} + +\begin{doc}{ + key={name=direct ccmemo input, conditional=false}, + } + When this key is set to false, a cc-memo is processed indirectly: it is first + read into a token register, and it is the contents of this register which are + executed. When the key is set to true, the cc-memo is simply |\input|ted. + + The indirect execution is implemented to facilitate inverse search. Under + the direct cc-memo input, inverse search pointed at an included extern will + visit the cc-memo, which is not practical; under the indirect regime, the + inverse search will work as expected, and this is why the indirect cc-memo + input is the default. + + The overhead produced by the default indirect input method seems negligible, + but there are other factors which might make the user switch to the direct + input. For one, a cc-memo changing some category codes will require direct + input (no such cc-memos are ever produced out of the box). Less crucially, + sometimes one would like to use the inverse search to figure whether a part + of the document was produced by regular compilation or utilization, and which + memos\slash externs were utilized if the latter. Figuring this out under the + indirect input regime is harder: (i) reading the tracing information shown by + \refmmz{trace} is the surest way to learn what's going on, although (ii) + visual inspection of the externs and (iii) grepping through the |.memo.dir| + folder for particular code often help, as well. + + Both input methods use the same cc-memos; there is no need to + \refmmz{recompile} the memos when switching the cc-memo input method. Note + that the default indirect input method crucially relies on cc-memos ending + with \refcmd{mmzEndMemo}; this macro should not appear in the cc-memo itself. +\end{doc} + + + +\subsubsection{Internal memo commands} +\label{sec:ref:memoization:internal} + +The end-user should never have to use these commands. They are not formally +marked as internal by a |@| in their name only because doing so would +complicate |\input|ting the memos due to the category code changes it would +require. + +\begin{doc}{ + cmd={name=mmzMemo}, + } + This macro marks the beginning of a c-memo and a cc-memo core. Without it, + utilization of a memo will not work. +\end{doc} + +\begin{doc}{cmd={name=mmzSource}} + This macro marks the beginning of the memoized source in the c-memo. That + source is not used by Memoize in any way. It's inclusion into the c-memo may + be switched off by \refmmz{include source in cmemo}|=false|. +\end{doc} + +\begin{doc}{ + cmd={name=mmzResource, par=\marg{filename}}, + } + This is an internal command, which only occurs in a cc-memo. It checks + whether file \meta{filename} exists and is non-empty, and triggers + recompilation of the memoized code if the check fails. +\end{doc} + +\begin{doc}{ + cmd={ + name=mmzIncludeExtern, + par=\marg{seq}\braces{\refcmd{hbox}\alt\refcmd{vbox}}% + \marg{expected width}\marg{expected height}\marg{expected depth}% + \marg{padding left}\marg{padding bottom}\marg{padding right}\marg{padding top}, + } + } + + This is an internal command, which only occurs in a cc-memo. It includes the + extern identified by the sequential number \meta{seq} into the document as a + box of the specified type (horizontal or vertical). The extern is trimmed by + the given padding values. After trimming, the command checks whether the + size of the resulting box matches the given expectations; if it doesn't, a + warning is yielded. + + Before this command is executed, the externs should be listed by a sequence + of \refcmd{mmzResource} commands; \meta{seq} refers to the sequential number + of an extern in this sequence. + + This command may also be executed by executing the entire contents of + \refcmd{mmzCCMemo} after memoization. +\end{doc} + +\begin{doc}{ + cmd={name=mmzLabel, par=\marg{label key}\marg{label value}} + } + This is an internal command written into the cc-memo by the auto-handler of + \refcmd{label}. It temporarily stores \meta{label value} into + \refcmd{@currentlabel} and then executes \cs{label}\marg{label name}. +\end{doc} + +\begin{doc}{ + cmd={name=mmzEndMemo}, + } + This macro marks the end of a cc-memo. It is used to grab the cc-memo core + (everything between \refcmd{mmzMemo} and \refcmd{mmzEndMemo}) under the + indirect cc-memo input regime, i.e.\ when \refmmz{direct ccmemo input} is not + in effect. +\end{doc} + + +\subsection{Location of memos and externs} +\label{sec:ref:dirs} + +\begin{doc}{easy, + key={name=memo dir,par=\meta{name}, desc={style, default \texttt{\string\jobname}}}, + } + A convenient way to store memos and externs in a dedicated directory (and + create this directory in case it does not exist). Without an argument, this + key places these files in subdirectory \meta{document name}|.memo.dir| of the + current directory. See section~\ref{sec:tut:memodir} for the tutorial. + + This key sets \refmmzpath{relative} to |false|, \refmmzpath{dir} to + \meta{name}|.memo.dir|, \refmmzpath{prefix} to empty and \refmmz{mkdir} to + |true|. In effect, memos and externs are placed in the subdirectory + \meta{name}|.memo.dir| of the directory containing the document; their + filenames contain no \meta{name} prefix, as \meta{name} already occurs in the + directory name. The latter feature also makes it easy for projects (or parts + of a project) to share memos and externs; see section~\ref{sec:tut:multifile} + for a typical usage case. +\end{doc} + +\begin{doc}{easy,key={name=no memo dir,desc=style}} + A convenient way to undo the effect of \refmmz{memo dir} and revert to the + initial settings where memos and externs are located in the current + directory. See section~\ref{sec:tut:memodir} for the tutorial. + + This key resets the subkeys of \refmmz{path} to their initial values, and + sets \refmmz{mkdir} to |false|. +\end{doc} + +\begin{doc}{ + key={name=path, par=\meta{keylist}, desc=style}, + } + + Set the \emph{path prefix} to memo and extern files by executing the given + \meta{keylist} in keypath \docaux{key path}{mmz/path}. + + The path prefix contains both the location of the memo\slash extern files + (set by \refmmzpath{relative} and \refmmzpath{dir}) and the initial, + fixed part of their filenames (set by \refmmzpath{prefix}): + \begin{center} + \meta{path prefix}=[|./|]\meta{directory}|/|\meta{prefix} + \end{center} + where ``|./|'' is only present when \refmmzpath{relative} is in effect. + Given the \meta{path prefix}, this is how the paths to memos and externs are + constructed: + \begin{center} + \begin{tabular}{r@{ }l} + c-memo:&\meta{path prefix}\meta{code md5 sum}|.memo|\\ + cc-memo:&\meta{path prefix}\meta{code md5 sum}|-|\meta{context md5 sum}|.memo|\\ + extern:&\meta{path prefix}\meta{code md5 sum}|-|\meta{context md5 sum}-$N$|.pdf|\\ + &(where ``-$N$'' is only present when $N\neq 0$, i.e.\ for non-first externs)\\ + \end{tabular} + \end{center} + + For example, the default \refmmz{no memo dir} \meta{path prefix} equals + \meta{jobname}|.| (note the dot), and after issuing \refmmz{memo dir}, the + \meta{path prefix} is \meta{jobname}|.memo.dir|. Executing + \refmmz{path}|=|\bracestt{\refmmzpath{dir}=project-memo-dir, + \refmmzpath{prefix}=\string\jobname.} would set the memo prefix to + |./project-memo-dir/|\meta{jobname}|.|, allowing the author to keep the memo + files of all documents compiled from the project directory in the same place. + + The following keys may occur in the \meta{keylist}: + \yadocset{keypath=/mmz/path} + + \begin{doc}{ + key={name=relative, conditional=true}, + } + + This key determines whether the location is relative to the current + directory, i.e.\ the directory where \hologo{TeX} is executed from; + usually, this will be the directory where the compiled |.tex| file resides. + When set to |true| (the default), the directory set by \refmmzpath{dir} + is prefixed by ``|./|''. + + You will probably need to set |openout_any=a| in \reffile{texmf.cnf} + to allow writing to an arbitrary directory --- but remember, this might be + dangerous! + \end{doc} + + \begin{doc}{ + key={name=dir, par=\meta{name}, desc={no default, initially empty}}, + } + + Set the \meta{directory} where Memoize will search for memos and externs + and\slash or create them in. + + Unless \refmmzpath{relative} is set to |false|, this location is + relative to the current directory. Given the empty default, memos and + externs are therefore created in the directory holding the source file by + default. + + In principle, the given memo directory must already exist, but see + \refmmz{mkdir}. + \end{doc} + + \begin{doc}{ + key={name=prefix, par=\meta{name}, + desc={no default, initially \texttt{\string\jobname.}}}, + } + + Set the \meta{prefix}, i.e.\ the initial part of the memo\slash extern + filename. Initially, it is set to the document name. + \end{doc} + + After processing the \meta{keylist}, \refmmz{path} records the new path + prefix by invoking \refmmz{record/record type/prefix}, which typically results + in a \refcmd{mmzPrefix} entry in the \dmmz file, and attempts to create the + \meta{directory} is \refmmz{mkdir} is in effect --- except when \refmmz{path} + is executed in the document preamble: then, these actions are only carried + out at the beginning of the document, for the final value of the keys. +\end{doc} + +\begin{doc}{key={name=mkdir, conditional=false}} + When this key is set to true, Memoize will attempt to create the directory + set by \refmmz{path} if it does not yet exist. The directory creation takes + place at the beginning of the document and at every subsequent invocation of + key \refmmz{path}, using the system command specified by \refmmz{mkdir + command}. +\end{doc} + +\begin{doc}{ + key={name=mkdir command, par=\meta{system command invocation}, + desc={no default, initially |mkdir "\#1"|}}, + } + Sets the command used to create \refmmzpath{dir} when \refmmz{mkdir} is in effect. + + This is a system command, so an appropriate shell escape mode must be in + effect to execute it successfully. + + The default should work on Linux, MacOS and Windows. On Linux systems, it + makes sense to change the default (using \reffile{memoize.cfg}) to |mkdir + -p "#1"|, which can create parent folders as necessary. + + Note that extraction methods \refmmz{extract=perl} and + \refmmz{extract=python} set \refmmz{mkdir command} to + \refscript{memoize-extract.pl} \refscript{memoize-extract.pl--mkdir} and + \refscript{memoize-extract.py} \refscript{memoize-extract.pl--mkdir}, + respectively. Unlike system |mkdir|, these commands are safe in the sense + that they conform to the paranoid openout regime (|openout_any=p|). +\end{doc} + +\begin{doc}{ + key={name=output-directory, par=\meta{directory}, + desc={no default, initially undefined, as package option only}}, + cmd={name=mmzOutputDirectory, par=\meta{directory}, desc={pre-defineable}} + } + If the \hologo{TeX} binary was invoked with option |-output-directory|, one + should use this key or define this macro to inform Memoize this, as there is + no way to learn about it automatically. + + The given \meta{directory} must end with a slash (|/|). + + The key is only allowed as a package option. But I imagine that defining the + macro from the command line, prior to inputting the document source, will be + more common: + \begin{center} + |pdftex -output-directory somedir '\def\mmzOutputDirectory{somedir/}\input doc'| + \end{center} +\end{doc} + + +\subsection{Extern extraction} +\label{sec:ref:externs} + +\begin{doc}{easy, + key={name=extract,par=\marg{extraction method}, + desc={preamble-only, initially \refmmz{extract=perl}, default: see below}}, + } + This key selects or executes the extern \meta{extraction method}, i.e. the + method which Memoize will use to extract the extern pages out of the document + PDF. + + Out of the box, Memoize recognizes the following \meta{extraction method} + keywords: \docAux{easy, of=key:/mmz/extract, value={name=perl}, comma, + value={name=python}, comma, value={name=tex}, and, value={name=no}}. The final + keyword (not available in \hologo{plainTeX}) instructs Memoize to \emph{not} + perform to extraction; it should be used when extraction is performed + externally (for details, see section~\ref{sec:setup}). Additional methods + may be installed by defining key \docaux{key + path}{mmz/extract}|/|\meta{extraction method}. + + When invoked from \reffile{memoize.cfg} or used as a package option, this key + \emph{selects} the extraction method. In this case, the key has no default + value, i.e.\ it is illegal to use it without an argument. The method + selected by the package option overrides the method selected in + \reffile{memoize.cfg}, which in turn overrides the package-initial value + \refmmz{extract=perl}. + + In \hologo{LaTeX} and \hologo{ConTeXt}, the selected method is executed at + the end of loading the package. Afterwards, the key is disabled. If you + really need to invoke extraction in the preamble, or again, write + \refmmz{extract}|/|\meta{method}. + + When invoked from a \refcmd{mmzset} in the document preamble, this key + immediately \emph{executes} the given extraction method. In the preamble + incarnation, the key may be invoked without a value to execute the previously + selected method. + + As we want to allow a \hologo{plainTeX} author to override the extraction + method specified by the package (\refmmz{extract=perl}) or in + \reffile{memoize.cfg}, Memoize does \emph{not} perform extern extraction + while loading the package in this format. In \hologo{plainTeX}, internal + extraction can only be triggered by an explicit invocation of + \refmmz{extract} in the ``document preamble'' --- i.e.\ between |\input + memoize| and \refcmd{mmzset}\braces{\refmmz{begin document}}. In this case, + the key does not require an argument; invoking it without the argument will + execute either the package-default, \refmmz{extract=perl}, or whatever the + user had selected in \reffile{memoize.cfg}. + + Executing extraction method \refmmz{extract=perl} or \refmmz{extract=python} + has an additional effect of setting the \refmmz{mkdir command} to the + extraction script with option \refscript{memoize-extract.pl--mkdir}. This + obviates the need to include |mkdir| among the restricted shell commands if + one is using the restricted shell mode. +\end{doc} + +\subsubsection{Perl- and Python-based extraction} +\label{sec:ref:extraction-perl-python} + +\begin{doc}{ + key={name=perl extraction command, par=\meta{system command}, + desc={no default, initially \refscript{memoize-extract.pl}}}, + key={name=perl extraction options, par=\meta{options}, + desc={no default, initially: see below}}, + key={name=python extraction command, par=\meta{system command}, + desc={no default, initially \refscript{memoize-extract.py}}}, + key={name=python extraction options, par=\meta{options}, + desc={no default, initially: see below}}, + } + These keys determine the system calls used for invoking the extraction + scripts \refscript{memoize-extract.pl} and \refscript{memoize-extract.py}. + All the details below apply both to the Perl and the Python version. + + Use |perl|\slash|python extraction command| to set the name of the extraction + script. If necessary, include the full path to the scrip, or |perl|\slash + |python| plus the path to the script. Whatever you set here must be allowed + by the shell escape mode. + + Use |perl|\slash|python extraction options| to set the options that the + script will receive; consult the documentation of + \refscript{memoize-extract.pl} for their meaning. + + The initial value of \refmmz{perl extraction options}, shown below, (i) sets + embedded mode, which prefixes each output line with the script name, (ii) + requests a log file named \meta{jobname}|.mmz.log| (|-l|; |\input|ting the + log after extraction informs Memoize, and the author, whether extraction was + successful), and (iii) sets the warning template suitable for the format + (|-w|; this way, any warning messages issued by the script can be reissued by + the compilation of the document). + + These keys were initialized using \pkg{pgfkeys} handler |.initial|, so their + values may be modified by handlers |.prefix|, |.append|, etc. During the + execution of the system call, their values are fully expanded --- thus the + |\string|s in the initial options value below. The guards + (|%<latex>| etc.) make sure that + each format asks for a warning message it understands (the code shown below + is an excerpt from the |.dtx| file; ultimately, each format receives a single + of the three guarded lines). + + \makeexcerpt{perl-extraction-options} + \tcbinputexample[.tex][.excerpt]{listing only, one file, no attachment, + title={The initial value of \refmmz{perl extraction + options}}, full width, left=-2mm, right=0mm, listing options app={ + morecomment={[s]{\%<}>} }, } +\end{doc} + +\begin{doc}{ + script={name=memoize-extract.pl, par=[\meta{options}] \meta{name}\texttt{.mmz}}, + script={name=memoize-extract.py, par=[\meta{options}] \meta{name}\texttt{.mmz}}, + } + These scripts extract the new externs recorded in \meta{name}\dmmz from + \meta{name}|.pdf|. Memoize invokes them when loaded with package option + \refmmz{extract}|=|\refmmz{extract=perl} (the default) or + \refmmz{extract}|=|\refmmz{extract=python}. + + The script inspects the given record file, \meta{name}\dmmz, for lines of + form \refcmd{mmzNewExtern}\marg{extern filename}\marg{extern page + number}\braces{\meta{expected width}\texttt{pt}}\braces{\meta{expected + height}\texttt{pt}}. For each such line, page number + \meta{extern page number} is extracted from \meta{name}|.pdf| into + \meta{extern filename}. Other lines are ignored (and so are commented + invocations of \refcmd{mmzNewExtern}). + + The \meta{extern filename} may contain a (relative or absolute) path to the + new extern file. The relative paths are relative to the location of the + \dmmz file, even when the script is invoked from some other directory. + + To guard against extracting a wrong page, the script checks whether the size + of each extracted page matches the \meta{expected width} and \meta{expected + height}.\footnote{\label{fn:tolerance}To avoid false positives, the match + need not be exact, a difference up to |0.01pt| is tolerated. Some PDF + tools, notably the |PDF::API2| library deployed by the Perl version of the + script, round the dimensions of a PDF page, recorded in |/MediaBox|, to two + digits.} If it does not, the script refuses to extract the page, yields a + warning and even removes the extern file if it exist. + + The extraction script's paranoia extends further. It will refuse to extract + the page, yielding a warning, if a (c)c-memo associated to the extern does + not exist. And it will refuse (yielding a runtime error) to write to any + file whose absolute path does not occur under the current directory or the + directory set by \docref{TEXMFOUTPUT} (in \reffile{texmf.cnf} or as an + environment variable); \docref{TEXMFOUTPUT} is also not allowed to point to + the root directory, except on Windows, where it can only point to a drive + root. + + \yadocset{ + of=script:memoize-extract.pl, + override/.style={ + index annotation={option of {% + \hypercolor{link}{gray}% + \hyperref[script:memoize-extract.pl]{\texttt{memoize-extract}}% + }}, + }, + } + \begin{doc}{ + option={name=pdf, short name=P, par=\meta{pdf}} + } + Extract the externs from the given \meta{pdf} instead of the default + \meta{name}\texttt{.mmz}. Note that file \meta{pdf}, despite the different + name, should be produced by the same compilation that produced + \meta{name}\dmmz, otherwise wrong pages might be extracted. + \end{doc} + \begin{doc}{ + option={name=prune, short name=p} + } + After extraction, remove the extracted extern pages from the document PDF. + \end{doc} + \begin{doc}{ + option={name=keep, short name=k} + } + By default, the script comments out the \refcmd{mmzNewExtern} lines in the + \dmmz file, to prevent multiple extractions. Specifying this option + prevents this behaviour. + \end{doc} + \begin{doc}{ + option={name=log, short name=l, parameters=\meta{filename}} + } + When given this option, the script creates a log file with the given name. + The log will receive any warnings yielded by the script, see also + \refscript{memoize-extract.pl--warning-template}. A well-formed log ends + with |\endinput|, indicating that the \dmmz file was completely processed. + + In absence of this option, the warnings are printed to the standard error. + \end{doc} + \begin{doc}{ + option={name=warning-template, short name=w, parameters=\meta{template}}, + } + When this option is given, a warning message is not logged (or printed to + standard error) as-is; it is wrapped by the template given as the argument + of this option. More precisely, the script will log the given + \meta{template}, with all occurrences of \docaux{cmd}{warningtext} + substituted by the actual warning message. + + The option can be used to propagate warnings to the upstream compilation, + simply by setting this option appropriately (see the initial value of + \refmmz{python extraction options}) and |\input|ting the log after + extraction. + \end{doc} + \begin{doc}{ + option={name=force, short name=f} + } + Force extern extraction even if the size-check fails. The failure will + still be logged. + \end{doc} + \begin{doc}{ + option={name=quiet, short name=q} + } + Normally, the script prints what it is doing to the standard output; in + particular, it prints out the page number and the filename of each extern + it is extracting. This option disables this behaviour. + \end{doc} + \begin{doc}{ + option={name=embedded, short name=e} + } + When given this option, everything printed to the standard output is + prefixed by the script name. The idea is to use this option when calling + the script from the \hologo{TeX} compilation, as it makes it easy to + identify the output of the script. + \end{doc} + \begin{doc}{ + option={name=mkdir, short name=m} + } + When given this option, the extraction script transforms into a paranoid + |mkdir| (|-p|). Argument \meta{name}|.mmz| is interpreted as a path to the + directory to create; all other options are ignored. + + The ancestors of the directory are created as needed. The script will + refuse to create any directory whose absolute path does not occur under the + current directory or a directory listed in \docref{TEXMFOUTPUT} (set in + \reffile{texmf.cnf} or as an environment variable). + + This option exists so that the author using the restricted shell mode does + not have to list |mkdir| among the restricted shell mode commands (and it + is also safer than a plain |mkdir|). + + Memoize automatically uses this script to create the memo directory when + option \refmmz{extract} is given value \refmmz{extract=perl} or + \refmmz{extract=python}. + \end{doc} + \begin{doc}{ + option={name=help, short name=h} + } + Show help. + \end{doc} + \begin{doc}{ + option={name=version, short name=V} + } + Show Memoize version. + \end{doc} + + Functionally, the Perl (|.pl|) and the Python (|.py|) version of the script + are almost equivalent. The minor differences, listed below, are mostly due + to the underlying PDF-processing library: + \hreftt{https://metacpan.org/pod/PDF::API2}{PDF::API2} in Perl, and + \hreftt{https://pypi.org/project/pdfrw2/}{pdfrw2} in Python. + \begin{itemize} + \item The Python script is about twice as fast as the Perl script. However, + both scripts are very fast compared to the \hologo{TeX}-based extraction. + On my computer, extracting 160 externs out of a 400-page book takes 1.4s + with Python, 3.7s with Perl, and 65s with \hologo{TeX}. But even when + using \hologo{TeX}-based extraction, externalization using Memoize is + extremely fast compared to the \TikZ's externalization library. Adding + the regular compilation time of one minute to the above numbers, we arrive + at the maximum externalization time of about two minues, whereas my + estimate for the production of all 160 externs using \TikZ's + externalization would be an \emph{hour} or more. + \item The Python script cannot extract externs out of PDFs created without + stream compression, i.e.\ with \refcmd{pdfvariable} + \docref{pdfvar:compresslevel} set to |0|. + \item Occasionally, the Perl script crashes during extraction externs; see + section~\ref{sec:troubleshooting} for details. + \end{itemize} +\end{doc} + +\subsubsection{\texorpdfstring{\hologo{TeX}}{TeX}-based extraction} +\label{sec:ref:extraction-tex} + +\begin{doc}{ + key={name=tex extraction command, par=\meta{system command}, + desc={no default, initially |pdftex|}}, + key={name=tex extraction options, par=\meta{options}, + desc={no default, initially: see below}}, + key={name=tex extraction script, par=\meta{\hologo{TeX} code}, + desc={no default, initially: see below}}, + } + \yadocset{ + before/.style={label prefix=cmd:te}, + index annotation={defined at \hologo{TeX} extraction}, + } + + Together, these keys determine the system call used for invoking the + \hologo{TeX}-based extraction script, \refscript{memoize-extract-one.tex}. + (They were initialized using \pkg{pgfkeys} handler |.initial|, so their + values may be modified by handlers |.prefix|, |.append|, etc.) + + Memoize uses the resulting system call string at the following occasions. + First, it executes it, once for each new extern in the \dmmz file, during the + internal extraction, i.e.\ when it is loaded with package option + \refmmz{extract}|=|\refmmz{extract=tex}. Second, it uses it to construct + record files of types \refmmz{record=sh}, \refmmz{record=bat} and + \refmmz{record=makefile} when recording of these types is requested. + + Key \refmmz{tex extraction command} sets the \hologo{TeX} binary used for + \hologo{TeX}-based internal extraction. By default, the \hologo{pdfTeX} + engine |pdftex| is used; other sensible values for this key are |luatex| and + |xetex|, or the program name including the path. Note that shell escape mode + must be configured appropriately, and that + \refscript{memoize-extract-one.tex} must be compiled with the + \hologo{plainTeX} format. + + The value of key \refmmz{tex extraction options} is passed as options to the + \hologo{plainTeX} binary. As shown in the initial value in the frame below, + it may use the temporary macro \docaux{cmd}{externbasepath}, which expands to + the path to the extern without the |.pdf| suffix. This macro is available + during internal \hologo{TeX}-based extraction and during the execution of + \refmmz[show keypath]{record/record type/new extern} key for and \meta{record + type}. + + \makeexcerpt{tex-extraction-options} + \tcbinputexample[.tex][.excerpt]{% + listing only, one file, no attachment, title={The initial value of + \refmmz{tex extraction options}}, + } + + Key \refmmz{tex extraction script} defines the command-line script executed + to extract the extern. The default value, shown in the frame below, invokes + \refscript{memoize-extract-one.tex} after setting its parameter macros. + Temporary macros \docAux{cmd={name=pagenumber}, comma, cmd={name=expectedwidth}, + and, cmd={name={expectedheight}}} are defined at the same occasions as + \refcmd{te:externbasepath} above, i.e.\ during internal \hologo{TeX}-based + extraction and during the execution of a \refmmz{record/record type/new + extern} key. The initial value requests the extern to be of the same version as + the main document, if possible;\footnote{As far as I know, it is impossible + to access the version of the PDF being produced in \hologo{XeTeX}, i.e.\ + there are no registers \docref{reg:pdfmajorversion} and + \docref{reg:pdfminorversion}. To request production of a specific version of + PDF, \hologo{XeTeX} must be invoked by with command-line option + \code{-output-driver 'xdvipdfmx -V $N$'}.} note that Memoize defines + \docref{reg:pdfmajorversion} and \docref{reg:pdfminorversion} in \hologo{LuaTeX}. + + \makeexcerpt{tex-extraction-script} + \tcbinputexample[.tex][.excerpt]{% + listing only, one file, title=The initial value of \refmmz{tex extraction + script}, no attachment} + + As the value of \refmmz{tex extraction script} is fully expanded when used, + the initial value shown above must prevent the expansion of much code. + Furthermore, the initial value varies with the \hologo{TeX} format, as + indicated by the |.dtx| guards in the definition of + \refcmd{meo:warningtemplate}. +\end{doc} + +\begin{doc}{ + script={ + head prefix={% + (\texttt{pdf}\alt\texttt{lua}\alt\texttt{xe})\texttt{tex -jobname} + \meta{extern filename} + \texttt{"}\meta{parameters} + \cs{input}\texttt{\textbraceleft} + }, + name=memoize-extract-one.tex, + head infix=\texttt{\textbraceright"}, + }, + } + Compiling \refscript{memoize-extract-one.tex} with \hologo{plainTeX} + produces an extern file containing a single (extern) page extracted from the + document PDF. + + Memoize invokes this script, once for each new extern appearing in the \dmmz + file, when loaded with package option + \refmmz{extract}|=|\refmmz{extract=tex}. + + The desired \meta{extern filename} is given as the value of option |-jobname| + of the \hologo{TeX} binary. To set the extraction \meta{parameters}, define + the following macros before |\input|ting the file: + \yadocset{ + index annotation=option of \texttt{memoize-extract-one.tex}, + before/.style={head prefix=\cs{def}, label prefix=cmd:meo}, + } + \begin{doc}{cmd={name=fromdocument, parameters=\marg{document pdf filename}}} + Defining this macro sets the filename of the PDF which the externs will be + extracted from. The filename is relative to the working directory. + \end{doc} + \begin{doc}{cmd={name=pagenumber, parameters=\marg{number}}} + Defining this macro sets the number of the page to extract. The first page + has number 1. + \end{doc} + \begin{doc}{ + cmd={name=expectedwidth, parameters=\marg{dimension}, desc=optional}, + cmd={name=expectedheight, parameters=\marg{dimension}, desc=optional}, + } + Defining these macros sets the expected width and height of the extracted + page. + + To guard against extracting a wrong page, the dimensions of the extracted + page are compared against the expected width and height. If the size check + fails,\footnote{The match need not be exact, see + footnote~\ref{fn:tolerance}.} the resulting extern PDF is empty (which + counts as non-existent when Memoize checks for its presence when it + attempts to utilize it), and a warning message (formatted via + \refcmd{meo:warningtemplate}) is printed to the log file, if logging was + requested via \refcmd{meo:logfile}. + + If any of these macros is undefined, the size check will be skipped. + \end{doc} + \begin{doc}{cmd={name=logfile, parameters=\marg{filename}, desc=optional}} + Defining this macro sets the name of the log file. If not defined, no log + file will be produced. + + The log file is intended to be used when the script is invoked from an + outer \hologo{TeX} compilation. In particular, it is intended to be + |\input| by that compilation to see whether the extraction was successful. + Upon a failed size check, it will contain a warning (formatted by + \refcmd{meo:warningtemplate}, if that macro is defined). The log file ends + with |\endinput| to signal that extraction actually took place. + \end{doc} + \begin{doc}{cmd={name=warningtemplate, parameters=\marg{code}, desc=optional}} + Defining this macro determines how to log the warning message in the case + of a failed size check. The macro should expand to a \hologo{TeX} + format-specific warning message code containing the warning text given in + \docAux{cmd={name=warningtext, label prefix=cmd:meo, into + index=false}}.\footnote{Macro \refcmd{meo:warningtemplate} is passed + the warning text by a macro rather than a formal parameter to avoid + category code problems with the parameter character when setting key + \refmmz{tex extraction script}.} + + While the script formats the warning message \emph{text} on its own (``I + refuse to extract page \dots''), the warning message is not written into + the log unadorned. The log file is intended to be \cs{input} by the outer + \hologo{TeX} compilation, and the idea is that inputting it should yield a + warning in that compilation (in the case of a failed size + check). Therefore, the content of the log file must contain an invocation + of the command used to produce warning messages in the \hologo{TeX} format + used by the outer compilation. + + For example, when this script is invoked from within a \hologo{LaTeX} + compilation, it makes sense to define something like + |\def\warningtemplate{\PackageWarning{memoize}{\warningtext}}|. + \end{doc} + \begin{doc}{cmd={name=force, + parameters=\bracestt{\texttt{true}\Alt\texttt{false}}, desc=optional}} + If this macro is defined to |true|, extern extraction will be carried out + even if the size-check fails. The failure will still be logged. + \end{doc} + \begin{doc}{ + cmd={name=mmzpdfmajorversion, parameters=\marg{number}, desc=optional}, + cmd={name=mmzpdfminorversion, parameters=\marg{number}, desc=optional}, + } + Defining (one or both of) these macros requests that the extern PDF be + produced with the given major\slash minor PDF version, i.e.\ the extraction + script will set registers \docref{reg:pdfmajorversion} and + \docref{reg:pdfminorversion}. + \end{doc} + + After extracting the extern, the script will end the compilation, i.e.\ + intentionally, only one page documents can be produced. +\end{doc} + +\subsubsection{The clean-up scripts} +\label{sec:cleanup-scripts} + +\begin{doc}{ + script={name=memoize-clean.pl, par=[\meta{options}] [\meta{name}\texttt{.mmz} \dots]}, + script={name=memoize-clean.py, par=[\meta{options}] [\meta{name}\texttt{.mmz} \dots]}, + } + This script removes memo and extern files whose filenames start with + \meta{path prefix}es mentioned in the given \dmmz files or by the + \refscript{memoize-clean.pl--prefix} option. Unless option + \refscript{memoize-clean.pl--all} is given, the script only deletes the + \emph{stale} files, i.e.\ the files not mentioned in any of the given \dmmz + files. + + A \meta{path prefix} of a memo or an extern is what was set by key + \refmmz{path}, or more commonly, one of the shortcut keys \refmmz{memo dir} + and \refmmz{no memo dir}; see section~\ref{sec:ref:dirs} for details on the + form of a memo/extern filename. + + In detail, the script scans the given \dmmz files for occurrences of + \refcmd{mmzPrefix}, and adds their \meta{path prefix} arguments to the list + of prefixes given on the command line by option + \refscript{memoize-clean.pl--prefix}; a \meta{path prefix} occurring in some + \dmmz file is interpreted relatively to the location of the \dmmz file. The + script removes all files whose full pathname (relative to the current + directory) matches pattern \meta{path + prefix}\meta{md5sum}(|-|\meta{md5sum})(|.memo|\alt(|-|$N$)|.pdf|\alt|.log|),% + \footnote{The |.log| files are produced by the \hologo{TeX}-based extraction + script.} except those which occur as the \meta{filename} argument to one + of \refcmd{mmzUsedCMemo}, \refcmd{mmzUsedCCMemo}, \refcmd{mmzUsedExtern}, + \refcmd{mmzNewCMemo}, \refcmd{mmzNewCCMemo} and \refcmd{mmzNewExtern} in one + of the \dmmz files. + + The script is fairly paranoid. It refuses to delete anything if a \dmmz file + is malformed in any way (but not if it doesn't exist or is completely empty, + which facilitates its usage in clean-up scripts), or if it would remove a + file not residing under the current directory. Before removing the files, it + lists the files to be removed and asks for confirmation. + + Functionally, the Perl (|.pl|) and the Python (|.py|) version are completely + equivalent. + + \yadocset{ + of=script:memoize-clean.pl, + override/.style={ + index annotation={option of {% + \hypercolor{link}{gray}% + \hyperref[script:memoize-clean.pl]{\texttt{memoize-clean}}% + }}, + }, + } + \begin{doc}{ + option={name=prefix, short name=p, par=\meta{path prefix}} + } + Add \meta{path prefix} to the list of prefixes; the given prefix is + relative to the current directory. This option may be given multiple + times. + \end{doc} + \begin{doc}{ + option={name=all, short name=a} + } + When given this option, the script removes \emph{all} memos and externs + belonging to the document, not just the stale ones, i.e.\ it effectively + ignores the occurences of \refcmd{mmzUsedCMemo} and friends in the \dmmz + file. + \end{doc} + \begin{doc}{ + option={name=yes, short name=y} + } + When given this option, the script does not ask for confirmation before + removing the files. + \end{doc} + \begin{doc}{ + option={name=quiet, short name=q} + } + Normally, the script prints what it is doing to the standard output; in + particular, it prints out the filename of each file as it is deleting it. + This option disables this behaviour. + \end{doc} + \begin{doc}{ + option={name=help, short name=h} + } + Show help. + \end{doc} + \begin{doc}{ + option={name=version, short name=V} + } + Show Memoize version. + \end{doc} +\end{doc} + +\subsubsection{Record files} +\label{sec:ref:record-files} + +\begin{doc}{ + key={name=record, par=\marg{record type}, + desc={cumulative, initially \refmmz{record=mmz}, no default}}, + key={name=no record}, + } + Memoize records which externs were produced and used in the compilation, + producing a record file of every type found in the record-type list. These + keys add \meta{record type} to the record-type list, or clear this list. See + section~\ref{sec:record-files} for details. + + Note that passing an undefined \meta{record type} to this key will not yield + an error. + + Out of the box, the following \meta{record type}s are recognized: + \yadocset{of=key:/mmz/record} + \begin{doc}{value={name=mmz}} + This record type produces a \dmmz file recording new/used + externs/c-memos/cc-memos and changes in the \refmmz{path} prefix to these + files; see section~\ref{sec:.mmz} for details. + + The produced file is named \docAux{file={name=mmz, + name prefix=\meta{jobname}., + ref prefix=., + index prefix=., + }}. This name cannot be changed. + + The \dmmz file is a \hologo{TeX} file, but uses only a simple subset of + the \hologo{TeX} syntax, to be easily parsable by the external scripts such + as \refscript{memoize-extract.pl}. Each line of the file consists of a + (possibly commented) invocation of one of the commands listed below; the + final line is |\endinput|. The \meta{path prefix} below consists of the + path to memos/externs and the immutable \refmmzpath{prefix} of their filename. + \begin{doc}{ + cmd={name=mmzUsedCMemo, par=\marg{filename}}, + cmd={name=mmzUsedCCMemo, par=\marg{filename}}, + cmd={name=mmzUsedExtern, par=\marg{filename}}, + } + Record that the (c)c-memo or extern residing in file \meta{filename} was + utilized. + \end{doc} + \begin{doc}{ + cmd={name=mmzNewCMemo, par=\marg{filename}}, + cmd={name=mmzNewCCMemo, par=\marg{filename}}, + } + Record that a new (c)c-memo residing in file \meta{filename} was + produced. + \end{doc} + \begin{doc}{cmd={name=mmzNewExtern, par=\marg{filename}\marg{page number}% + \marg{expected width}\marg{expected height}}} + Record that a new extern was produced and dumped as page \meta{page + number} into the document, that it should be extracted into file + \meta{filename}, and that it should be \meta{expected width} wide and + \meta{expected height} high (modulo tolerance of |0.01pt|, see + footnote~\ref{fn:tolerance}), where the height is the total height + comprising both \hologo{TeX} height and depth. + \end{doc} + \begin{doc}{cmd={name=mmzPrefix, par=\marg{path prefix}}} + Record that the \refmmz{path} prefix of memo and extern files was + changed. + \end{doc} + \end{doc} + \begin{doc}{value={name=makefile}} + This record type produces a makefile which, when processed by the |make| + utility, triggers \hologo{TeX}-based extraction of the new externs. + \begin{doc}{ + key={name=makefile, par=\marg{filename}, desc={no default, + initially \texttt{memoize-extract.\jobname.makefile}}} + } + Use this key to change the filename of the produced makefile. + \end{doc} + \end{doc} + \begin{doc}{value={name=sh}, value={name=bat}} + These record types produce a shell script which, when executed, triggers + \hologo{TeX}-based extraction of the new externs. + + Use \refmmz{record=sh} on Unix-like systems, and \refmmz{record=bat} on + Windows. + + \begin{doc}{ + key={name=sh, par=\marg{filename}, desc={no default, + initially \texttt{memoize-extract.\jobname.sh}}}, + key={name=bat, par=\marg{filename}, desc={no default, + initially \texttt{memoize-extract.\jobname.bat}}} + } + Use these keys to change the filename of the produced shell script. + \end{doc} + \end{doc} +\end{doc} + +\begin{doc}{ + desc=definable, + keypath=/mmz/record/\meta{record type}, + keypath label=/mmz/record/record type, + name prefix={\meta{record type}/}, ref prefix=, + index annotation={key in \texttt{/mmz/\meta{record type}}}, + key={name=begin}, + key={name=prefix, par=\marg{path prefix}}, + key={name=new extern, par=\marg{filename}}, + key={name=new cmemo, par=\marg{filename}}, + key={name=new ccmemo, par=\marg{filename}}, + key={name=used extern, par=\marg{filename}}, + key={name=used cmemo, par=\marg{filename}}, + key={name=used ccmemo, par=\marg{filename}}, + key={name=end}, + } + A new record type can be implemented by defining these keys in keypath + \docAux{key path={name=mmz/record/\meta{record type}, label=mmz/record/record + type}} (using the standard \pkg{pgfkeys} handlers such as |.code| and + |.style|). The keys are invoked by Memoize where appropriate if recording + for the defined type is activated by \refmmz{record}|=|\meta{record type}, + just as for the predefined types. Only those keys which are required for + implementing the desired functionality need to be defined. + + The following macros are available during the execution of key + \refmmz{record/record type/new extern}: + \yadocset{before/.style={label prefix=cmd:ne}, + index annotation=defined at \refmmz{record/record type/new extern}} + \begin{doc}{cmd={name=pagenumber}} + This macro holds the number of the extern page. + \end{doc} + \begin{doc}{ + cmd={name=expectedwidth}, + cmd={name=expectedheight}, + } + These macros hold the width and the height of the extern page. + \end{doc} + \begin{doc}{cmd={name=externbasepath}} + This macro holds the filename of the extern, minus the |.pdf| suffix (but + including the path leading to the extern). + \end{doc} +\end{doc} + + + +\subsection{Automemoization} +\label{sec:ref:advicememoization} + + +\subsubsection{Package Advice} +\label{sec:ref:advice} + +Package Advice is a namesake of \Emacs's Advice. As such, it implements a +generic framework for extending the functionality of selected commands and +environments. Each \emph{advised} command and environment is assigned a piece +of \emph{advice} --- a command which is executed instead of the advised command +and environment, and which may, or may not, invoke the original command or +environment during its execution. The package offers an elegant way of +declaring advice, setting up the conditions upon which the advised command will +actually be replaced by the advice, collecting the arguments of the advised +command and invoking it, and (de)activating the advice. + +Before the advising framework can be used, it must be installed into a selected +\pkg{pgfkeys} keypath (multiple installations into different keypaths are +allowed, even if they handle the same commands). Memoize installs the +framework into keypath \refkeypath{/mmz} and (primarily) uses it to +automatically memoize the results of compilation of selected commands and +environments. + +\begin{doc}{ + keypath={/handlers}, + key={name=.install advice, + sort index=install advice, + par=\marg{configuration keylist}, + }, + } + This key is a \pkg{pgfkeys} key handler (see \PGFmanual{87.3.5}) which + installs the advising framework into the keypath which it was invoked from + --- henceforth, the \meta{namespace}. + + For example, \code{\cs{pgfkeys}\bracestt{/my/\refkey{/handlers/.install + advice}}} installs the framework into keypath |/my|. + + Argument \meta{configuration keylist} may contain the following keys: + \yadocset{keypath=/advice/install} + \begin{doc}{key={ + name=setup key, + parameters=\marg{name}, + description={no default, initially \texttt{advice}}}, + } + This key determines the names of the user-interface keys used to setup + advice for commands and environments in \meta{namespace}. + + The keys whose names are determined by this key are the following: + \meta{name}, \code{\meta{name} csname}, \code{\meta{name} key}, + \code{\meta{name}'}, \code{\meta{name} csname'} and \code{\meta{name} + key'}. Memoize sets \refkey{/advice/install/setup key}|=auto|, and + thereby defines \refmmz{auto}, \refmmz{auto csname}, \refmmz{auto key}, + \refmmz{auto'}, \refmmz{auto csname'} and \refmmz{auto key'}. + \end{doc} + \begin{doc}{key={ + name=activation, + par=\meta{initial activation type}, + desc={no default, initially \texttt{immediate}} + }, + } + This key sets the \meta{initial activation type} for \meta{namespace}. + + At the end of the installation, the system will execute + \meta{namespace}|/|\refmmz{activation}|=|\meta{initial activation type}; + consequently, \meta{initial activation type} must be one of + \refmmz{activation=immediate} and \refmmz{activation=deferred}. In + Memoize, the \meta{initial activation type} is + \refmmz{activation=deferred}. + + Setting the \refmmz{activation} type during the installation only matters + in \hologo{LaTeX}, where the installation ends by advising \refcmd{begin} + to implement advising of environments. + \end{doc} + + \begin{tcolorbox}[warning] + Writing the documentation for Advice, I was faced with a dilemma. Should + the documentation reflect the fact that the full names of keys defined by + the package depend on the installed instance of the framework, in + particular on \meta{namespace} and \meta{setup key}? For example, should + the reference headers contain things like \meta{namespace}|/activate| and + \meta{namespace}|/|\meta{setup key} |csname|? In my opinion, this would + make the reference hard to read, so I decided to have the reference headers + refer to the Advice keys of the Memoize installation, where + \meta{namespace}=|/mmz| and \meta{setup key}=|auto|, resulting in + friendlier headers such as \refkeypath{/mmz}|/|\refmmz{activate} and + \refkeypath{/mmz}|/|\refmmz{auto csname}. (Consequently, it also made + sense to document Advice within the Memoize documentation.) + + The bottomline: if you're reading this section with a non-Memoize + installation in mind, you have to mentally replace any \refkeypath{/mmz} + and \refmmz{auto} in the reference headers with the \meta{namespace} and + the \meta{setup key} selected by that installation. + (Section~\ref{sec:ref:advice:memoization} is another matter. Keys + described there are only available in Memoize.) + \end{tcolorbox} + + In more detail, key handler \refkey{/handlers/.install advice} performs the + following actions (as explained in the box above, we assume that the + advising framework was installed into keypath \refkeypath{/mmz} with the + setup key named |auto|): + \begin{itemize} + \item \edef\origtolerance{\the\tolerance}\tolerance=1000 + It defines the following keys in keypath \refkeypath{/mmz}: + \refmmz{auto}, + \refmmz{auto csname}, + \refmmz{auto key}, + \refmmz{auto'}, + \refmmz{auto csname'}, + \refmmz{auto key'}, + \refmmz{activation}, + \refmmz{activate deferred}, + \refmmz{activate}, + \refmmz{deactivate}, + \refmmz{activate csname}, + \refmmz{deactivate csname}. + \refmmz{activate key}, + \refmmz{deactivate key}. + \refmmz{force activate}, + \refmmz{try activate}. + \item + It defines the following keys in keypath \refkeypath{/mmz/auto}: + \refmmzauto{run conditions}, + \refmmzauto{outer handler}, + \refmmzauto{bailout handler}, + \refmmzauto{collector}, + \refmmzauto{args}, + \refmmzauto{collector options}, + \refmmzauto{clear collector options}, + \refmmzauto{raw collector options}, + \refmmzauto{clear raw collector options}, + \refmmzauto{inner handler}, + \refmmzauto{options}, + \refmmzauto{clear options}, + \refmmzauto{reset}. + \item \tolerance\origtolerance\relax + It defines the |.unknown| key handler for \refkeypath{/mmz/auto}. + This handler appends any unknown keys (and their values) to + \refmmzauto{options}. + \item It executes \refmmz[show keypath]{activation}|=|\meta{initial + activation type}. + \item In \hologo{LaTeX}, it submits \refcmd{begin} to advising, thereby + enabling environment support in this format. Consequently, advising of + environments can switched off by writing \refmmz{deactivate}|=\begin|. + \end{itemize} +\end{doc} + +\paragraph*{The keys installed into keypath \metabf{namespace}} are used to +declare and (de)activate advice. In the documentation in this subsection, +we assume that \meta{namespace}=\refkeypath{/mmz} and that \meta{setup + key}=\refmmz{auto}. In particular, this also applies to the reference +headers. + +\begin{doc}{ + key={name=activation, par=\docAux{of=key:/mmz/activation, value={name=immediate}, + text=\Alt, value={name=deferred}}, desc={style, no default}}, + key={name=activate deferred, desc={style}}, + } + Key \refmmz{activation} selects the activation regime. Under the + \refmmz{activation=immediate} regime, keys \refmmz{activate}, + \refmmz{deactivate}, \refmmz{force activate} and \refmmz{try activate} behave + as described in their documentation below. Under the + \refmmz{activation=deferred} regime, however, those keys are not executed; + rather, their invocations are appended to style \refmmz{activate deferred}. + For example, writing \refmmz{activate}|=\foo| in the deferred activation + regime appends \refmmz{activate}|=\foo| to \refmmz{activate deferred}. It is + up to the user if and when to execute the keys collected in \refmmz{activate + deferred}; see the documentation of \refmmz{manual} to learn what Memoize + does with the contents of this style. +\end{doc} + + +\begin{doc}{easy, + par=\marg{list of commands and/or environments}, + desc={style}, + key={name=activate}, + key={name=deactivate}, + } + These keys activate or deactivate the advice for the given commands and + environments. When the advice is activated, it replaces the advised command; + when it is deactivated, the command is reverted to its original definition. + + In Memoize, these keys are most commonly used to activate or deactivate + automemoization for the given commands or environments. For example, write + \refmmz{deactivate}|={\tikz,tikzpicture}| to deactivate automemoization of + \TikZ pictures (which is declared and active by default). The curly braces + may be omitted if the list contains a single command or environment, e.g.\ + \refmmz{deactivate}|=\tikz| or \refmmz{deactivate}|=tikzpicture|. + + (De)activation of a piece of advice is completely orthogonal to its declaration with + \refmmz{auto}. For example, there is no need to deactivate a command before + redeclaring its advice, and reactivate it afterwards. A command may be + activated even before declaring its advice --- however, the command itself + must be defined at the time of activation. + + As the advice is normally automatically activated upon declaration with + \refmmz{auto}, explicit activation is rarely needed, but see \refmmz{auto'}. + The effect of these keys under the deffered activation regime is described in + \refmmz{activation}. + + Note that I sometimes speak of (de)activating a command, and sometimes of + (de)activating its advice. I mean the same thing. +\end{doc} + +\begin{doc}{ + par=\marg{control sequence name}, + desc={style}, + key={name=activate csname}, + key={name=deactivate csname}, + } + These keys activate and deactivate a command given by its \meta{control + sequence name}; for example, \refmmz{activate csname}|=foo| is equivalent + to \refmmz{activate}|=\foo|. Note that unlike the regular \refmmz{activate} + and \refmmz{deactivate}, their |csname| variants only accept a single command + at a time (otherwise, including a comma in the command name would be + impossible). +\end{doc} + +\begin{doc}{ + par=\marg{list of full key names}, + desc={style}, + key={name=activate key}, + key={name=deactivate key}, + } + These keys activate and deactivate |pgfkeys| keys. Note that \emph{full} key + names must be given, i.e.\ the names must include the keypath. + + Under the hood, these keys merely execute \refmmz{activate} and + \refmmz{deactivate} on the internal macros corresponding to the given keys. +\end{doc} + +\begin{doc}{ + key={name=try activate, conditional=false}, + } + When this conditional is set to true, \refmmz{activate} will not yield an + error if the advice is already activated, and \refmmz{deactivate} will not + yield an error if the advice is not yet activated. + + This key applies to the next, and only to the next, invocation of key + \refmmz{activate} or \refmmz{deactivate}, i.e.\ it is reset back to |false| + after invoking \refmmz{activate} or \refmmz{deactivate}. +\end{doc} + +\begin{doc}{ + key={name=force activate, conditional=false}, + } + When this conditional is set to true, \refmmz{activate} will activate even a + previously activated command, provided that, additionally, the command has + been redefined since the prior activation. + + In more detail, the original definition of the advised command is saved upon + activation (to provide the possibility of both deactivation and the usage of + the original command by the handler). Consequently, activation of an already + activated command would result in the saved original definition being + overwritten by the redefinition made during the first activation. However, + if the handled command was meanwhile redefined by a third party, reactivation + makes sense, under the assumption that the former original definition is + obsolete and should be replaced by the (third party) redefinition. As a + safeguard, however, \refmmz{activate} requires such reactivation to be + explicitly requested using conditional \refmmz{force activate}.\footnote{A + potential problem, not (yet) addressed, is that the third party might be + another incarnation of the advising framework. In this case, forced + reactivation will result in the loss of the original command and a circular + dependency between the two pieces of advice.} + + This key applies to the next, and only to the next, invocation of key + \refmmz{activate}, i.e.\ it is reset back to |false| after invoking + \refmmz{activate}. This key does not apply to \refmmz{deactivate}. +\end{doc} + +\begin{doc}{easy, + key={name=auto, par=\marg{command or environment}{\marg{keylist}}, desc=style}} + + This key sets up the advice for the given command or environment, or updates + the configuration of an existing piece of advice. + + In Memoize, this key is most commonly used to submit a command or environment + to automemoization. For an environment (say, |bar|), it suffices to write + \refmmz{auto}|=|\bracestt{bar}\bracestt{\refmmzauto{memoize}}; for a command, + we usually need to include its argument specification: + \refmmz{auto}|=\foo|\bracestt{\refmmzauto{memoize}, + \refmmzauto{args}=\bracestt{...}}. Another common usage is to prevent + memoization during the execution of a command or environment: + \refmmz{auto}|=|\bracestt{bar}\bracestt{\refmmzauto{nomemoize}}. For + details, see sections~\ref{sec:tut:automemoization}, + \ref{sec:tut:working-on-a-picture} and~\ref{sec:tut:verbatim}. + + The advice is configured by the given \meta{keylist}, which is executed with + the default keypath set to \docaux{key path}{mmz/auto}. Any unknown keys in + \meta{keylist} are passed on to key \refmmzauto{options}; for example, a + plain \refmmz{verbatim} or \refmmz{padding}|=2in| have the same effect as + \refmmzauto{options}|=|\refmmz{verbatim} or + \code{\refmmzauto{options}=\braces{\refmmz{padding}=2in}}. + + This key automatically activates the declared advice, unless it is already + activated; under the deferred activation regime, the automatic activation is + deferred as well. Use variant \refmmz{auto'} when you don't want to + automatically activate the advice. + + When this key is used on a command or environment with an existing piece of advice, + the advice is merely updated. This makes it easy to, for example, + temporarily switch to verbatim collection of an environment in Memoize: + \refmmz{auto}|=|\bracestt{tcolorbox}\braces{\refmmz{verbatim}}. Use key + \refmmzauto{reset} to setup the advice from scratch: + \refmmz{auto}|=|\bracestt{...}\bracestt{\refmmzauto{reset}, ...}. + + A piece of advice consists of several interlocked components, declared by keys + residing in path \refkeypath{/mmz/auto}: \refmmzauto{run conditions}, + \refmmzauto{bailout handler}, \refmmzauto{outer handler}, + \refmmzauto{collector} and \refmmzauto{inner handler}. During the execution + of the advice, these components are available through the following macros: + \refcmd{AdviceRunConditions}, \refcmd{AdviceBailoutHandler}, + \refcmd{AdviceOuterHandler}, \refcmd{AdviceCollector} and + \refcmd{AdviceInnerHandler}. These macros are also defined during setup, and + it is possible to change the configuration by modifying them directly; in + that case, it likely also makes sense to use the low-level variant of this + key, macro \refcmd{AdviceSetup}. + + Control sequences used in the advice components do not need to be defined at + the time of invoking key \refmmz{auto}, or activating the advice; they must + only be defined at the time the advice is actually executed. It is thus + perfectly fine to declare \refmmzauto{inner handler}|=\myinnerhandler| before + defining |\myinnerhandler|, or to redefine |\myinnerhandler| between the + invocations of the advised command. + + This key configures not only the components, but also the options of the + advice. These options are set by keys \refmmzauto{args}, + \refmmzauto{collector options}, \refmmzauto{raw collector options} and + \refmmzauto{options}. Whether these options are used or not depends on the + advice components. (Same as the components, the options have their + corresponding low-level macros: \refcmd{AdviceArgs}, + \refcmd{AdviceCollectorOptions}, \refcmd{AdviceRawCollectorOptions} and + \refcmd{AdviceOptions}.) + + Parameter symbols (i.e.\ |#|) are not allowed in advice settings. + + A command or environment may be submitted to several instances of the + advising framework, i.e.\ instances installed under different keypaths. The + effect of such chained advice depends on the order of activation. If advice + $A$ is activated before advice $B$, it will also be applied before $B$. + + The advice setup takes place in a group. Use key \refmmzauto{after setup} to + execute code outside this group. + + In general, the name of this key equals whatever was submitted to + \refkey{/advice/install/setup key} during the installation of the advising + framework via \refkey{/handlers/.install advice}. +\end{doc} + +\begin{doc}{ + key={name=auto csname, par=\marg{control sequence name}{\marg{keylist}}, desc=style} + } + This key is a variant of \refmmz{auto}, but with the command of the first + argument given as a control sequence name, i.e.\ \refmmz{auto + csname}|={foo}{...}| is equivalent to \refmmz{auto}|=\foo{...}|. +\end{doc} + +\begin{doc}{ + key={name=auto key, par=\marg{full key}{\marg{keylist}}, desc=style}, + } + This key is a variant of \refmmz{auto}, but it works with \pkg{pgfkeys} keys. + The first argument should be a \meta{full key} like |/tcb/float|, i.e.\ it + must consist of both the keypath and the keyname. + + This key sets up advice for the internal command corresponding to the given + \meta{full key}, and also properly initializes the collector, so that + \refmmzauto{inner handler} will ``just work.'' +\end{doc} + +\begin{doc}{ + key={name=auto', par=\marg{command or environment}{\marg{keylist}}, desc=style}, + key={name=auto csname', par=\marg{control sequence name}{\marg{keylist}}, desc=style}, + key={name=auto key', par=\marg{full key}{\marg{keylist}}, desc=style}, + } + These keys are variants of \refmmz{auto}, \refmmz{auto csname} and + \refmmz{auto key} which do not attempt to activate the command after setting + it up. +\end{doc} + +\begin{doc}{ + cmd={ + name=AdviceSetup, + par=\marg{namespace}\marg{command or environment}{\marg{setup code}} + }} + This macro is the low-level variant of key \refmmz{auto}. The differences + between the two are the following: + \begin{itemize} + \item An invocation of the macro must provide the namespace (i.e.\ the + installation keypath) as the first argument. + \item There is no automatic activation at the end of the setup. + \item The final argument should not be a keylist (of keys belonging to + \refkeypath{/mmz/auto}) but \hologo{TeX} code adjusting the contents of the + settings macros \refcmd{AdviceRunConditions}, \refcmd{AdviceBailoutHandler}, + \refcmd{AdviceOuterHandler}, etc. For the full list of available macros, see + the documentation of their corresponding keys below; the setting macros are + mentioned at the end of each entry. + \end{itemize} +\end{doc} + +\begin{doc}{ + cmd={name=AdviceTracingOn}, + cmd={name=AdviceTracingOff}, + } + Advice tracing is initially off. When it is on, Advice will show (on the + terminal and in the |.log| file) which advice components are executed, and + what arguments and options they have received. +\end{doc} + +\paragraph*{The keys installed into keypath \metabf{namespace}\texttt{/}\metabf{setup key}} +are used to configure advice. They may only occur within the second +argument of the setup key. In the documentation in this subsection, we assume +that \meta{namespace}=\refkeypath{/mmz} and that \meta{setup + key}=\refmmz{auto}. In particular, this also applies to the reference +headers. + +\begingroup +\yadocset{keypath=/mmz/auto} + +\begin{doc}{% + key={name=run conditions, par=\meta{\hologo{TeX} code}, + desc=initially and default: \refcmd{AdviceRuntrue}}, + } + + This key declares the \meta{control sequence} as the run conditions component + of the advice. + + The run conditions macro is executed at the very start of the advice. Its + function is to decide whether we should proceed to advise the command by + executing the outer handler, or execute the original command (after invoking + the bailout handler). + + The run conditions macro should take no arguments. If it determines that the + run conditions are satisfied, it should set the \hologo{TeX} conditional + \docaux{cmd}{ifAdviceRun} to true by executing \docaux{cmd}{AdviceRuntrue}. + There is no need to execute \docaux{cmd}{AdviceRunfalse} when the run + conditions are not satisfied. + + Initially, the run conditions are set to \refcmd{AdviceRuntrue}, translating to + ``always run.'' For two non-trivial examples, see \refmmzauto{run if + memoization is possible} and \refmmzauto{run if memoizing}. Executing this + key without a value restores it to the initial value. + + During advising and advice setup, the run conditions of the advised command + are accessible through \docaux{cmd}{AdviceRunConditions}, a parameterless + macro expanding to the given \meta{\hologo{TeX} code}. +\end{doc} + +\begin{doc}{% + key={name=bailout handler, par=\meta{\hologo{TeX} code}, + desc=initially and default: \cs{relax}}, + } + + This key declares the \meta{\hologo{TeX} code} as the bailout handler + component of the advice. + + The bailout handler is executed when the run conditions are not met, just prior + to executing the original definition of the advised command. The bailout + handler should take no arguments. + + The initial bailout handler, |\relax|, does nothing. Memoize defines and + uses a bailout handler which clears the next-options. Executing this key + without a value restores it to the initial value. + + During advising and advice setup, the bailout handler of the handled command is + accessible through \docaux{cmd}{AdviceBailoutHandler}, a parameterless macro + expanding to the given \meta{\hologo{TeX} code}. +\end{doc} + +\begin{doc}{ + key={name=outer handler,par=\meta{\hologo{TeX} code}, + desc=initially and default: see below}, + } + + This key declares the \meta{\hologo{TeX} code} as the outer handler component + of the advice. + + The outer handler can be safely imagined as the command which replaces the + handled command. This also holds for handled environments, but with a + caveat: for a \hologo{plainTeX} or \hologo{ConTeXt} environment |foo|, the + outer handler replaces |\foo| and |\startfoo|, respectively; in the case of a + \hologo{LaTeX} environment, it replaces |\begin{foo}|.%] + + The outer handler is the first component which has the opportunity to inspect + the arguments given to the handled command. It is invoked just in front of + these arguments (which are, in case \hologo{TeX} hasn't seen them yet, + untokenized), and while it is expected that the advice will consume + the same arguments as the advised command itself would, how precisely that + happens may vary from situation to situation. In particular, the argument + structure of the outer handler is not prescribed. + + In fact, the outer handler has complete control over the remainder of the + advising process. In situations where advising requires knowledge of the + advised command's arguments as a whole, the outer handler executes the + collector, which in turn invokes the inner handler, which does the real work; + see \refmmzauto{memoize} for the usage case which inspired this design. + Sometimes, however, it is the outer handler which does the real work (and + there is thus no inner handler). This is the case in situations when the + arguments of the handled command are irrelevant for the functioning of the + advice, or when the advice needs to inspect some individual argument of the + handled command; for examples of such situations, see \refmmzauto{abort} and + \refmmzauto{ref}. + + To reiterate the argument situation of the outer handler, it sees the + arguments of the handled command as they were given. The arguments are + \emph{not} collected before invoking the outer handler --- in fact, avoiding + the argument collection is the raison d'être of the outer handler! (In the case + of an advised environment, the environment body can be seen as an argument of + \pkg{xparse} type \docref{xparse:+}\docref{xparse:b}.) + + The outer handler (and any other component of the advice it invokes) has + access to the following auxiliary macros, defined by the framework: + \begin{itemize} + \item \tolerance 1000 the macros holding the configuration of the advised + command, as set up by \refmmz{auto}: \refcmd{AdviceRunConditions}, + \refcmd{AdviceBailoutHandler} and \refcmd{AdviceOuterHandler} are probably + useless, as they refer to components already invoked, but the remaining + components (\refcmd{AdviceCollector} and \refcmd{AdviceInnerHandler}) and + their options (\refcmd{AdviceArgs}, \refcmd{AdviceCollectorOptions}, + \refcmd{AdviceRawCollectorOptions} and \refcmd{AdviceOptions}) should be + commonly used. + \item the macros holding information about the namespace and the advised + command or environment: \refcmd{AdviceNamespace}, \refcmd{AdviceName}, + \refcmd{AdviceReplaced} and \refcmd{AdviceOriginal}. (Command + \refcmd{AdviceGetOriginal} might also be useful, although using + \refcmd{AdviceOriginal} will likely be more practical.) + \end{itemize} + + This key is initially set to an internal control sequence which merely + invokes the collector by executing \refcmd{AdviceCollector}; in other words, + the initial outer handler leaves all the work to the collector and the inner + handler. There is no need to specifically set up the outer handler when + using the inner handler. Executing this key without a value restores it to + the initial value. + + During advising and advice setup, the outer handler of the advised command is + accessible through \docaux{cmd}{AdviceOuterHandler}, a parameterless macro + expanding to the given \meta{\hologo{TeX} code}. +\end{doc} + +\begin{doc}{ + key={name=collector,par=\meta{\hologo{TeX} code}, + desc=initially and default: see below}, + } + + This key declares the \meta{\hologo{TeX} code} as the collector component of + the advice. + + The collector, if used, is invoked by the outer handler. It is invoked + immediately in front of the advised command's arguments (which are, in case + \hologo{TeX} hasn't seen them yet, untokenized), and its function is to + collect these arguments and pass them on, as a single argument, to the inner + handler. + + While this manual occasionally states that the initial argument collector is + \refcmd{CollectArguments} of package CollArgs, this is, if we're precise, + incorrect on two counts. For one, the initial collector is not a CollArgs + command, but a macro which acts as the ``bridge'' between Advice + and CollArgs. Second, the initial collector does not really invoke + \refcmd{CollectArguments}, but its cousin, \refcmd{CollectArgumentsRaw}, + which allows Advice (and Memoize) to fine tune its behaviour + using the fast low-level (``programmer interface'') commands rather than the + slower \pkg{pgfkeys} interface; clearly, the latter point also provides + raison d'être for \refmmzauto{raw collector options}. Summing up, this key + is initially set to an internal control sequence which compiles the settings + provided by \refmmzauto{args}, \refmmzauto{collector options} and + \refmmzauto{raw collector options} into an invocation of + \refcmd{CollectArgumentsRaw} of package CollArgs.\footnote{The initial + collector also sets the CollArgs' option \refcollargs{caller} to the + name of the advised command or environment.} Executing this key without a + value restores it to the initial value. + + The above-mentioned collector settings were clearly tailored to suit + \refcmd{CollectArgumentsRaw}. In general, a collector might or might not use + them, and if it does, it may interpret them in any way. For example, Advice + ships with a \refcmd{tikz} collector, \docaux{cmd}{mmzCollectTikZArguments}, which + ignores them completely, as it knows everything about the idiosyncrasies of + that command anyway. Incidentally, \refcmd{mmzCollectTikZArguments} becomes + available upon loading \reffile{advice-tikz.code.tex} (which Memoize does + automatically in the presence of \TikZ). + + The collector has access to the same auxiliary macros as the outer handler. + In particular, it will \emph{have} to use \refcmd{AdviceInnerHandler} (followed + by the braced collected arguments) to invoke the inner handler. + + During advising and advice setup, the collector of the advised command is + accessible through \docaux{cmd}{AdviceCollector}, a parameterless macro + expanding to the given \meta{\hologo{TeX} code}. +\end{doc} + +\begin{doc}{easy, + key={name=args,par=\meta{argument specification}, desc=initially and default: unset}, + } + + This key describes the \meta{argument specification} of the advised command. + + Assuming that the initial value of \refmmzauto{collector} has not been + modified, the given \meta{argument specification} is eventually interpreted + by command \refcmd{CollectArguments} of package CollArgs, which expects an + argument specification in the format specified by package \pkg{xparse}; the + format is summarized in the frame below for convenience, for details, see the + \pkg{xparse} manual. If the specification is not given, the initial collector + assumes that the advised command was defined using \refcmd{NewDocumentCommand} + (or similar) of package \pkg{xparse}, and will attempt to retrieve the argument + specification automatically via \refcmd{GetDocumentCommandArgSpec}. + + \begin{tcolorbox}[float, before float=\hfill, + title={The {\pkg[white]{xparse}} argument specification + (as understood by \refcmd[link color=white]{CollectArguments})}] + \begin{tabularx}{\linewidth}{>{\tt}lX} + \multicolumn{2}{l}{\rm\textbf{Mandatory argument types}}\\ + m&standard (a single token or multiple tokens in braces)\\ + r\meta{token$_1$}\meta{token$_2$}&delimited by + \meta{token$_1$} and \meta{token$_2$} \\ + v&verbatim, in the style of \cs{verb}\\ + b&the body of an environment\\ + [1ex]\multicolumn{2}{l}{\rm\textbf{Optional argument types}}\\ + o&in square brackets\\ + d\meta{token$_1$}\meta{token$_2$}&delimited by + \meta{token$_1$} and \meta{token$_2$}\\ + s&an optional star\\ + t\meta{token}&an optional \meta{token}\\ + e\marg{tokens}&a set of embellishments\\ + [1ex]\multicolumn{2}{l}{\rm\textbf{Weird argument types}}\\ + l&a mandatory argument until the first begin-group token\\ + u\marg{tokens}&\hologo{TeX}'s delimited argument\\ + g&an optional argument inside braces\\ + [1ex]\multicolumn{2}{l}{\rm\textbf{Modifiers}}\\ + +&allow the next argument to be long\\ + !&disallow spaces before arguments of type \docref{xparse:d} and \docref{xparse:t}\\ + >\marg{processor}&process the next argument\\ + [1ex]\multicolumn{2}{l}{\rm\textbf{CollArgs extensions}}\\ + \docref{xparse:b}\marg{name}&set the environment name for this environment\\ + \docref{xparse:amp}\marg{options}&apply CollArgs options to the next argument\\ + \docref{xparse:amp}\docref{xparse:amp}\marg{raw options}&apply raw CollArgs options to the next argument\\ + \end{tabularx} + + \smallskip + + \refcmd{CollectArguments} can grab an argument of any type in the + \refcollargs{verbatim} mode. + + As \refcmd{CollectArguments} does not use the arguments but only collects + them, it does not care about the default values of optional arguments. + Therefore, argument types with defaults (\docref{xparse:O}, + \docref{xparse:D} and \docref{xparse:R}) may be substituted by their + \texttt{-NoValue-} counterparts (\docref{xparse:o}, \docref{xparse:d} and + \docref{xparse:r}) and are therefore not included in the above table. + \end{tcolorbox} + + In general, however, an argument collector may this interpret this setting it + in any way it sees fit --- or not at all. For example, in Memoize the value + of \refmmzauto{args} is ignored for command \refcmd{tikz}, which requires a special + collector (\refcmd{mmzCollectTikZArguments}). + + When setting up advice for a \emph{command}, this key is initially + ``unset,'' i.e.\ it holds a special value indicating that the argument + specification is not provided. Note that this special value is not an empty + string --- \refmmzauto{args}|={}|, or simply \refmmzauto{args}|=|, indicates + a command which takes no arguments. During the execution of the advice, one + may use the \hologo{LaTeX}-style conditional + \docaux{cmd}{AdviceIfArgs}\marg{true branch}\marg{false branch} to test + whether the argument specification was provided. Executing this key without + a value restores it to the initial, unset value. + + When setting up the advice of an \emph{environment}, this key is + initialized to \docref{xparse:+}\docref{xparse:b} (a long environment body), + making it unnecessary to specify this value manually. Note that this holds + even for environments with arguments other that the environment body + ``argument'': those arguments will be caught as the start of the body even if + not explicitly specified. + + During advising and advice setup, the argument specification of the advised + command is accessible through \docaux{cmd}{AdviceArgs}, a parameterless macro + expanding to the given \meta{argument specification}. +\end{doc} + + +\begin{doc}{ + key={name=collector options, par=\marg{keylist}, + desc={cumulative, initially empty, value required}}, + key={name=raw collector options, par=\marg{code}, + desc={cumulative, initially empty, value required}}, + } + These keys append the given value to the list of user-friendly and raw + collector options, respectively. A comma is prefixed to the user-friendly + \meta{keylist} before appending it. + + Both kinds of collector options are intended to be used by the collector, + which may interpret them in any way it sees fit --- or not at all. The + initial collector, which invokes \refcmd{CollectArgumentsRaw} of package + CollArgs, passes both lists to this command, which interprets + \refmmzauto{collector options} as a user-friendly \pkg{pgfkeys} keylist + (which therefore requires a bit of processing) and \refmmzauto{raw collector + options} as plain \hologo{TeX} code (expecting it to contain only the + allowed, ``programmer's interface'' macros).\footnote{Clearly, + \refmmzauto{raw collector options} are why Advice deploys + \refcmd{CollectArgumentsRaw} rather than \refcmd{CollectArguments}. But + how does it then pass the user-friendly \refmmzauto{collector options} to + that command? It embeds them in \refcmd{collargsSet}.} The raw variant is + used internally by both Advice and Memoize, and may be used by a package + deploying the advising framework which wants to save a few processing cycles. + In CollArgs, the two kinds of options are functionally equivalent; both are + documented in section~\ref{sec:ref:collargs}. + + Initially, the list of collector options is empty, and for commands, so is + the list of raw collector options. For environments, however, the latter + list is initialized to set (the raw equivalent of) \refcollargs{environment} + to the environment name, and \refcollargs{end tag} to true. The rationale + for the latter is that the environment body containing the end tag (e.g.\ + |\end{foo}|) is nicely compatible with \refcmd{AdviceReplaced} (which equals +the begin tag, e.g.\ |\begin{foo}|) and \refcmd{AdviceOriginal} (which executes + the original definition of e.g.\ |\begin{foo}|). For example, thanks to + \refcollargs{end tag}, writing \refcmd{AdviceOriginal}|#1| in the inner + handler executes the original environment. Importantly, the original + environment can be executed without explicitly referring to the + environment's name, and with code that works not only for environments of + any \hologo{TeX} format, but is actually the same as the code which invokes + an original \emph{command}. Consequently, the same inner handler works for + both commands and environments, and in all \hologo{TeX} formats. + + Furthermore, the initial collector also sets option \refcollargs{caller} + to the name of the advised command or environment (however, + \refcollargs{caller} never appears in any of the collector options + lists; it is simply prefixed to them while constructing the invocation of + \refcmd{CollectArgumentsRaw}). And in Memoize, using keys + \refmmz{verbatim}, \refmmz{verb} or \refmmz{no verbatim} triggers the + addition of the cognominal \refcollargs{verbatim}, \refcollargs{verb} + or \refcollargs{no verbatim} among the collector options. + + Precious few CollArgs' options thus remain to be set by the author. For + memoization, the most likely candidates are \refcollargs{ignore nesting} + and \refcollargs{ignore other tags}, which could help deal with unusual + environments. Overriding the initial \refcollargs{end tag} by + \refcollargs{begin tag}, \refcollargs{end tag} and/or \refcollargs{tags} + might also be useful on occasion. + + During advising and advice setup, the \pkg{pgfkeys} and the raw collector + options of the advised command are accessible through + \docaux{cmd}{AdviceCollectorOptions} and + \docaux{cmd}{AdviceRawCollectorOptions}, both a parameterless macro expanding + to the given \meta{keylist} and \meta{code}, respectively. +\end{doc} + +\begin{doc}{ + key={name=clear collector options}, + key={name=clear raw collector options}, + } + These keys empty the list of user-friendly and raw collector options, + respectively. +\end{doc} + +\begin{doc}{ + key={name=inner handler, par=\meta{\hologo{TeX} code}, long description=0.5, + desc=initially and default: see below}, + } + + This key declares the \meta{\hologo{TeX} code} as the inner handler component + of the advice. + + The inner handler is intended to be used in situations which require + knowledge of the advised command's arguments as a whole. In such situations, + the outer handler will normally invoke the collector, which will in turn + execute the inner handler and provide it with a single (braced) argument, + containing the collected arguments of the advised command. See + \refmmzauto{memoize} for the usage case which inspired this design. + + The simplest example of an inner handler is a (single-parameter) macro which + does nothing. Surprisingly enough, such an inner handler could be useful. + Defining |\def\Gobble#1{}| and setting + \code{\refmmz{auto}=\cs{foo}\bracestt{\refmmzauto{inner handler}=\cs{Gobble}, + \refmmzauto{args}=\bracestt{...}}} with the argument structure + appropriate for |\foo| could be used to eradicate all invocations of |\foo| + from the document. + + The inner handler has access to all the macros available to the outer + handler, but given that most of them have already fulfilled their function, + only the following will likely be useful in the inner handler: + \refcmd{AdviceNamespace}, \refcmd{AdviceName}, \refcmd{AdviceReplaced}, + \refcmd{AdviceOriginal}, and \refcmd{AdviceOptions}. + + Because there is clearly no reasonable default for the inner handler, this key + is initially set to an internal control sequence producing an ``undefined + inner handler'' error. Note that it is not necessary to define a dummy inner + handler when handling is entirely performed by the outer handler, i.e.\ in + cases when the inner handler is not invoked. Executing this key without a + value restores it to the initial value. + + During advising and advice setup, the inner handler of the advised command is + accessible through \docaux{cmd}{AdviceInnerHandler}, a parameterless macro + expanding to the given \meta{\hologo{TeX} code}. +\end{doc} + +\begin{doc}{easy, + key={name=options,par=\marg{keylist}, + desc={cumulative, initially empty, value required}}, + key={name=clear options}, + } + + The first key appends the given \meta{keylist} to the list of advice options + (after prefixing it by a comma), and the second one empties this list. For a + \meta{key} undefined in keypath \refkeypath{/mmz/auto}, + \meta{key}|=|\meta{value} has the same effect as + \refmmzauto{options}|=|\bracestt{\meta{key}=\meta{value}}. + + In Memoize, the options set by this key are known as + \emph{auto-options} --- options which are applied (using \refcmd{mmzset}) at + every invocation of the advised command or environment. For example, the + \env{tcolorbox} environment of package \pkg{tcolorbox} is used extensively + for typesetting this manual, and I have submitted this environment to + automemoization. However, the \env{tcolorbox}es in this manual often include + code listings. To memoize such environments successfully, their bodies must + be grabbed verbatim. I have therefore submitted the \env{tcolorbox} + environment to automemoization like this: + \refmmz{auto}|={tcolorbox}|\bracestt{\refmmzauto{memoize}, + \refmmzauto{options}=\refmmz{verbatim}}; the simpler + \refmmz{auto}|={tcolorbox}|\bracestt{\refmmzauto{memoize}, \refmmz{verbatim}} + would work as well. + + In general, whether to use the options set by this key, and how, remains at + the sole discretion of the advice. Note that they might be used by either + the outer or the inner handler, or perhaps even the collector. + + During advising and advice setup, the options of the advised command are + accessible through \docaux{cmd}{AdviceOptions}, a parameterless macro expanding + to the given \meta{keylist}. +\end{doc} + +\begin{doc}{key={name=reset, desc=style}} + Executing this key restores all \refmmz{auto} keys to their initial values. + + Invoking \refmmz{auto} on the same command or environment again + \emph{updates} the advice configuration. Use this key to start from scratch. +\end{doc} + +\begin{doc}{key={name=after setup, desc={initially empty, cumulative}}} + The code given to this key will be executed after exiting the group opened by + \refmmz{auto}. The same effect may be achieved by appending to macro + \docaux{cmd}{AdviceAfterSetup}. + + For example, \refmmzauto{integrated driver} uses this key to declare a new + conditional. +\end{doc} + +\paragraph{Commands available during the execution of advice} + +With the exception of \refcmd{AdviceGetOriginal}, the commands listed below +only become available in the outer handler, and if that handler does nothing +funky, they should be available in the collector and the inner handler, as +well. However, once the advice yields control to foreign code, these macros +are not guaranteed to hold the expected values anymore, because the foreign +code might trigger another piece of advice. Consequently, these macros +should be expanded, once, before integrating them into arbitrary (non-advice) +code; in particular, this applies to \refcmd{AdviceOriginal}. + +\begin{doc}{cmd={name=AdviceNamespace}} + This macro holds the \meta{namespace}, i.e.\ the keypath which this instance + of the advising framework was installed into. +\end{doc} + +\begin{doc}{cmd={name=AdviceName}} + This macro holds the name of the advised command or environment, i.e.\ the + name which was used as the first argument to \refmmz{auto}. For a command, + this will be a control sequence, e.g.\ |\foo|; for environments (in any + \hologo{TeX} format), their name, e.g.\ |foo|. +\end{doc} + +\begin{doc}{cmd={name=AdviceReplaced}} + This macro holds the code which was replaced by the outer handler. For + commands, this will be the command itself, e.g.\ |\foo|, so + \refcmd{AdviceReplaced} will equal \refcmd{AdviceName}. For an environment + |foo|, \refcmd{AdviceReplaced} is set to |\begin{foo}| in \hologo{LaTeX}, + |\foo| in \hologo{plainTeX} and |\startfoo| in \hologo{ConTeXt}. +\end{doc} + +\begin{doc}{cmd={name=AdviceOriginal}} + This macro executes the original code of the advised command. + + This macro is defined as \refcmd{AdviceGetOriginal}\marg{namespace}\marg{name}, + and therefore acts as a shortcut for an explicit invocation of + \refcmd{AdviceGetOriginal}. When executing the original command directly from + the advice, one may safely write \refcmd{AdviceOriginal}. However, whenever + \refcmd{AdviceOriginal} is embedded in code which might contain other advised + commands, it should be pre-expanded, exactly once. +\end{doc} + +\begin{doc}{cmd={name=AdviceGetOriginal, par=\marg{namespace}\marg{control sequence}}} + This command invokes the original definition of the \meta{control sequence} + advised by the \meta{namespace} instantiation of Advice. It may be safely + used outside the advice, even if the advised command is not activated. + + For example, upon executing key |/ns/|\refmmz{auto}|=\foo{...}|, + |\AdviceGetOriginal{/ns}{\foo}| will recall the original definition of |\foo| + if |\foo| is activated, and simply execute |\foo| otherwise. + + The second argument of this command should \emph{not} be an environment name. + To execute the original environment |foo| in \hologo{TeX} or + \hologo{ConTeXt}, use \refcmd{AdviceGetOriginal} with the appropriate macro: + |\AdviceGetOriginal{/ns}{\foo}| or |\AdviceGetOriginal{/ns}{\startfoo}|. In + \hologo{LaTeX}, one should use |\AdviceGetOriginal{/ns}{\begin}{foo}|, which + executes the original \refcmd{begin} and provides it with the environment name. + + Within the advice, you will probably never have to use this command directly, + but will rather rely on the (plain or pre-expanded) \refcmd{AdviceOriginal}. + However, outside the advice, this command provides the only means to + access the original definition of an advised command. (Unlike the + commands described above, this command is available throughout the + document.) + + \begin{tcolorbox}[warning] + A typo in the invocation of this command may result in an infinite loop. + Assume that the advice for |\foo|, declared in namespace \refkeypath{/mmz}, + executes \refcmd{AdviceGetOriginal}|{/zzm}{\foo}|, which incorrectly refers + to the non-existing namespace |/zzm|, and that command |\foo| is activated. + Executing |\foo| will eventually execute + \refcmd{AdviceGetOriginal}|{/zzm}{\foo}|, which won't find the original + definition of |\foo| in the non-existing namespace |/zzm| and will thus + execute macro |\foo| (again), which, being advised, will lead to another + \refcmd{AdviceGetOriginal}|{/zzm}{\foo}|, etc. My advice is to define an + abbreviation like |\def\mmzAdviceGetOriginal{\AdviceGetOriginal{/mmz}}|. + And note that the name\-space is a full keypath, which begins with a slash + (|/|), but has no slash at the end. + \end{tcolorbox} +\end{doc} + +\paragraph{Support for specific packages} +At the moment, Advice only implements specific support for \TikZ, by defining a +\refmmzauto{collector} for command \refcmd{tikz}. + +\begin{doc}{ + cmd={name=AdviceCollectTikZArguments}, + } + This command collects the arguments in the format expected by \refcmd{tikz}, and + executes macro \refcmd{AdviceInnerHandler} with the collected arguments given + as a single braced argument. The collector supports both the group and the + semicolor invocation of |\tikz|, i.e.\ both |\tikz{...}| and |\tikz...;|. + + This command is only available upon |\input|ting file + \docaux{file}{advice-tikz.code.tex}. +\end{doc} + +\endgroup % keypath = /mmz/auto + +\subsubsection{Memoization-related additions to the advising framework} +\label{sec:ref:advice:memoization} + +In section~\ref{sec:ref:advice}, we have seen that Memoize installs the +advising framework into keypath \refkeypath{/mmz}, with setup key name +\refmmz{auto}. This populates keypaths \refkeypath{/mmz} and +\refkeypath{/mmz/auto} with various generic advice keys. However, Memoize +installs further advice\slash automemoization-related keys into these keypaths. +It is these keys which are described in this section.\footnote{One such key, + \refmmzauto{integrated driver}, is actually documented in + section~\ref{sec:ref:memoization}.} + +Therefore, in contrast to section~\ref{sec:ref:advice}, \refkeypath{/mmz} and +\refmmz{auto} have no secret generic meaning here, i.e.\ they should \emph{not} +be generalized to \meta{namespace} and \meta{setup key} of +\refkey{/handlers/.install advice}. + +\paragraph{Keys residing in \refkeypath{/mmz}} + +\begingroup +\setlength\textfloatsep{20pt plus 2pt minus 4pt}% back to the default + +\begin{doc}{easy, + key={name=manual, description={preamble-only,{ }}, conditional=false}, + } + When this conditional is set to true, no commands are \refmmz{activate}d at + the beginning of the document. The list of commands and environments advised + and activated out of the box can be found in Table~\ref{tab:advised-commands}. + + The auto-framework allows the activation to be deferred (see + \refmmz{activation} and \refkey{/handlers/.install advice}) but leaves it + open to the specific instance of the framework to use the deferred activation + commands as it sees fit. Normally, Memoize switches to immediate activation + at the end of the preamble (hook \refmmz{begindocument/before}) and issues + \refmmz{activate deferred} at the beginning of the document, more precisely + in hook \refmmz{begindocument/end} (afterwards, \refmmz{activate deferred} is + emptied). However, when \refmmz{manual} is in effect, the deferred + activation is suppressed (though it may be still carried out by the user by + executing \refmmz{activate deferred}). + + In \hologo{LaTeX}, \refmmz{manual} affects the (internal) activation of + \refcmd{begin} as well, which effectively deactivates handling of all environments. +\end{doc} + +\begin{table} + \centering + \begin{tabularx}{\linewidth}{llX} + \toprule + command/environment&handler¬es\\ + \midrule + \refcmd{begin}&custom&Only in \hologo{LaTeX}; declared by Advice.\\ + \refcmd{errmessage}&\refmmzauto{abort}&Not available in \hologo{LuaTeX}, + where better error-detection is implemented.\\ + \refenv{forest}&\refmmzauto{memoize}\\ + \refcmd{Forest}&\refmmzauto{memoize}\\ + \refcmd{index}&\refmmzauto{replicate}&The argument is expanded prior to replication.\\ + \refcmd{label}&custom&Globally appends \refcmd{mmzLabel}\marg{label key}\marg{current label} + to register \refcmd{mmzCCMemo}; \refmmzauto{run if memoizing}.\\ + \refcmd{pageref}&\refmmzauto{ref}\\ + \refcmd[into index=false]{pdfsavepos}&\refmmzauto{abort}&Not available in \hologo{LuaTeX}.\\ + \refcmd{pgfsys@getposition}&\refmmzauto{abort}&Available only in \TikZ is + loaded, it aborts memoization of a picture which gets accidentally marked as + ``remembered''.\\ + \refcmd{ref}&\refmmzauto{ref}\\ + \refcmd[short]{savepos}&\refmmzauto{abort}&Available only in \hologo{LuaTeX}.\\ + \refcmd{tikz}&\refmmzauto{memoize}\\ + \refenv{tikzpicture}&\refmmzauto{memoize}\\ + \bottomrule + \end{tabularx} + \caption{Commands advised by Memoize} + \label{tab:advised-commands} +\end{table} + +\begin{doc}{easy, + key={name=ignore spaces, conditional=false}, + } + Ignore any spaces following \emph{auto}memoized code. This key has no effect + for manual memoization, i.e.\ command \refcmd{mmz} and environment + \refenv{memoize}. + + It is common practice to conclude the definition of a command by \hologo{TeX} + primitive \refcmd{ignorespaces}, which consumes any following spaces, to + prevent unintended blank space after the command's invocation. Automemoizing + such a command disrupts this behaviour.\footnote{It is clear that + \refcmd{ignorespaces} is disrupted during utilization; in this case, the + original command, including the concluding \cs{ignorespaces}, is never even + executed. However, the disruption also occurs during memoization, and even + during regular compilation. In both cases, the memoized code is embedded + in some internal Memoize code. Therefore, the original \cs{ignorespaces} + does not occur directly in front of the rest of the document.} The + workaround is to use this key, normally as an option in the \refmmz{auto} + declaration; it will work both for automemoized macros and environments. +\end{doc} + +\paragraph{Keys residing in \refkeypath{/mmz/auto}} + +\begingroup +\yadocset{keypath=/mmz/auto} + +\begin{doc}{easy,key={name=memoize, desc=style}} + This key sets up advice which triggers memoization of the command or + environment whenever it is encountered; we often refer to such a command as + ``automemoized,'' or say that it was ``submitted to automemoization.'' + + An automemoized command will consume the next-options, whether memoization + actually occurs or not. + + Under the hood, this key declares both an \refmmzauto{outer handler} and an + \refmmzauto{inner handler}. The outer handler opens the memoization group + (so that options can be applied locally), applies the auto-options (given by + \refmmzauto{options} within \refmmz{auto}) and the next-options (given by + \refcmd{mmznext}) by executing \refmmzauto{apply options}, and appends the + verbatim keys to \refmmzauto{collector options} if necessary. The inner + handler invokes \refcmd{Memoize} (which closes the group opened by the outer + handler): the first argument is \refcmd{AdviceReplaced}, expanded once and + followed by the arguments of the handled command; the second argument is + \refcmd{AdviceOriginal}, also expanded once and followed by the arguments of + the handled command. The inner handler also makes sure that \refmmz{ignore + spaces} is respected. +\end{doc} + +\begin{doc}{easy,key={name=nomemoize,desc=style}} + This key installs advice which disables memoization for the space of the + command or environment; we sometimes refer to such commands as + ``autodisabled.'' + + This key is merely an abbreviation for \refmmzauto{noop}|,| + \refmmzauto{options}|=|\refmmz{disable}. See the documentation of + \refmmzauto{noop} for further details. +\end{doc} + +\begin{doc}{key={name=noop,desc=style}} + This key sets up advice which does nothing. + + Ok, not nothing at all. The installed handler applies the auto-options and + the next-options by executing \refmmzauto{apply options}, and makes sure that + \refmmz{verbatim} and \refmmz{ignore spaces} are respected. + + For commands and non-\hologo{LaTeX} environments, this key declares the same + outer handler as \refmmzauto{memoize}, while the inner handler merely + executes the original command (respecting the potential verbatim mode), + closes the group opened by the outer handler, and makes sure that + \refmmz{ignore spaces} is respected. + + For \hologo{LaTeX} environments, which open the group necessary for the local + application of options themselves, this key declares an outer handler which + adds the relevant code into the next hook |env/|\meta{environment + name}|/begin|. There is no need to open a group, collect the environment + body, or make special provisions for the verbatim mode. +\end{doc} + + +\begin{doc}{key={name=apply options,desc=style}} + This style, used by \refmmzauto{memoize}, \refmmzauto{nomemoize} and + \refmmzauto{noop} described above, installs two handlers: + \begin{itemize} + \item an outer handler which opens a group, applies auto-options and + next-options by executing \refcmd{mmzAutoInit}, and executes the collector; + and + \item a bailout handler which clears the next-options. + \end{itemize} +\end{doc} + + +\begin{doc}{cmd={name=mmzAutoInit}} + This macro applies the auto-options and the next-options. + + Additionally, if \refmmz{verbatim}, \refmmz{verb} or \refmmz{no verbatim} was + previously executed, this style appends the corresponding CollArgs key + (\refcollargs{verbatim}, \refcollargs{verb} or \refcollargs{no + verbatim}) to \refcmd{AdviceRawCollectorOptions}. In case several of the + verbatim keys were executed, the final one takes effect. +\end{doc} + + +\begin{doc}{easy, key={name=abort,desc=style}} + This key sets up advice which aborts any ongoing memoization. + + Under the hood, the advice merely executes \refcmd{mmzAbort} followed by + \refcmd{AdviceOriginal}. The advised command does \emph{not} consume the + next-options. Out of the box, we submit two control sequences to this + handler: + \begin{itemize} + \item \refcmd{errmessage}: this allows us to detect and abort upon at least some + errors. + \item \refcmd[into index=false]{pdfsavepos} (in \hologo{LuaTeX}, + \refcmd[short]{savepos}): one common effect is that memoization of any + \TikZ picture with \refkey{/tikz/remember picture} set is aborted. + \end{itemize} +\end{doc} + + +\begin{doc}{key={name=unmemoizable,desc=style}} + This key sets up advice which aborts the ongoing memoization and marks the + automemoized code as unmemoizable, so that it will be henceforth compiled + regularly. + + Under the hood, the advice merely executes \refcmd{mmzUnmemoizable} followed + by \refcmd{AdviceOriginal}. The advised command does \emph{not} consume the + next-options. + + Out of the box, we submit no control sequences to this advice, but it might + make sense to submit \refcmd[into index=false]{pdfsavepos}\slash + \refcmd[short]{savepos}. Keys \refmmzauto{abort}|=|\refcmd[short]{savepos} + and \refmmzauto{unmemoizable}|=|\refcmd[short]{savepos} will most often have + the same effect, as far as the author is concerned; the former was chosen as + the default because it does not produce a c-memo; see + \refcmd{mmzUnmemoizable} for a situation where \refmmzauto{unmemoizable} is + preferred. +\end{doc} + + +\begin{doc}{easy, + key={name=ref,desc=style}, + key={name=force ref,desc=style}, + } + + These keys set up advice which adds the reference key to the context + expression. They are intended to be used with cross-referencing commands + such as \refcmd{ref} and \refcmd{pageref}. + + Indeed, \refcmd{ref} and \refcmd{pageref} are submitted to this advice by + Memoize, with the effect that standard cross-referencing inside memoized code + ``just works.'' Note that the stabilization of the document after changing + the reference takes three compilation cycles, i.e.\ one cycle more than + without memoization. + + The advice set up by \refmmzauto{ref} aborts memoization if the reference + key is undefined, the rationale being that the produced memo and extern would + most often be useless, and could even obscure an undefined reference. The + \refmmzauto{force ref} handler produces the memo and the extern even when the + reference is undefined. + + The reference produced by the advised command should be fully expandable + (because it will be expanded as a part of the context expression). + + Typically, a \refcmd{ref} command takes a single argument, the reference key. + However, some packages may define a reference command which takes optional + arguments, as well; in particular, the \pkg{hyperref}'s incarnation of \refcmd{ref} + takes an optional star. This advice does not care: it will accept any number + of any kind of optional arguments, as long as the reference key is the first + braced argument following the advised command; for example, |\ref*{key}|, + |\ref[opt]{key}|, |\ref*[opt]{key}| etc.\ will all be handled correctly, + while |\ref{mand}{key}| will not work. Effectively, it is as if we had set + \refmmzauto{args}|=lm| --- and with the same downside, namely that an + unlikely unbraced single-token reference key, like |\ref k|, will not work. + + Under the hood, these two pieces of advice pass the reference key to macros + \refcmd{mmzNoRef} and \refcmd{mmzForceNoRef}, and it is these commands --- + which may also be used in user-defined advice or the document itself --- + which actually add the reference key to the context expression. +\end{doc} + +\begin{doc}{easy, + key={name=refrange,desc=style}, + key={name=force refrange,desc=style}, + } + These keys have the same function as \refmmzauto{ref} and \refmmzauto{force + ref}, but they operate on reference-range commands, such as \pkg{cleveref}'s + \refcmd{crefrange}, which take two arguments (the starting and the ending reference + key). +\end{doc} + +\begin{doc}{easy, + key={name=multiref,desc=style}, + key={name=force multiref,desc=style}, + } + These keys have the same function as \refmmzauto{ref} and \refmmzauto{force + ref}, but they operate on ``multireference'' commands, such as + \pkg{cleveref}'s \refcmd{cref}, which allow the author to list several + comma-separated reference keys in a single argument. +\end{doc} + +\begin{doc}{ + key={name=replicate, desc=style} + } + This key sets up advice which replicates the invocation of the command in + the cc-memo during memoization. + + When using this key, it is necessary to set \refmmzauto{args} as well. For + \refcmd{index}, Memoize executes + \refmmz{auto}|=\index|\bracestt{\refmmzauto{args}=m, \refmmzauto{replicate}}. + + This key takes an auto-option, \docAux{keypath=/mmz/auto/replicate, + key={name=expanded}}.\footnote{This option is unrelated to Memoize's options, + settable by \refcmd{mmzset}.} If given, the collected arguments will be + expanded before replicating them in the cc-memo; in \hologo{LaTeX}, this + expansion is |\protect|ed. + + In \hologo{LaTeX}, Memoize submits \refcmd{index} to this handler (with + expansion). Therefore, any |\index{key}| in the memoized code gets copied + into the cc-memo. Effectively, indexing from within the memoized code ``just + works.'' + + Note that \refcmd{label}, despite essentially requiring replication, cannot + use this advice, because it needs to replicate not only the label key but + \refcmd{@currentlabel} as well. +\end{doc} + +\begin{doc}{key={name=run if memoization is possible, desc=style}} + Under the run conditions installed by this key, a command is only advised if + Memoize is enabled but we're not already ``within Memoize,'' i.e.\ memoizing + or normally compiling some code submitted to memoization. In code: + \refcmd{ifmemoize}\refcmd{ifinmemoize}|\else|\refcmd{AdviceRuntrue}|\fi\fi|. + + Internally, this key is used by \refmmzauto{memoize} and \refmmzauto{noop}. +\end{doc} + +\begin{doc}{key={name=run if memoizing, desc=style}} + Under the run conditions installed by this key, a command is only advised + during memoization. In code: + \refcmd{ifmemoize}\refcmd{ifmemoizing}\refcmd{AdviceRuntrue}|\fi\fi|. + + Internally, this key is used by \refmmzauto{abort}, \refmmzauto{replicate}, + and \refmmzauto{ref} and friends. +\end{doc} + +\endgroup % keypath = /mmz/auto +\endgroup + + + +\subsubsection{Package CollArgs} +\label{sec:ref:collargs} + +\begingroup +\yadocset{keypath=/collargs} + +\begin{doc}{cmd={ + name=CollectArguments, + par=\oarg{options}\marg{argument specification}\marg{next-code}% + \textcolor{gray}{\meta{tokens}}}} + + This command determines the extent to which the \meta{tokens} following the + the three formal arguments of the command conform to the given \meta{argument + specification}, effectively splitting \meta{tokens} into \meta{argument + tokens} and the \meta{rest} of the tokens, and then executes + \meta{next-code} with the \meta{argument tokens} provided as a single, braced + argument: + \begin{center} + \meta{next-code}\marg{argument tokens}\textcolor{gray}{\meta{rest}} + \end{center} + If the initial part of \meta{tokens} does not conform to \meta{argument + specification}, \refcmd{CollectArguments} throws an error. (In this case, + \meta{next-code} is not executed, and the \meta{tokens} collected until the + error are thrown away.) + + The optional \meta{options} are processed using the \pkg{pgfkeys} utility of + PGF/\TikZ (see \PGFmanual{87}), with the default path set to + \docaux{key path}{collargs}. The given options apply to all the arguments in + \meta{argument specification}. The recognized keys are listed in the rest of + the section. + + The \meta{argument specification} should be given in the \pkg{xparse} format + (we summarize this format in the documentation for \refmmzauto{args} in + section~\ref{sec:ref:advice}), with several extensions:\footnote{Collargs + internally uses a dot (|.|) to delimit the argument specification from the + following argument tokens. Therefore, the dot really counts as an extra + argument type, in the sense that Collargs will stop working if the dot + becomes an argument type or a modifier in some future release of + \pkg{xparse}.} + \begin{itemize} + \item We introduce modifier \docAux{xparse modifier={name=\&,label=amp,index + annotation/.prefix={additional\ }, index annotation/.append={\ + (options)} }} taking a mandatory argument specifying the options to + apply to the following argument in the specification. Options given here + override the \meta{options} given as the optional argument. + \item The environment body type \docAux{xparse type={name=b,index + annotation/.append={\ (environment body)}}} may be followed by an optional + \emph{braced} argument providing the name of the environment to collect. + The name given here overrides the name given by the + \refcollargs{environment} option. + \item The number of collected ``arguments'' is unlimited. + \end{itemize} + + Also note that the effect of \docref{xparse:O}\marg{default} is the same as + the effect of \docref{xparse:o}, and similarly for other pairs of types with + and without defaults (\docref{xparse:R} and \docref{xparse:r}, + \docref{xparse:D} and \docref{xparse:d}, and \docref{xparse:E} and + \docref{xparse:e}). CollArgs is dedicated to collecting the argument tokens + precisely as they are given: if an optional argument is missing, its default + value is \emph{not} inserted among the collected arguments --- consequently, + \refcmd{CollectArguments} is utterly uninterested in the default value. + + Collection of environments automatically adapts to the format, i.e.\ given + environment body name |foo|, \refcmd{CollectArguments} knows to search for + \refcmd{begin}\bracestt{foo} |...| \refcmd{end}\bracestt{foo} in + \hologo{LaTeX}, \cs{foo} |...| \cs{endfoo} in \hologo{plainTeX}, and + \cs{startfoo} |...| \cs{stopfoo} in \hologo{ConTeXt}. For further + information on environment collection, see keys \refcollargs{ignore + nesting} and \refcollargs{tags}. +\end{doc} + +\begin{doc}{cmd={ + name=CollectArgumentsRaw, + par=\marg{option-setting code}\marg{argument specification}\marg{next-code}% + \textcolor{gray}{\meta{tokens}}}} + + This command is the programmer's interface to CollArgs, intended to be used + instead of \refcmd{CollectArguments} when compilation speed is an issue. The + two commands only differ in how they deal with options. + + One difference is that for \refcmd{CollectArgumentsRaw}, the options form a + mandatory rather than an optional argument. More importantly, however, they + do not take the form of a keylist, but should be composed out of low-level + option-setting commands. Each key documented in this section has a + corresponding low-level macro; these macros are listed in footnotes alongside + the keys. The name of the macro starts with |\collargs| and continues with + the name of the key, without spaces, each word capitalized; if the key is + boolean, this convention applies to the base of the \hologo{TeX} + conditional. For example, + \begin{tcblisting}{listing only, listing options app={escapechar=|}} +\CollectArguments[caller=\foo, tags, verbatim]|\marg{argument specification}\marg{next-code}| + \end{tcblisting} + is equivalent to + \begin{tcblisting}{listing only, listing options app={escapechar=|}} +\CollectArgumentsRaw{% + \collargsCaller{\foo}% + \collargsBeginTagtrue\collargsEndTagtrue + \collargsVerbatim +}|\marg{argument specification}\marg{next-code}| + \end{tcblisting} + + Withing the option-setting code, the programmer may also deploy macro + \docaux{cmd}{collargsSet}, which processes the \meta{options} in the keylist + format. One idea could be to execute this macro at the end of the low-level + options; this would set the ``defaults'' using the fast programmer's + interface, but still allow for user customization. +\end{doc} + + +\begin{doc}[ + pi={\docaux{cmd}{collargsCaller}}, + ]{ + key={name=caller, par=\meta{control sequence (name)}, + desc={no default, initially \cs{CollectArguments}}}, + } + Set the control sequence to refer to in error messages. + + If \meta{tokens} do not match the \meta{argument specification}, + \refcmd{CollectArguments} throws an error. By default, the error message + contains a reference to \refcmd{CollectArguments} itself, for example % + |! Argument of \CollectArguments has an extra }.| However, this might not be +very informative to the author. When \refcollargs{caller}|=\cs| is in effect, +the error messages will refer to the given |\cs| instead. + +If the value of this key is not a control sequence, it is assumed to be an +environment name, but as the caller must be a macro, this name will be +converted into a control sequence. Setting \refcollargs{caller}|=foo| will +result in error messages referencing |\foo| in \hologo{plainTeX}, |\startfoo| +in \hologo{ConTeXt} and |\begin{foo}| (a single control sequence!) in + \hologo{LaTeX}. +\end{doc} + +\begin{doc}[ + pi=\docaux{cmd}{collargsEnvironment}, + ]{key={name=environment, par=\meta{environment name}, + desc={applicable to type \docref{xparse:b}, no default, initially empty}} + } + + Set the name of the environment collected by argument type + \docref{xparse:b}. +\end{doc} + +\begin{doc}[ + pi={\docaux{cmd}{ifcollargsBeginTag}, \docaux{cmd}{ifcollargsEndTag}; + \refcollargs{tags} has no corresponding low-level command} + ]{ + key={name=begin tag, desc={applicable to type \docref{xparse:b},{ }}, conditional=false}, + key={name=end tag, desc={applicable to type \docref{xparse:b},{ }}, conditional=false}, + key={name=tags, par=\meta{boolean}, % not a conditional + desc={applicable to type \docref{xparse:b}, style, default |true|}}, + } + + When \refcollargs{begin tag}\slash \refcollargs{end tag} is in effect, the + begin\slash end tag will be will be prepended/appended to the collected + environment body. Style \refcollargs{tags} is a shortcut for setting + \refcollargs{begin tag} and \refcollargs{end tag} simultaneously. + + In \hologo{LaTeX}, using \refcollargs{tags} will thus dress up the + collected body in a pair or \refcmd{begin}\marg{environment name} and + \refcmd{end}\marg{environment name}. CollArgs will automatically use the tags + appropriate to the format. + + In the verbatim modes, the added tags are verbatim as well, with the detail + that in \hologo{LaTeX}, there is a slight difference between the full + \refcollargs{verbatim} and the partial \refcollargs{verb} mode. In the + full verbatim mode, the braces surrounding the environment name are verbatim + (the characters used as braces are actually determined by key + \refcollargs{braces}). In the partial verbatim, as well as the + non-verbatim mode, the environment name is surrounded by a pair of actual + braces of category 1 and 2, regardless of which characters are of these + categories in the calling code. +\end{doc} + +\begin{doc}[ + pi=\docaux{cmd}{ifcollargsIgnoreNesting}, + ]{ + key={name=ignore nesting, desc={applicable to type \docref{xparse:b},{ }}, + conditional=false}, + } + When this key is \emph{not} in effect, CollArgs respects the hierarchical + structure created by tag pairs such as \refcmd{begin}\bracestt{foo} and + \refcmd{end}\bracestt{foo}. Given the situation below on the + left, argument type |b{foo}| will collect everything up until the + \emph{second} |\end{foo}|. Now this is what we usually want, because +\hologo{LaTeX} keeps track of environment embedding as well. However, all +\emph{verbatim} environments I know of, starting with the standard +\hologo{LaTeX} \refenv{verbatim}, will ignore the nesting and simply scoop up +everything up to the first \cs{end}|{verbatim}|. In CollArgs, we can replicate +their behaviour by setting \refcollargs{ignore nesting}, as shown below on the +right. (Of course we also need to set \refcollargs{verbatim} if we want to +grab the environment body in the verbatim mode.) + + \begin{tcbraster}[raster columns=2] + \begin{tcblisting}{listing only, mark region={1}{5}, + example title=\texttt{ignore nesting=false}} + ... + \begin{foo} + ... + \end{foo} + ... +\end{foo} + \end{tcblisting} + \ExampleName{_collargs-verbatim} + \makeexample{\examplename.tex.c1} + \tcbinputexample{listing only, no attachment, mark region={1}{3}, + example title=\texttt{ignore nesting=true}} + \end{tcbraster} + + This key applies not only to argument type \docref{xparse:b} (in either + normal or verbatim mode), but also to the verbatim argument type + \docref{xparse:v} and to argument types \docref{xparse:m} and + \docref{xparse:g} in the \refcollargs{verbatim} (but not normal, or + \refcollargs{verb}) mode. With these keys, the relevant structure markers + are braces, |{| and |}|. +\end{doc} + +\begin{doc}[ + pi=\docaux{cmd}{ifcollargsIgnoreOtherTags}, + ]{ + key={name=ignore other tags, desc={applicable to type \docref{xparse:b},{ }}, + conditional=false}, + } + In \hologo{LaTeX}, the environment tags, \refcmd{begin}\marg{name} and + \refcmd{end}\marg{name}, contain braces, which retain their usual category codes + in the non-verbatim and in the partial verbatim mode. Consequently, CollArgs + cannot easily search for the full tags to delimit the + environment. + + When this key is \emph{not} in effect, CollArgs takes the easy path, and + determines the end of the environment only by inspection of \cs{begin}s and + \cs{end}s, without reference to what \meta{name} they begin or end. Only + when this key \emph{is} in effect does CollArgs inspect these \meta{name}s, + effectively ignoring any \cs{begin}s and \cs{end}s not followed by the name of + environment being collected. The effect of absence vs.\ presence of this key + is shown below, where the shaded area marks the code collected into + environment |foo|. + + \begin{tcbraster} + \ExampleName{_collargs-ignore-other-tags} + \makeexample{\examplename.tex.c1 N=2} + \tcbinputexample{listing only, no attachment, mark region={3}{3}, + example title=\texttt{ignore other tags=false}} + \tcbinputexample[.tex][.c2]{listing only, no attachment, mark region={3}{5}, + example title=\texttt{ignore other tags=true}} + \end{tcbraster} + + This key does not have any effect the full verbatim mode, which always + behaves as if this key was set to true, because braces are of category + ``other'' as well. Similarly, it is as if this key was always true in + \hologo{plainTeX} and \hologo{ConTeXt}, simply because environment tags in + these formats don't contain braces. +\end{doc} + +\begin{doc}[ + pi={\raggedright + \docaux{cmd}{collargsAppendPreprocessor}, + \docaux{cmd}{collargsPrependPreprocessor}, + \docaux{cmd}{collargsAppendPostprocessor}, + \docaux{cmd}{collargsPrependPostprocessor}}, + ]{ + key={name=append preprocessor, par=\meta{code}, desc={no default}}, + key={name=prepend preprocessor, par=\meta{code}, desc={no default}}, + key={name=append postprocessor, par=\meta{code}, desc={no default}}, + key={name=prepend postprocessor, par=\meta{code}, desc={no default}}, + } + These keys declare processors which will transform the collected argument + before appending it to the argument list. + + A collected argument undergoes the following transformations: + \begin{itemize} + \item First, the argument is processed by any \emph{pre}processors, in the + order indicated by |append| and |prepend|. + \item Next, the processed argument is dressed up in the delimiters according + to its type. For example, an optional argument of type \docref{xparse:o} + will be surrounded by square brackets. + \item Finally, the delimited argument is processed by any + \emph{post}processors, again in the order indicated by |append| and + |prepend|. + \end{itemize} + + \meta{code} will typically consist of a single control sequence pointing to a + one-argument macro, which will receive the collected argument (possibly + modified by the processors already applied). In general, however, the value + of this key may be any code; Collargs will execute \meta{code}\marg{collected + argument}. + + The processed argument should be returned by storing it into token register + \docaux{cmd}{collargsArg}. + + The following example illustrates how one could go about reimplementing Bruno + Le Floch ingenious package \pkg{cprotect}.\footnote{This example is merely a + proof of concept. For the bells and whistles which would make it useful in + real life, see the documentation of \pkg{cprotect}.} We define processor + |\writetofile| which dumps the argument into a file, replacing it with the + |\input| statement. (Of course, to allow for verbatim content in the + footnote, we also have to mark the argument as \refcollargs{verbatim}. + And we use \refcollargs{no delimiters} to get rid of the braces around the + footnote text.) + + \ExampleName{collargs-processor} + \makeexample{\examplename.tex.c1} + \tcbinputexample{listing and text, listing file=\examplepath.tex.c1} +\end{doc} + +\begin{doc}[ + pi={\docaux{cmd}{collargsClearPreprocessors}, + \docaux{cmd}{collargsClearPostprocessors}}, + ]{ + key={name=clear preprocessors}, + key={name=clear postprocessors}, + } + Clear the list of pre- or post-processors. +\end{doc} + +\begin{doc}[ + pi={\docaux{cmd}{collargsAppendExpandablePreprocessor}, + \docaux{cmd}{collargsPrependExpandablePreprocessor}, + \docaux{cmd}{collargsAppendExpandablePostprocessor}, + \docaux{cmd}{collargsPrependExpandablePostprocessor} + }, + ]{ + key={name=append expandable preprocessor, par=\meta{code}, desc={no default}}, + key={name=prepend expandable preprocessor, par=\meta{code}, desc={no default}}, + key={name=append expandable postprocessor, par=\meta{code}, desc={no default}}, + key={name=prepend expandable postprocessor, par=\meta{code}, desc={no default}}, + } + These keys may be used to add fully expandable processors. A processor added + with one of these keys will end up among the processors declared by + \refcollargs{append preprocessor} et al. + + A processor declared by one of these keys will define the processed argument + as the full expansion (|\edef|) of \meta{code}\marg{collected argument}. + \meta{code} will typically consist of a single control sequence pointing to a + fully expandable one-argument macro. + + For example, |\trim@spaces@noexp| from package \pkg{trimspaces} could be used + as an expandable processor of environment body to remove the spaces around + the grabbed environment body. +\end{doc} + +\begin{doc}[ + pi={\docaux{cmd}{collargsAppendPrewrap}, + \docaux{cmd}{collargsPrependPrewrap}, + \docaux{cmd}{collargsAppendPostwrap}, + \docaux{cmd}{collargsPrependPostwrap}}, + ]{ + key={name=append prewrap, par=\meta{macro definition}, desc={no default}}, + key={name=prepend prewrap, par=\meta{macro definition}, desc={no default}}, + key={name=append postwrap, par=\meta{macro definition}, desc={no default}}, + key={name=prepend postwrap, par=\meta{macro definition}, desc={no default}}, + } + These keys add processors which transform the collected argument in a single + expansion. + + The declared processor will use \meta{macro definition} to define a temporary + one-argument \meta{macro}, and then set the \meta{processed argument} to be + the single expansion of \meta{macro}\marg{collected argument}. + + For example, to add quotes around the collected argument, write + \refcollargs{append prewrap}|={``#1''}| (doubling the hash when executing + \refcmd{CollectArguments} from a macro, of course). Or, perhaps more + usefully, \refcollargs{append prewrap}|={\scantokens{#1}}| can be used to + retokenize a verbatim argument (during the execution of the + \meta{next-code}). +\end{doc} + + +\begin{doc}[ + pi=\docaux{cmd}{ifcollargsNoDelimiters} + ]{ + key={name=no delimiters, conditional=false}, + } + When this key is in effect, the collected argument will not be dressed up + into delimiters that it was dressed up in \meta{argument tokens}. For + example, an optional argument, encountered as [\meta{argument}] inside + \meta{argument tokens}, will be spit out simply as \meta{argument}. + + Any user-specified pre- or post-processing will still be applied. + \ExampleName{collargs-nodelimiters} + \makeexample{\examplename.tex.c1} + \tcbinputexample{comment=\input{\examplepath.tex.c1}} +\end{doc} + + +\begin{doc}[ + pi={\docaux{cmd}{collargsVerbatim}, + \docaux{cmd}{collargsVerb}, + \docaux{cmd}{collargsNoVerbatim}. + To ensure the same effect as with the keys, + place these macros at the end of the option code}, + ]{ + key={name=verbatim, desc=style}, + key={name=verb, desc=style}, + key={name=no verbatim, desc=style}, + } + Select the full verbatim, the partial verbatim, or the non-verbatim mode of + argument collection. + + In the full \refcollargs{verbatim} mode, the arguments are collected under + a category code regime in which all characters are of category 12, ``other''. + The same goes for the partial \refcollargs{verb} mode, except that in this + case, the grouping characters --- usually the braces |{| and |}| --- retain + their usual category codes 1 and 2. Key \refcollargs{no verbatim} selects + the normal, non-verbatim mode. + + The partial \refcollargs{verb} mode can be useful for verbatim collection + of an optional argument. To pass |]| as an optional argument to command +|\foo|, we normally enclose it in braces: ˇ\foo[{~]~}]ˇ. However, if we try to +collect |[{]}]| with \refcmd{CollectArguments}|[|\refmmz{verbatim}|]{o}|, we +will get |{| (and most likely an error, as well), because in the + \refmmz{verbatim} mode, braces do not have their grouping function. Using + the \refmmz{verb} mode solves the problem: occuring within braces, the first + |]| is ``invisible'' to \refcmd{CollectArguments}|[|\refmmz{verb}|]|, so the +optional argument is correctly recognized as ending at the second |]|. + +The partial \refcollargs{verb} mode is also useful for collecting the bodies +of \emph{\hologo{LaTeX}} environments. The full \refcollargs{verbatim} mode +will only correctly collect these bodies when the relevant \refcmd{begin} +and\slash or \refcmd{end} control sequences are followed by the grouped +environment name without any intervening spaces. The partial +\refcollargs{verb} mode has no such restriction. + +In the verbatim modes, modifier \docref{xparse:+} has no effect. The arguments +are always collected as if they were long. + +To correctly collect arguments in the verbatim modes, CollArgs has to mimic the +many details of \hologo{TeX}'s tokenization and argument delineation. These +details depend on the category code regime, and CollArgs automatically adapts +to the ``outside'' category code regime, i.e.\ the regime in effect at the time +of invoking \refcmd{CollectArguments}. In particular, CollArgs remembers which +characters were of category codes 0, 1, 2, 5, 10 and 11, and adapts the +argument collection accordingly. For example, it will correctly pick up a +control sequence as a single-token \docref{xparse:m}-type (\hologo{TeX}'s undelimited) +argument even when it begins with a non-standard character of category code 0. +The single caveat is that only a single pair of characters can function as the +grouping characters in the full verbatim mode; to compensate for the +deficiency, this character pair is customizable via key +\refcollargs{braces}. +\end{doc} + +\begin{doc}[ + pi={\docaux{cmd}{collargsFixFromVerbatim}, + \docaux{cmd}{collargsFixFromVerb}, + \docaux{cmd}{collargsFixFromNoVerbatim}}, + ]{ + key={name=fix from verbatim, desc=style}, + key={name=fix from verb, desc=style}, + key={name=fix from no verbatim, desc=style}, + } + Key \refcollargs{fix from no verbatim} should be used when the first + argument should be collected in a verbatim mode, but the outside code has + already tokenized the first character of the subsequent input stream (most + probably by a |\futurelet|) in the non-verbatim category code regime. Using + this key will trigger a CollArgs' ``mode transition'' (described below) which + will fix the situation. (This key is used in the implementation of + \refcmd{mmz}.) + + The other two keys should be used in the unlikely reverse situation, where + the outside code has tokenized the following character in a verb(atim) mode, + while CollArgs is requested to collect the first argument in the non-verbatim + mode. +\end{doc} + +\begin{doc}[ + pi=\docaux{cmd}{collargsBraces} + ]{ + key={name=braces, par={\meta{begin-group char}\meta{end-group char}}, + desc={no default, for details see below}}, + }% here + This key sets the verbatim begin-group and end-group characters. The setting + affects collection of argument types \docref{xparse:m}, \docref{xparse:g}, + \docref{xparse:v}, \docref{xparse:l}, \docref{xparse:e} and (in + \hologo{LaTeX}) \docref{xparse:b} in the \emph{full} verbatim + mode.\footnote{The choice of the verbatim grouping characters also affects + the effect of \refcollargs{begin tag} and\slash or \refcollargs{end tag}; + see the documentation of these keys for details.} + + For example, in the non-verbatim and the partial verbatim mode, an + \docref{xparse:m}-type argument may be delimited by any characters of + category code 1 (``begin-group'') and 2 (``end-group''). In the full + verbatim mode, there are of course no characters of these categories, so + CollArgs internally assigns the grouping function to some pair of characters. + When entering the full verbatim mode, CollArgs automatically sets the + verbatim grouping characters to characters which were of categories 1 and 2 + in the ``outside'' category code regime, i.e.\ the regime in effect at the + time of invoking \refcmd{CollectArguments}. However, in contrast to + \hologo{TeX}'s internal argument parser, only one pair of characters may + serve as the begin-group and the end-group character in CollArgs' full + verbatim mode. In case multiple characters were of category 1 or 2 on the + outside, CollArgs therefore has to make a choice, and it chooses the + candidate with the lowest character code. This choice may be overridden by + the user by invoking key \refcollargs{braces}; the user may even choose + characters which did not belong to categories 1 and 2 in the outside regime. + + When \meta{begin-group char} and \meta{end-group char} are of categories 1 + and 2 in the outside category regime, they must be enclosed in a triple + group. For example, if both |()| and |{}| have the grouping function on the + outside, and the user wants to select |{}| as the verbatim grouping + characters (CollArgs would go for |()|, as this pair has lower character + codes), the correct way to invoke this key is |braces={{{{}}}}| or + |braces=((({})))|. \footnote{This complication is due to the details of + \pkg{pgfkeys}' keylist processing, and does not apply to + \refcmd{collargsBraces}.} +\end{doc} + +\begin{doc}[ + pi=\docaux{cmd}{collargsVerbatimRanges} + ]{ + key={name=verbatim ranges, + par={\bracestt{\meta{from}\texttt{-}\meta{to}(\texttt{,} \meta{from}\texttt{-}\meta{to})*}}, + desc={no default, initially \texttt{0-255}}}, + } + If run under the \hologo{pdfTeX} or \hologo{XeTeX} engine, this key + determines which characters will be assigned category code 12 in the verbatim + mode. In \hologo{pdfTeX}, the range should remain at the initial |0-255|, + but in \hologo{XeTeX}, some rare situations might require extending this + range (don't attempt to set the full range of |0-1114111|, as this would be + very slow and you would most likely run out of save stack). + + In \hologo{LuaTeX}, we switch the category code regime using category code + tables, so this key has another meaning: it determines the range in which + CollArgs will scan for characters of category codes 1, 2 and 14, whose + identity it needs to know, for internal reasons. +\end{doc} + + + +\paragraph{Mode transition limitations} + +\refcmd{CollectArguments} has some minor limitations regarding the transition +from a verbatim into non-verbatim mode, or vice versa. The gist of the issue +is best illustrated with the optional argument type \docref{xparse:o} collected +in the verbatim mode. CollArgs determines whether an argument of this type is +present by peeking ahead (using \hologo{TeX}'s |\futurelet| primitive) into the +input stream. If the argument is present (i.e.\ if the input stream continues +with an open bracket, |[|%] +), all is well. But when the optional argument is absent, the peek-ahead will +tokenize the following character, which presents a problem when no more +arguments are present in the input stream, like in the example below, where the +verbatim \docref{xparse:o} is the (only and) final type in the argument +specification. In this case, the peek-ahead ``incorrectly'' assigns category +code 12 (``other'') to the first |$|. %$ +This character was intended to be tokenized as the math shift character of +category 3, to start the math mode after \refcmd{CollectArguments} is finished, +but having been assigned category code 12, it cannot perform this function, +resulting in error |! Missing $ inserted| %$ +once \hologo{TeX} encounters the superscript character |^|. + +\ExampleName{collargs-transition-ok} +\makeexample{\examplename.tex.c1} +\tcbinputexample{sidebyside, lefthand ratio=0.6, + comment=\input{\examplepath.tex.c1}} + +Well --- this is what \emph{would} happen if CollArgs didn't address the +transition issue described above. In fact, the above example compiles just +fine, because CollArgs \emph{does} address this issue, but unfortunately, +certain transition problems simply cannot be resolved --- read on to learn what +can go wrong. + +For example, you can typeset the name of the document author via +\hologo{LaTeX}'s internal command |\@author|, but to use this command in the +document, you have to precede it by |\makeatletter|. As shown by the first +line of the example below, this works rather nicely: |\makeatletter| sets the +category code of |@| to 11 (``letter''), so |@| may help form the control word +|\@author| --- importantly, |\makeatletter| sets the category code of |@| +\emph{before} control sequence |\@author| is constructed, even if it precedes +it immediately. + +\ExampleName{collargs-transition-cs} +\makeexample{\examplename.tex.c1} +\long\def\ShowArgs#1{Collected: ``{\color{red}\tt\detokenize{#1}}''} +\tcbinputexample{comment=\input{\examplepath.tex.c1}} + +In the second line of the example, our clever invocation of |\@author| is +immediately preceded by a call to \refcmd{CollectArguments}, which tries to +collect a verbatim argument of type \docref{xparse:o}. It doesn't find it, which results in +the wrong, verbatim tokenization of the escape character of |\makeletter|. +CollArgs realizes the problem and tries to fix it. But while it is searching +for the end of control sequence |\makeletter| (which it successfully +constructs), it triggers the tokenization of what follows --- which, as |@| is +at that point of category 12 (``other''), yields the control symbol |\@| (later +followed by word ``author'', typeset in the example). + +In short, the solution has created another, delayed instance of the problem --- +an instance which cannot be addressed any further. But we're nevertheless +better off, as this particular issue will bite only in the case when the +``corrupted'' control sequence immediately following the invocation +\refcmd{CollectArguments} changes category codes in a way that affects the +tokenization of what immediately follows it. + +This was an example of what can go wrong in the transition from the +\meta{argument tokens} to the \meta{rest} of the tokens following an invocation +of \refcmd{CollectArguments}. As the ``outside world'' is non-verbatim, this +transition can only be problematic if the argument which corrupted the first of +the \meta{rest} tokens was verbatim, so if the transition was from the verbatim +to the non-verbatim mode. Such a transition can also occur within the +\meta{argument tokens}, but the good news here is that CollArgs successfully +solves any problems that occur there, so you should only worry about the +end-of-arguments situation. + +The other direction of the transition, from the non-verbatim to the verbatim +mode, however, can affect both the internal and the external transitions. Let +us illustrate the problem with the internal transition. Say you want to +collect an optional argument (\docref{xparse:o}) in the non-verbatim mode, and +then a mandatory argument (\docref{xparse:m}) in (either full or partial) +verbatim mode. In the first invocation of \refcmd{CollectArguments} below, the +optional argument is present, and we get what we expect: the percent sign is +collected, verbatim, into the mandatory argument. In the second invocation, +however, the percent character retains its usual commenting function --- +despite the fact that we have requested verbatim mode for the mandatory +argument --- which results in the group in the second line being picked up as +the mandatory argument. Again, this happens because CollArgs has to peek ahead +in the input stream when determining whether the optional argument is present. +Having requested non-verbatim mode for the optional argument, the peeking is +performed in the non-verbatim mode, and as the optional argument is not +present, it finds the comment character, which fulfills its regular function of +disappearing along with the rest of the line. Once CollArgs sets to find the +(verbatim) mandatory argument, the rest of the line is already gone, so it +searches for, and finds, this argument in the next line. + +\ExampleName{collargs-transition-comment} +\makeexample{\examplename.tex.c1} +\long\def\ShowArgs#1{Collected: ``{\color{red}\tt\detokenize{#1}}''} +\tcbinputexample{listing and text} + +Nothing can be done here --- commenting deletes information, irrevocably --- +and in a similar fashion, nothing can be done to catch a verbatim end-of-line +character when preceded by an absent optional argument in the non-verbatim mode +(because it was already tokenized into a space token). + +Finally, the transition issues are not limited to transits from argument type +\docref{xparse:o}. The full list of argument types which give rise to +transition problems (when transiting \emph{from} arguments of these types) is +as follows: \docref{xparse:o}, \docref{xparse:d}, \docref{xparse:s}, +\docref{xparse:t}, \docref{xparse:g}, \docref{xparse:e}. + + +\endgroup % /collargs + + +\section{Varia} + +\subsectionclearpagefalse + +\subsection{Known issues} +\label{sec:known-issues} + +\paragraph*{Bitmap graphics export} is coming up in the next release of the +package. + + +\paragraph{Disappearing errors} +If a non-fatal internal \hologo{TeX} error occurs during memoization, the memos +and externs may be nevertheless produced and utilized in subsequent +compilations. In such a case, the erroneous code won't be compiled again, and +therefore won't yield any errors, giving the mistaken impression that the code +is error-free. + +This problem does not apply to errors which trigger \refcmd{errmessage}, +because that control sequence is advised by \refmmzauto{abort}. +Note that internal \hologo{TeX} errors like |Undefined control sequence| are +\emph{not} reported through \refcmd{errmessage}, and will therefore cause the +issue. + +This problem does not affect \hologo{LuaTeX}, because this engine allows +Memoize to detect errors and abort memoization if it encounters any. + + +\paragraph{A minimal issue with \hologo{XeLaTeX}} +If the very first page of a document of class |minimal|, compiled by +\hologo{XeLaTeX}, happens to be an extern page, we have a problem: all the +regular pages of the document will be of the same size as that extern page. +\hologo{pdfLaTeX} and \hologo{LuaLaTeX} do not exhibit this behaviour, nor do +\hologo{LaTeX} classes other than |minimal|, even when compiled with +\hologo{XeLaTeX}. + + +\paragraph{CollArgs} + +Due to an unfortunate design decision, CollArgs does not accept a dot |.| as +the \meta{token} argument of types, \docref{xparse:r}, \docref{xparse:R}, +\docref{xparse:d}, \docref{xparse:D}, and \docref{xparse:t}. Furthermore, +\refcollargs{append prewrap} and friends (and their macro counterparts) do not +accept a parameter symbol consistently. These issues will be fixed in the next +release. + + +\subsection{Troubleshooting} +\label{sec:troubleshooting} + +\paragraph{Extern extraction does not work} +\label{sec:fadings-problem} +\label{sec:loading-order} + +If the internal extern extraction does not work, you should have gotten one of +the following warnings: +\begin{enumerate} +\item \code{Package memoize Warning: Extraction of externs from document + "\meta{jobname}.pdf" using method "perl" was unsuccessful. Have you set the + shell escape mode as suggested in chapter 1 of the manual?} + + This warning is thrown when the extraction log \meta{jobname}|.mmz.log|, + which should be produced by the extraction script, is incomplete, i.e.\ it + does not finish by |\endinput|.\footnote{The extraction log will never be + absent, because Memoize clears it before executing the extraction script + --- and clearing it creates it if it doesn't exist.} The most likely + reason is that the script was not executed at all, so the question asked in + the warning message is pertinent. However, if all is well with the + shell-mode, you have a mystery at your hands. To gather information, I + suggest inspecting: + \begin{itemize} + \item \meta{jobname}|.log| --- search for \code{runsystem(memoize-extract.pl + ...)}, it will tell you whether the script was executed; + \item the terminal output --- if the script was executed, it should've + announced itself by \code{Extracting externs from \meta{jobname}.pdf}; are + there any further messages between this header and the warning message? + \item \meta{the path to the extern}|.log|, if you are using the + \hologo{TeX}-based extraction (\code{\refmmz{extract}=\refmmz{extract=tex}}). + \end{itemize} + \hologo{TeX}-based extraction will yield this error even in the case of a + missing or corrupted PDF, i.e.\ in + case~\ref{item:warning:extract:cannot-read} below. + +\item\label{item:warning:extract:cannot-read} + \code{Package memoize (perl-based extraction) Warning: Cannot read file + "\meta{jobname}.pdf". Perhaps you have to load Memoize earlier in the + preamble?} + + Embedded extern extraction requires an intact document PDF from the previous + compilation, so Memoize must be loaded before the document PDF is opened for + writing the results of the ongoing compilation. In particular, the PDF is + opened by \PGF library |fadings|, included by \TikZ's libraries |fadings| + and |shadows|, so Memoize must be loaded before any of these libraries. + + With \pkg{beamer}, the problem is particularly acute because the PDF is + opened while loading the class. In this case, simply moving + |\usepackage{memoize}| up the preamble, as suggested, won't help: you have to + write |\RequirePackage{memoize}| \emph{before} |\documentclass{beamer}|! + +\item\label{item:warning:extract:unexpected-size} \code{Package memoize + (perl-based extraction) Warning: I refuse to extract page $n$ from + "\meta{jobname}.pdf", because its size ($\meta{width}\times\meta{height}$) + is not what I expected\\ ($\meta{expected width}\times\meta{expected + height}$)} + + If the compilation which produced the offending extern pages yielded any + errors, you should probably disregard this warning, fix the errors, and + compile again. Otherwise, you have somehow winded up with mismatched + \meta{jobname}|.pdf| and \meta{jobname}\dmmz (the latter file contains + instructions on which pages to extract, complete with the expected + dimensions). Are you sure that they were produced by the same compilation, + and have remained untouched since? Are you perhaps trying to perform the + extraction the second time, after the first extraction + \refscript{memoize-extract.pl--prune}d the PDF? + + If the warning stubbornly persists, but you are sure that the page the script + is refusing to extract is correct, you can force the extraction by adding + option \refscript{memoize-extract.pl--force} to the script invocation, which + can be set by \refmmz{perl extraction options}. However, as such a situation + probably indicates a bug in Memoize, please let me know about it. +\item\code{\myfontexpansionon Invalid dictionary key at + /.../perl5/site\_perl/PDF/API2/Basic/PDF/File.pm line $N$\myfontexpansionoff} + + This can sometimes happen when extracting with the Perl-based script. The + issue might be related to PDF version --- by default, \hologo{TeX} produces + PDFs of version 1.5, while the PDF library + \hreftt{https://metacpan.org/pod/PDF::API2}{PDF::API2} officially only + supports versions up to 1.4 --- but I'm afraid I haven't identified the exact + circumstances yet. Possibly, the externalizing a picture containing an + embedded PDF file might be the culprit. The workaround is to use Python- or + \hologo{TeX}-based extraction, i.e.\ load Memoize with package option + \refmmz{extract}|=|\refmmz{extract=python} or + \refmmz{extract}|=|\refmmz{extract=tex}. +\end{enumerate} + +The failure to extract externs intentionally doesn't result in an error, +because what if you deleted the document PDF on purpose? At any rate, the +compilation will succeed, it's just that as Memoize cannot extract the externs, +they will be produced, and dumped into your document, at each and every +compilation. + +\paragraph{An extern won't be included} + +Did you receive a warning or error message? +\begin{enumerate} +\item \code{Package memoize Warning: Unexpected size of extern "\meta{extern + path}.pdf"; expected\\ $\meta{expected width}\times\meta{expected + height}$, got $\meta{width}\times\meta{height}$} + + This warning is related to warning~\ref{item:warning:extract:unexpected-size} + above, only that it occurs once the extern is extracted. The same + investigative methods apply. +\item \code{!pdfTeX error: pdflatex (file \meta{extern path}.pdf): reading + image file failed} + + This is a fatal error. The extern file got corrupted, somehow --- + inexistent, or even empty, extern files merely trigger recompilation. +\end{enumerate} + +If there was no warning or error --- are you certain that Memoize is +\refmmz{enable}d, and that it is not in the \refmmz{recompile} mode? Remember +that these settings can also apply only to a part of the document; search for +any stray \refcmd{mmzset} or \refcmd{mmznext} commands. + + +\paragraph*{Warnings about duplicate labels, indices, etc.} may be safely +disregarded. + +Externalization causes any (non-immediate) |\write| commands in the extern to +be executed twice, once upon the shipout of the regular page, and once upon the +shipout of the extern page. This results in warnings about doubly defined +labels, hyperreferences, indices, etc. For example, you might get +\texttt{LaTeX Warning: Label `<name>' multiply defined} or \texttt{warning (pdf + backend): ignoring duplicate destination with the name '<name>'}. You can +safely disregard these warnings; they will disappear once the extern is +utilized. + + +\paragraph{Memoization was aborted} + +This warning means that either: +\begin{itemize} +\item you are trying to (auto)memoize a \refenv{tikzpicture} with + \refkey{/tikz/remember picture} set, or more generally, some code which + contains \refcmd{savepos} --- this can't be done, see + section~\ref{sec:tut:good-to-know}; or +\item an error occurred during memoization --- in this case, Memoize cowardly + refuses to proceed with memoization, see section~\ref{sec:known-issues} for + details. +\end{itemize} + + +\subsection{License} + +Copyright \textcopyright\ 2020- SaÅ¡o Živanović. + +This work may be distributed and/or modified under the conditions of the +\hologo{LaTeX} Project Public License, either version 1.3c of this license or +(at your option) any later version. The latest version of this license is +in \url{https://www.latex-project.org/lppl.txt} and version 1.3 or later is +part of all distributions of \hologo{LaTeX} version 2008 or later. + +This work comprises of the sources, generated files, accompanying scripts, +auxiliary files and scripts, documentation, documentation sources, examples and +example sources of packages |memoize|, |nomemoize|, |memoizable|, |auto| and +|collargs|. The files belonging to this work and covered by LPPL are listed in +\meta{texmf}|/doc/generic/memoize/FILES|. + +This work has the LPPL maintenance status `maintained.' The Current Maintainer +of this work is SaÅ¡o Živanović. The work is available on CTAN +at \url{https://ctan.org/pkg/memoize} and on GitHub +at \url{https://github.com/sasozivanovic/memoize}. + + +\subsection{Changelog} + +\newcommand\githubrelease[2]{% + \href{https://github.com/sasozivanovic/memoize/releases/tag/#2}{#1 #2}% +} +\begin{description} +\item[\githubrelease{2020/07/17}{v0.1}] + The proof-of-concept, available only at GitHub. +\item[\githubrelease{2023/10/10}{v1.0.0}] + A complete, fully documented reimplementation. +\end{description} + +\subsection{Acknowledgments} + +First and foremost, my gratitude goes to Stefan Müller of Language Science +Press for his encouragement and patience --- without him, this package might've +remained an idea forever! + +But there were others as well. Shunsaku Hirata of the \hologo{XeTeX} team +promptly resolved an issue with forward references in |xdvipdfmx|, and thereby +made Memoize work with \hologo{XeTeX}. Joseph Wright kindly didn't object to +me misappropriating \pkg{etoolbox} for other formats. Petra Rübe-Pugliese +(CTAN) and Karl Berry (\hologo{TeX}Live) offered good advice on packaging the +package; I would never get to call Advice Advice without their advice. Karl +also performed a security review of the extraction scripts, providing some very +useful comments along the way, and agreed to include the scripts among the +shell escape commands allowed in \hologo{TeX}Live. + +Thank you all! + + +\printindex + +\end{document} + + +% Local Variables: +% TeX-engine: luatex +% TeX-master: t +% End: diff --git a/macros/generic/memoize/doc/yadoc.sty b/macros/generic/memoize/doc/yadoc.sty new file mode 100644 index 0000000000..c069b11f55 --- /dev/null +++ b/macros/generic/memoize/doc/yadoc.sty @@ -0,0 +1,491 @@ +%% yadoc.sty +%% +%% This file is a part of Memoize, a TeX package for externalization of +%% graphics and memoization of compilation results in general, available at +%% https://ctan.org/pkg/memoize and https://github.com/sasozivanovic/memoize. +%% +%% Copyright (c) 2020- Saso Zivanovic <saso.zivanovic@guest.arnes.si> +%% (Sa\v{s}o \v{Z}ivanovi\'{c}) +%% +%% This work may be distributed and/or modified under the conditions of the +%% LaTeX Project Public License, either version 1.3c of this license or (at +%% your option) any later version. The latest version of this license is in +%% https://www.latex-project.org/lppl.txt and version 1.3c or later is part of +%% all distributions of LaTeX version 2008 or later. +%% +%% This work has the LPPL maintenance status `maintained'. +%% The Current Maintainer of this work is Saso Zivanovic. +%% +%% The files belonging to this work and covered by LPPL are listed in +%% (<texmf>/doc/generic/memoize/)FILES. + +\RequirePackage{tcolorbox} +%\tcbuselibrary{listings,documentation,xparse,raster,hooks,breakable} +\tcbuselibrary{raster,skins} + +\RequirePackage{etoolbox} + +\newif\ifyadoc@into@index +\newif\ifyadoc@index@gather +\newif\ifyadoc@index@colorize@names +\newif\ifyadoc@ref@page +\newif\ifyadoc@show@keypath + +\newcommand\yadocset[1]{\pgfqkeys{/yadoc}{#1}\ignorespaces} + +\yadocset{ + cmd item/.style={ + name prefix=\textbackslash, + label prefix=cmd, + }, + key item/.style={ + name prefix={\ifdefempty{\yadoc@keypath}{}{% + \ifyadoc@show@keypath + \textcolor + {\yadoc@name@color!\yadoc@keypath@color@opacity}% + {\yadoc@keypath/}% + \fi + }% + }, + ref prefix={\protect\ifyadoc@show@keypath\yadoc@keypath/\protect\fi}, + index prefix=, + parameters prefix={\texttt{=}}, + label prefix:=key:\yadoc@keypath@label/, + index annotation={key\ifdefempty{\yadoc@keypath}{}{ in \texttt{\yadoc@keypath}}}, + }, + env item/.style={ + head prefix={\texttt{\cs{begin}\{}}, + head infix={\}}, + head suffix={% + \par\strut{\ttfamily\nobreakspace\nobreakspace}\meta{environment body}% + \par\strut\texttt{\cs{end}\{\yadoc@do@name\}}}, + index annotation=environment, + label prefix=env, + }, + register item/.style={ + name prefix=\textbackslash, + label prefix=reg, + index annotation=register, + }, + file item/.style={ + index annotation=file, + label prefix=file, + }, + value item/.style={ + index annotation={value of + {\docref[show keypath,into index=false,link color=gray]{\yadoc@of}}}, + label prefix:={\yadoc@of=}, + }, + scope/.code={% + \begingroup + \pgfkeysalso{#1}% + \endgroup + }, + generic item/.style={ + label prefix:=, + }, + % + raster options/.style={}, + before body/.store in=\yadoc@before@body, + after body/.store in=\yadoc@after@body, + left/.dimstore in=\yadoc@left, + right/.dimstore in=\yadoc@right, + left indent/.dimstore in=\kvtcb@doc@indentleft, + right indent/.dimstore in=\kvtcb@doc@indentright, + name/.store in=\yadoc@name, + name/.append style={label={#1}, index={#1}, sort index={#1}}, + parameters/.store in=\yadoc@parameters, + description/.store in=\yadoc@description, + label/.store in=\yadoc@label, + label prefix:/.store in=\yadoc@label@prefix, + label prefix/.style={label prefix:={#1:}}, + name font/.store in=\yadoc@name@font, + name color/.store in=\yadoc@name@color, + name prefix/.store in=\yadoc@name@prefix, + name suffix/.store in=\yadoc@name@suffix, + name prefix/.append style={index prefix={#1}, ref prefix={#1}}, + name suffix/.append style={index suffix={#1}, ref suffix={#1}}, + parameters font/.store in=\yadoc@parameters@font, + parameters prefix/.store in=\yadoc@parameters@prefix, + parameters suffix/.store in=\yadoc@parameters@suffix, + keypath/.store in=\yadoc@keypath, + keypath label/.store in=\yadoc@keypath@label, + keypath/.append style={keypath label={#1}}, + keypath color opacity/.store in=\yadoc@keypath@color@opacity, + show keypath/.is if=yadoc@show@keypath, + index/.store in=\yadoc@index, + index prefix/.store in=\yadoc@index@prefix, + index suffix/.store in=\yadoc@index@suffix, + index annotation/.initial=, + index font/.store in=\yadoc@index@font, + index annotation font/.store in=\yadoc@index@annotation@font, + sort index/.store in=\yadoc@sort@index, + index colorize names/.is if=yadoc@index@colorize@names, + index page number format/.store in=\yadoc@index@page@number@format, + index gather/.is if=yadoc@index@gather, + index command/.store in=\yadoc@index@command, + index actual/.store in=\yadoc@index@actual, + index quote/.store in=\yadoc@index@quote, + index level/.store in=\yadoc@index@level, + index encapsulator/.store in=\yadoc@index@encapsulator, + see/.initial=, + ref font/.store in=\yadoc@ref@font, + ref prefix/.store in=\yadoc@ref@prefix, + ref suffix/.store in=\yadoc@ref@suffix, + head prefix/.store in=\yadoc@head@prefix, + head infix/.store in=\yadoc@head@infix, + head suffix/.store in=\yadoc@head@suffix, + phrases/.code={\pgfqkeys{/yadoc/phrases}{#1}}, + phrases/.unknown/.code={\csdef{yadoc@phrases@\pgfkeyscurrentname}{#1}}, + ref page/.is if=yadoc@ref@page, + of/.store in=\yadoc@of, + link color/.code={\hypercolor{link}{#1}}, + long description/.style={% for do/def + head/.append style={ + sidebyside gap=1em, + lefthand ratio=#1, + }, + }, + long description/.default=0.6, + % + .unknown/.code={% + \pgfkeysifdefined{/yadoc/\pgfkeyscurrentname\space item/.@cmd}{% + \begingroup + \expandafter\pgfkeysalso\expandafter{% + \pgfkeyscurrentname\space item, + before,#1,after, + do + }% + \endgroup + }{% + \PackageError{yadoc}{Unknown item "\pgfkeyscurrentname"}% + {Perhaps you have misspelled an option name?}% + }% + }, + before/.code={}, + after/.code={}, + do/.is choice, + ref options/.style={ + show keypath=false, + }, + def options/.style={ + show keypath, + }, + aux options/.style={ + show keypath=false, + }, + foreign options/.style={ + show keypath=false, + cmd item/.append style={index annotation=command}, + }, + index options/.code=, + into index/.is if=yadoc@into@index, + print/.is if=yadoc@printref, +} + +\NewDocumentEnvironment{doc}{O{}m}{% + \begin{list}{}{% + \setlength{\leftmargin}{\yadoc@left}% + \setlength{\itemindent}{0pt}% + \setlength{\itemsep}{0pt}% + \setlength{\parsep}{0pt}% + \setlength{\rightmargin}{\yadoc@right}% + }% + \item + \begin{tcboxedraster}[ + raster columns=1, + raster row skip=0pt, + raster before skip=0pt, + raster after skip=0pt, + raster force size=false, + /yadoc/raster options + ]{blankest, + before skip=1\baselineskip plus 6\baselineskip minus .5\baselineskip, + after skip=0pt plus 2pt minus .5\baselineskip,% + #1% + }% + \yadocset{do/.default=def,def options,#2}% + \end{tcboxedraster} + \nopagebreak + \yadoc@before@body\relax + \nopagebreak + \ignorespaces +}{% + \ifvmode\else\unskip\fi + \yadoc@after@body\relax + \end{list}% +} + +\newcommand\docaux[2]{% + \begingroup + \yadocset{do/.default=aux,aux options,#1={name={#2}}}% + \endgroup +} + +\newcommand\docAux[1]{% + \begingroup + \yadocset{do/.default=aux,aux options,#1}% + \endgroup +} + +\newcommand\docForeign[1]{% + \begingroup + \yadoc@hyperreffalse + \yadocset{do/.default=foreign,foreign options,#1}% + \endgroup +} + +\newcommand\docindex[1]{% + \begingroup + \yadoc@hyperreffalse + \yadocset{do/.default=index,index options,#1}% + \endgroup +} + +\yadocset{ + do/def/.code={% + \begin{tcolorbox}[% + blank, + colback=white, colframe=white, + code={% + \tcbdimto\tcb@temp@grow@left{\yadoc@left}% + \tcbdimto\tcb@temp@grow@right{\yadoc@right}% + }, + grow to left by=\tcb@temp@grow@left, + grow to right by=\tcb@temp@grow@right, + sidebyside, sidebyside align=top, sidebyside gap=-\tcb@w@upper@real, + force nobeforeafter, + phantom=\phantomsection, + /yadoc/head, + ]% + \yadoc@head@prefix + \yadoc@do@name + \yadoc@do@index + \yadoc@do@label + \yadoc@head@infix + \yadoc@do@parameters + \yadoc@head@suffix + \yadoc@do@description + \end{tcolorbox} + \nopagebreak + }, + do/aux/.code={% + \yadoc@do@name + \yadoc@do@index + \yadoc@do@label + }, + do/foreign/.code={% + \yadoc@do@label + }, + do/index/.code={% + \yadoc@do@label + \yadoc@do@index + }, +} + +\def\yadoc@do@name{% + \begingroup + \yadoc@name@colorize{% + \yadoc@name@font + \strut\yadoc@name@prefix\yadoc@name\yadoc@name@suffix\strut + }% + \endgroup +} + +\def\yadoc@do@index{% + \ifyadoc@into@index + \yadoc@index@command{% + \yadoc@sort@index + \yadoc@index@actual + \yadoc@format@index + }% + \ifyadoc@index@gather + \yadoc@index@command{% + \yadoc@phrases@Commands + \yadoc@index@level + \yadoc@sort@index + \yadoc@index@actual + \yadoc@format@index + }% + \fi + \fi +} + +\def\yadoc@name@colorize{% + \ifdefempty\yadoc@name@color{}{% + \textcolor{\yadoc@name@color}% + }% +} + +\def\yadoc@format@index{% + {% + \ifyadoc@index@colorize@names\yadoc@name@colorize\fi + \yadoc@index@font + \yadoc@index@prefix\yadoc@index\yadoc@index@suffix + }% + {% + \ifkeyempty{/yadoc/index annotation}{}{% + \space\yadoc@index@annotation@font\pgfkeysvalueof{/yadoc/index annotation}% + }% + }% + \ifdefempty\yadoc@index@page@number@format{}{% + \yadoc@index@encapsulator + \yadoc@index@page@number@format + }% + \ifkeyempty{/yadoc/see}{}{% + \yadoc@index@encapsulator + see{\pgfkeysvalueof{/yadoc/see}}% + }% +} + +\def\yadoc@do@label{% + \ifdefempty{\yadoc@label}{}{% + \begingroup + \yadocset{ref options}% + \protected@edef\@currentlabel{% + \protect\ifyadoc@setupref + \ifyadoc@hyperref + \protect\yadoc@hyperreftrue + \else + \protect\yadoc@hyperreffalse + \fi + \protect\fi + \protect\ifyadoc@printref + \yadoc@ref@font + \yadoc@ref@prefix\yadoc@name\yadoc@ref@suffix + \protect\fi + \protect\ifyadoc@into@index + \yadoc@do@index + \protect\fi + }% + \label{\yadoc@label@prefix\yadoc@label}% + \endgroup + }% +} + +\def\yadoc@do@parameters{% + \ifdefempty\yadoc@parameters{}{% + \begingroup + \yadoc@parameters@font + \yadoc@parameters@prefix\yadoc@parameters\yadoc@parameters@suffix + \endgroup + }% +} + +\def\yadoc@do@description{% + \ifdefempty{\yadoc@description}{}{% + \tcblower + \raggedleft + \strut + (\yadoc@description)% + \strut + }% +} + +% ref + +\newif\ifyadoc@setupref +\newif\ifyadoc@printref +\newif\ifyadoc@hyperref +\yadoc@printreftrue +\yadoc@hyperreftrue +\DeclareRobustCommand\docref[2][]{% + \quitvmode % otherwise, extra vertical space if this begins an \item !?! + \begingroup + \yadocset{ref options,#1}% + \ifcsname r@#2\endcsname + \expanded{% + \noexpand\yadoc@setupreftrue + \ifyadoc@printref\noexpand\yadoc@printreffalse\fi + \ifyadoc@into@index\noexpand\yadoc@into@indexfalse\fi + \ifmemoize\noexpand\memoizefalse\fi + \ref*{#2}% + \noexpand\yadoc@setupreffalse + \ifyadoc@printref\noexpand\yadoc@printreftrue\fi + \ifyadoc@into@index\noexpand\yadoc@into@indextrue\fi + \ifmemoize\noexpand\memoizetrue\fi + }% + \fi + \ifyadoc@printref\else\yadoc@hyperreffalse\fi + \ifyadoc@hyperref + \ifnum\getpagerefnumber{#2}=\thepage\relax + \yadoc@into@indexfalse + \fi + \hyperref[#2]{% + \ref*{#2}% + \ifyadoc@printref + \ifyadoc@ref@page\yadoc@ref@pagenumber{#2}\fi + \fi + }% + \else + \ref*{#2}% + \ifyadoc@printref + \ifyadoc@ref@page\yadoc@ref@pagenumber{#2}\fi + \fi + \fi + \endgroup +} +\def\yadoc@ref@pagenumber#1{% + \ifnum\getpagerefnumber{#1}=\thepage\relax + \else% + \textsuperscript{% + {\fontfamily{pzd}\fontencoding{U}\fontseries{m}\fontshape{n}% + \selectfont\char213}% + \,\yadoc@phrases@pageshort\,\pageref*{#1}}% + \fi +} + + +\yadocset{ + before body=, + after body=, + left=2em, + right=0pt, + left indent=-2em, + right indent=0pt, + name=, + name font=\ttfamily\bfseries, + index font=\ttfamily, + ref font=\ttfamily, + index annotation font=, + name color=black, + name prefix=, + name suffix=, + label=, + label prefix=, + parameters=, + parameters font=, + parameters prefix=, + parameters suffix=, + keypath=, + keypath color opacity=30, + description=, + into index=true, + index colorize names=false, + index page number format=, + index command=\index, + index gather=false, + index actual={@}, + index quote={"}, + index level={!}, + index encapsulator={|}, + head prefix=, + head infix=, + head suffix=, + ref page=false, + phrases={ + index=Index, + pageshort=P., + Commands=Commands, + }, + text/.code={#1}, + comma/.code={,\ }, + and/.code={\ and\ }, +} + +\def\ifkeyempty#1{\ifcsempty{pgfk@#1}} + +\AddToHook{begindocument}{% + \robustify\index + \robustify\hypersetup +} diff --git a/macros/generic/memoize/memoize-clean.pl b/macros/generic/memoize/memoize-clean.pl new file mode 100755 index 0000000000..4e1334c695 --- /dev/null +++ b/macros/generic/memoize/memoize-clean.pl @@ -0,0 +1,163 @@ +#!/usr/bin/env perl + +# This file is a part of Memoize, a TeX package for externalization of +# graphics and memoization of compilation results in general, available at +# https://ctan.org/pkg/memoize and https://github.com/sasozivanovic/memoize. +# +# Copyright (c) 2020- Saso Zivanovic <saso.zivanovic@guest.arnes.si> +# +# This work may be distributed and/or modified under the conditions of the +# LaTeX Project Public License, either version 1.3c of this license or (at +# your option) any later version. The latest version of this license is in +# https://www.latex-project.org/lppl.txt and version 1.3c or later is part of +# all distributions of LaTeX version 2008 or later. +# +# This work has the LPPL maintenance status `maintained'. +# The Current Maintainer of this work is Saso Zivanovic. +# +# The files belonging to this work and covered by LPPL are listed in +# <texmf>/doc/generic/memoize/FILES. + +use strict; +use Getopt::Long; +# I intend to rewrite this script using Path::Class. +use Cwd 'realpath'; +use File::Spec; +use File::Basename; + +my $VERSION = '2023/10/10 v1.0.0'; + +my $usage = "usage: memoize-clean.py [-h] [--yes] [--all] [--quiet] [--prefix PREFIX] [mmz ...]\n"; +my $Help = <<END; +Remove (stale) memo and extern files. + +positional arguments: + mmz .mmz record files + +options: + -h, --help show this help message and exit + --version, -V show version and exit + --yes, -y Do not ask for confirmation. + --all, -a Remove *all* memos and externs. + --quiet, -q + --prefix PREFIX, -p PREFIX + A path prefix to clean; this option can be specified multiple times. + +For details, see the man page or the Memoize documentation. +END + +my ($yes, $all, @prefixes, $quiet, $help, $print_version); +GetOptions( + "yes|y" => \$yes, + "all|a" => \$all, + "prefix|p=s" => \@prefixes, + "quiet|q|?" => \$quiet, + "help|h|?" => \$help, + "version|V" => \$print_version, + ) or die $usage; +$help and die "$usage\n$Help"; +if ($print_version) { print("memoize-clean.pl of Memoize $VERSION\n"); exit 0 } + +my (%keep, %prefixes); + +my $curdir = Cwd::getcwd(); + +for my $prefix (@prefixes) { + $prefixes{Cwd::realpath(File::Spec->catfile(($curdir), $prefix))} = ''; +} + +my @mmzs = @ARGV; + +for my $mmz (@mmzs) { + my ($mmz_filename, $mmz_dir) = File::Basename::fileparse($mmz); + @ARGV = ($mmz); + my $endinput = 0; + my $empty = -1; + my $prefix = ""; + while (<>) { + if (/^ *$/) { + } elsif ($endinput) { + die "Bailing out, \\endinput is not the last line of file $mmz.\n"; + } elsif (/^ *\\mmzPrefix *{(.*?)}/) { + $prefix = $1; + $prefixes{Cwd::realpath(File::Spec->catfile(($curdir,$mmz_dir), $prefix))} = ''; + $empty = 1 if $empty == -1; + } elsif (/^%? *\\mmz(?:New|Used)(?:CC?Memo|Extern) *{(.*?)}/) { + my $fn = $1; + if ($prefix eq '') { + die "Bailing out, no prefix announced before file $fn.\n"; + } + $keep{Cwd::realpath(File::Spec->catfile(($mmz_dir), $fn))} = 1; + $empty = 0; + if (rindex($fn, $prefix, 0) != 0) { + die "Bailing out, prefix of file $fn does not match " . + "the last announced prefix ($prefix).\n"; + } + } elsif (/^ *\\endinput *$/) { + $endinput = 1; + } else { + die "Bailing out, file $mmz contains an unrecognized line: $_\n"; + } + } + die "Bailing out, file $mmz is empty.\n" if $empty && !$all; + die "Bailing out, file $mmz does not end with \\endinput; this could mean that " . + "the compilation did not finish properly. You can only clean with --all.\n" + if $endinput == 0 && !$all; +} + +my @tbdeleted; +sub populate_tbdeleted { + my ($basename_prefix, $dir, $suffix_dummy) = @_; + opendir(MD, $dir) or die "Cannot open directory '$dir'"; + while( (my $fn = readdir(MD)) ) { + my $path = File::Spec->catfile(($dir),$fn); + if ($fn =~ /\Q$basename_prefix\E[0-9A-F]{32}(?:-[0-9A-F]{32})?(?:-[0-9]+)?(\.memo|(?:-[0-9]+)?\.pdf|\.log)/ and ($all || !exists($keep{$path}))) { + push @tbdeleted, $path; + } + } + closedir(MD); +} +for my $prefix (keys %prefixes) { + my ($basename_prefix, $dir, $suffix); + if (-d $prefix) { + populate_tbdeleted('', $prefix, ''); + } + populate_tbdeleted(File::Basename::fileparse($prefix)); +} +@tbdeleted = sort(@tbdeleted); + +my @allowed_dirs = ($curdir); +my @deletion_not_allowed; +for my $f (@tbdeleted) { + my $f_allowed = 0; + for my $dir (@allowed_dirs) { + if ($f =~ /^\Q$dir\E/) { + $f_allowed = 1; + last; + } + } + push(@deletion_not_allowed, $f) if ! $f_allowed; +} +die "Bailing out, I was asked to delete these files outside the current directory:\n" . + join("\n", @deletion_not_allowed) if (@deletion_not_allowed); + +if (scalar(@tbdeleted) != 0) { + my $a; + unless ($yes) { + print("I will delete the following files:\n" . + join("\n",@tbdeleted) . "\n" . + "Proceed (y/n)? "); + $a = lc(<>); + chomp $a; + } + if ($yes || $a eq 'y' || $a eq 'yes') { + foreach my $fn (@tbdeleted) { + print "Deleting ", $fn, "\n" unless $quiet; + unlink $fn; + } + } else { + die "Bailing out.\n"; + } +} elsif (!$quiet) { + print "Nothing to do, the directory seems clean.\n"; +} diff --git a/macros/generic/memoize/memoize-clean.py b/macros/generic/memoize/memoize-clean.py new file mode 100755 index 0000000000..dd543327cb --- /dev/null +++ b/macros/generic/memoize/memoize-clean.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python + +# This file is a part of Memoize, a TeX package for externalization of +# graphics and memoization of compilation results in general, available at +# https://ctan.org/pkg/memoize and https://github.com/sasozivanovic/memoize. +# +# Copyright (c) 2020- Saso Zivanovic <saso.zivanovic@guest.arnes.si> +# +# This work may be distributed and/or modified under the conditions of the +# LaTeX Project Public License, either version 1.3c of this license or (at +# your option) any later version. The latest version of this license is in +# https://www.latex-project.org/lppl.txt and version 1.3c or later is part of +# all distributions of LaTeX version 2008 or later. +# +# This work has the LPPL maintenance status `maintained'. +# The Current Maintainer of this work is Saso Zivanovic. +# +# The files belonging to this work and covered by LPPL are listed in +# <texmf>/doc/generic/memoize/FILES. + +__version__ = '2023/10/10 v1.0.0' + +import argparse, re, sys, pathlib, os + +parser = argparse.ArgumentParser( + description="Remove (stale) memo and extern files.", + epilog = "For details, see the man page or the Memoize documentation." +) +parser.add_argument('--yes', '-y', action = 'store_true', + help = 'Do not ask for confirmation.') +parser.add_argument('--all', '-a', action = 'store_true', + help = 'Remove *all* memos and externs.') +parser.add_argument('--quiet', '-q', action = 'store_true') +parser.add_argument('--prefix', '-p', action = 'append', default = [], + help = 'A path prefix to clean; this option can be specified multiple times.') +parser.add_argument('mmz', nargs= '*', help='.mmz record files') +parser.add_argument('--version', '-V', action = 'version', + version = f"%(prog)s of Memoize " + __version__) +args = parser.parse_args() + +re_prefix = re.compile(r'\\mmzPrefix *{(.*?)}') +re_memo = re.compile(r'%? *\\mmz(?:New|Used)(?:CC?Memo|Extern) *{(.*?)}') +re_endinput = re.compile(r' *\\endinput *$') + +prefixes = set(pathlib.Path(prefix).resolve() for prefix in args.prefix) +keep = set() + +# We loop through the given .mmz files, adding prefixes to whatever manually +# specified by the user, and collecting the files to keep. +for mmz_fn in args.mmz: + mmz = pathlib.Path(mmz_fn) + mmz_parent = mmz.parent.resolve() + try: + with open(mmz) as mmz_fh: + prefix = '' + endinput = False + empty = None + for line in mmz_fh: + line = line.strip() + + if not line: + pass + + elif endinput: + raise RuntimeError( + r'Bailing out, \endinput is not the last line of file $mmz_fn.') + + elif m := re_prefix.match(line): + prefix = m[1] + prefixes.add( (mmz_parent/prefix).resolve() ) + if empty is None: + empty = True + + elif m := re_memo.match(line): + if not prefix: + raise RuntimeError( + f'Bailing out, no prefix announced before file "{m[1]}".') + if not m[1].startswith(prefix): + raise RuntimeError( + f'Bailing out, prefix of file "{m[1]}" does not match ' + f'the last announced prefix ({prefix}).') + keep.add((mmz_parent / m[1])) + empty = False + + elif re_endinput.match(line): + endinput = True + continue + + else: + raise RuntimeError(fr"Bailing out, " + fr"file {mmz_fn} contains an unrecognized line: {line}") + + if empty and not args.all: + raise RuntimeError(fr'Bailing out, file {mmz_fn} is empty.') + + if not endinput and empty is not None and not args.all: + raise RuntimeError( + fr'Bailing out, file {mmz_fn} does not end with \endinput; ' + fr'this could mean that the compilation did not finish properly. ' + fr'You can only clean with --all.' + ) + + # It is not an error if the file doesn't exist. + # Otherwise, cleaning from scripts would be cumbersome. + except FileNotFoundError: + pass + +tbdeleted = [] +def populate_tbdeleted(folder, basename_prefix): + re_aux = re.compile( + re.escape(basename_prefix) + + '[0-9A-F]{32}(?:-[0-9A-F]{32})?(?:-[0-9]+)?(?:\.memo|(?:-[0-9]+)?\.pdf|\.log)$') + try: + for f in folder.iterdir(): + if re_aux.match(f.name) and (args.all or f not in keep): + tbdeleted.append(f) + except FileNotFoundError: + pass + +for prefix in prefixes: + # "prefix" is interpreted both as a directory (if it exists) and a basename prefix. + if prefix.is_dir(): + populate_tbdeleted(prefix, '') + populate_tbdeleted(prefix.parent, prefix.name) + +allowed_dirs = [pathlib.Path().absolute()] # todo: output directory +deletion_not_allowed = [f for f in tbdeleted if not f.is_relative_to(*allowed_dirs)] +if deletion_not_allowed: + raise RuntimeError("Bailing out, " + "I was asked to delete these files outside the current directory:\n" + + "\n".join(str(f) for f in deletion_not_allowed)) + +_cwd_absolute = pathlib.Path().absolute() +def relativize(path): + try: + return path.relative_to(_cwd_absolute) + except ValueError: + return path + +if tbdeleted: + tbdeleted.sort() + if not args.yes: + print('I will delete the following files:') + for f in tbdeleted: + print(relativize(f)) + print("Proceed (y/n)? ") + a = input() + if args.yes or a == 'y' or a == 'yes': + for f in tbdeleted: + if not args.quiet: + print("Deleting", relativize(f)) + try: + f.unlink() + except FileNotFoundError: + print(f"Cannot delete {f}") + else: + print("Bailing out.") +elif not args.quiet: + print('Nothing to do, the directory seems clean.') diff --git a/macros/generic/memoize/memoize-extract.pl b/macros/generic/memoize/memoize-extract.pl new file mode 100755 index 0000000000..8fba3d356f --- /dev/null +++ b/macros/generic/memoize/memoize-extract.pl @@ -0,0 +1,205 @@ +#!/usr/bin/env perl + +# This file is a part of Memoize, a TeX package for externalization of +# graphics and memoization of compilation results in general, available at +# https://ctan.org/pkg/memoize and https://github.com/sasozivanovic/memoize. +# +# Copyright (c) 2020- Saso Zivanovic <saso.zivanovic@guest.arnes.si> +# +# This work may be distributed and/or modified under the conditions of the +# LaTeX Project Public License, either version 1.3c of this license or (at +# your option) any later version. The latest version of this license is in +# https://www.latex-project.org/lppl.txt and version 1.3c or later is part of +# all distributions of LaTeX version 2008 or later. +# +# This work has the LPPL maintenance status `maintained'. +# The Current Maintainer of this work is Saso Zivanovic. +# +# The files belonging to this work and covered by LPPL are listed in +# <texmf>/doc/generic/memoize/FILES. + +use strict; +use Getopt::Long; +use Path::Class; +use File::Spec; +use File::Basename; +use PDF::API2; + +my $VERSION = '2023/10/10 v1.0.0'; + +my $usage = "usage: memoize-extract.pl [-h] [--pdf PDF] [--prune] [--keep] [--force] [--log LOG] [--warning-template WARNING_TEMPLATE] [--quiet] [--mkdir] mmz\n"; +my $Help = <<END; +Extract extern pages out of the document PDF. + +positional arguments: + mmz the record file produced by Memoize: doc.mmz when compiling doc.tex + +options: + --help, -h show this help message and exit + --version, -V show version and exit + --pdf PDF, -P PDF extract from file PDF + --prune, -p remove the extern pages after extraction + --keep, -k do not mark externs as extracted + --force, -f extract even if the size-check fails + --log LOG, -l LOG the log file + --warning-template WARNING_TEMPLATE, -w WARNING_TEMPLATE + \warningtext in the template will be replaced by the warning message + --quiet, -q don't describe what's happening + --embedded, -e prefix all messages to the standard output with the script name + --mkdir, -m create a directory (and exit) + +For details, see the man page or the Memoize documentation. +END + +my ($pdf_arg, $prune, $keep, $log, $quiet, $embedded, $force, $mkdir, $help, $print_version); +my $warning_template = '\warningtext'; +Getopt::Long::Configure ("bundling"); +GetOptions( + "pdf|P=s" => \$pdf_arg, + "keep|k" => \$keep, + "prune|p" => \$prune, + "log|l=s" => \$log, + "quiet|q" => \$quiet, + "embedded|e" => \$embedded, + "force|f" => \$force, + "warning-template|w=s" => \$warning_template, + "mkdir|m" => \$mkdir, + "help|h|?" => \$help, + "version|V" => \$print_version, + ) or die $usage; +if ($help) {print("$usage\n$Help"); exit 0} +if ($print_version) { print("memoize-extract.pl of Memoize $VERSION\n"); exit 0 } +die $usage unless @ARGV == 1; + +my $message_prefix = $embedded ? basename($0) . ': ' : ''; +print("\n") if ($embedded); + +my @output_paths = (dir()->absolute->resolve); +my $texmfoutput = `kpsewhich --var-value=TEXMFOUTPUT`; +$texmfoutput =~ s/^\s+|\s+$//g; +if ($texmfoutput) { + my $texmfoutput_dir = dir($texmfoutput)->absolute->resolve; + push(@output_paths, $texmfoutput_dir) unless $texmfoutput_dir->dir_list == 1 && ! $texmfoutput_dir->volume; +} + +sub paranoia { + my $file = $_[0]; + die "${message_prefix}Cannot create a hidden file or dir: $file" + if $file->basename =~ /^\./; + my $parent = $file->parent->absolute->resolve; + die "${message_prefix}Cannot write outside the current working or output directory tree: $file" + unless grep($_->contains($parent), @output_paths); +} + +my $mmz_arg = $ARGV[0]; +my $mmz_file = file($mmz_arg); +my $mmz_dir = $mmz_file->parent; + +if ($mkdir) { + my $dir = dir($mmz_arg)->cleanup; + my $current = dir(File::Spec->catpath($dir->volume, + $dir->is_absolute ? File::Spec->rootdir : File::Spec->curdir, + ''))->absolute; + for my $c ($dir->components) { + $current = $current->subdir($c); + if (-d $current) { + $current = $current->resolve; + } else { + paranoia($current); + mkdir($current); + print("${message_prefix}Created directory $current\n") unless $quiet; + } + } + exit; +} else { + die "${message_prefix}The 'mmz' argument should be a file with suffix '.mmz', not '$mmz_file'\n" unless $mmz_file->basename =~ /\.mmz$/; +} + +# Enable in-place editing (of the .mmz file). +paranoia($mmz_file) unless $keep; +$^I = "" unless $keep; + +my $pdf_file = $pdf_arg ? file($pdf_arg) : $mmz_dir->file($mmz_file->basename =~ s/\.mmz$/\.pdf/r)->cleanup; +paranoia($pdf_file) if $prune; + +if ($log) { + paranoia(file($log)); + open LOG, ">$log"; +} else { + *LOG = *STDERR; +} + +my ($pdf, %extern_pages); +print "${message_prefix}Extracting externs from $pdf_file:\n" unless $quiet; + +my $tolerance = 0.01; +my $done_message = "${message_prefix}Done (there was nothing to extract).\n"; + +while (<>) { + if (/^\\mmzNewExtern *{(?P<extern_path>(?P<prefix>.*?)(?P<code_md5sum>[0-9A-F]{32})-(?P<context_md5sum>[0-9A-F]{32})(?:-[0-9]+)?.pdf)}{(?P<page_n>[0-9]+)}{(?P<expected_width>[0-9.]*)pt}{(?P<expected_height>[0-9.]*)pt}/) { + my $extern_file = file($+{extern_path}); + if (! $extern_file->is_absolute) { + $extern_file = $mmz_dir->file($+{extern_path}); + } + paranoia($extern_file); + my $page = $+{page_n}; + my $expected_width_pt = $+{expected_width}; + my $expected_height_pt = $+{expected_height}; + my $c_memo_file = $mmz_dir->file($+{prefix} . $+{code_md5sum} . '.memo'); + my $cc_memo_file = $mmz_dir->file($+{prefix} . $+{code_md5sum} . '-' . $+{context_md5sum} . '.memo'); + if (!(-e $c_memo_file && -e $cc_memo_file)) { + print LOG ($warning_template =~ s/\\warningtext/Not extracting page $page into extern $extern_file, because the associated (c)c-memo does not exist/gr), "\n\\endinput\n"; + last; + } + eval { $pdf = PDF::API2->open($pdf_file->stringify) unless $pdf; }; + if ($@) { + print LOG ($warning_template =~ s/\\warningtext/Cannot read file "$pdf_file". Perhaps you have to load Memoize earlier in the preamble?/gr), "\n\\endinput\n"; + close LOG; + die "${message_prefix}File '$pdf_file' cannot be read, bailing out.\n"; + } + my $extern = PDF::API2->new(); + $extern->version($pdf->version); + $extern->import_page($pdf, $page); + my $extern_page = $extern->open_page(1); + my ($x0, $y0, $x1, $y1) = $extern_page->get_mediabox(); + my $width_pt = ($x1 - $x0) / 72 * 72.27; + my $height_pt = ($y1 - $y0) / 72 * 72.27; + my $warning = ''; + if (abs($width_pt - $expected_width_pt) > $tolerance + || abs($height_pt - $expected_height_pt) > $tolerance) + { + $warning = "I refuse to extract page $page from $pdf_file, because its size (${width_pt}pt x ${height_pt}pt) is not what I expected (${expected_width_pt}pt x ${expected_height_pt}pt)"; + print LOG ($warning_template =~ s/\\warningtext/$warning/gr), "\n"; + } + if ($warning && !$force) { + unlink $extern_file; + } else { + $extern->saveas($extern_file->stringify); + $done_message = "${message_prefix}Done.\n"; + print STDOUT "${message_prefix} Page $page --> $extern_file\n" unless $quiet; + $extern_pages{$page} = 1 if $prune; + print("%") unless $keep; + } + } + print unless $keep; +} + +print $done_message unless $quiet; + +if ($pdf and $prune) { + paranoia($pdf_file); + my $pruned_pdf = PDF::API2->new(); + for (my $n = 1; $n <= $pdf->page_count(); $n++) { + if (! $extern_pages{$n}) { + $pruned_pdf->import_page($pdf, $n); + } + } + $pruned_pdf->save($pdf_file->stringify); + print("${message_prefix}The following extern pages were pruned out of the PDF: ", + join(",", sort(keys(%extern_pages))) . "\n") unless $quiet; +} + +if ($log) { + print LOG "\\endinput\n"; + close LOG; +} diff --git a/macros/generic/memoize/memoize-extract.py b/macros/generic/memoize/memoize-extract.py new file mode 100755 index 0000000000..54d8870e7b --- /dev/null +++ b/macros/generic/memoize/memoize-extract.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 + +# This file is a part of Memoize, a TeX package for externalization of +# graphics and memoization of compilation results in general, available at +# https://ctan.org/pkg/memoize and https://github.com/sasozivanovic/memoize. +# +# Copyright (c) 2020- Saso Zivanovic <saso.zivanovic@guest.arnes.si> +# +# This work may be distributed and/or modified under the conditions of the +# LaTeX Project Public License, either version 1.3c of this license or (at +# your option) any later version. The latest version of this license is in +# https://www.latex-project.org/lppl.txt and version 1.3c or later is part of +# all distributions of LaTeX version 2008 or later. +# +# This work has the LPPL maintenance status `maintained'. +# The Current Maintainer of this work is Saso Zivanovic. +# +# The files belonging to this work and covered by LPPL are listed in +# <texmf>/doc/generic/memoize/FILES. + +__version__ = '2023/10/10 v1.0.0' + +import argparse, re, sys, os, pdfrw, subprocess, itertools +from pathlib import Path + +parser = argparse.ArgumentParser( + description = "Extract extern pages out of the document PDF.", + epilog = "For details, see the man page or the Memoize documentation.", + prog = 'memoize-extract.py', +) +parser.add_argument('--pdf', '-P', help = 'extract from file PDF') +parser.add_argument('--prune', '-p', action = 'store_true', + help = 'remove the extern pages after extraction') +parser.add_argument('--keep', '-k', action = 'store_true', + help = 'do not mark externs as extracted') +parser.add_argument('--force', '-f', action = 'store_true', + help = 'extract even if the size-check fails') +parser.add_argument('--log', '-l', default = os.devnull, help = 'the log file') +parser.add_argument('--warning-template', '-w', default = '\warningtext', + help = '\warningtext in the template will be replaced by the warning message') +parser.add_argument('--quiet', '-q', action = 'store_true', + help = "describe what's happening") +parser.add_argument('--embedded', '-e', action = 'store_true', + help = "prefix all messages to the standard output with the script name") +parser.add_argument('--mkdir', '-m', action = 'store_true', + help = 'create a directory (and exit)') +parser.add_argument('mmz', + help = 'the record file produced by Memoize: doc.mmz when compiling doc.tex') +parser.add_argument('--version', '-V', action = 'version', + version = f"%(prog)s of Memoize " + __version__) + +args = parser.parse_args() + +message_prefix = parser.prog + ': ' if args.embedded else '' +if args.embedded: + print() + +# Even a bit more paranoid than required: +# (a) disallowing TEXMFOUTPUT=/ (while allowing C:\ on Windows) +# (b) waiting for access to '-output-directory'. +output_paths = [Path.cwd()] +if texmfoutput := subprocess.run( + ['kpsewhich', '--var-value=TEXMFOUTPUT'], + capture_output = True).stdout.decode().strip(): + texmfoutput_dir = Path(texmfoutput).resolve() + if len(texmfoutput_dir.parts) != 1 or texmfoutput_dir.drive: + output_paths.append(texmfoutput_dir) + +def paranoia(f): + p = Path(f).resolve() + if p.stem.startswith('.'): + raise RuntimeError(f"{message_prefix}Cannot create a hidden file or dir: {f}") + if not any(p.is_relative_to(op) for op in output_paths): + raise RuntimeError(f"{message_prefix}Cannot write outside the current working or output directory tree: {f}") + +mmz_file = Path(args.mmz) + +if args.mkdir: # Here, argument "mmz" is interpreted as the directory to create. + # We cannot simply say + # paranoia(mmz_file) + # mmz_file.mkdir(parents = True, exist_ok = True) + # because have be paranoid about the intermediate directories as well: + # memoize-extract.py -m a/../../BAD/../<cwd-name>/b + # Note that paranoia might kick in only after a part of the given path was + # already created. This is in accord to how "mkdir" behaves wrt existing + # files. + for folder in itertools.chain(reversed(mmz_file.parents), (mmz_file,)): + if not folder.is_dir(): + paranoia(folder) + folder.mkdir(exist_ok = True) + if not args.quiet: + print(f"{message_prefix}Created directory {folder}") + sys.exit() +elif mmz_file.suffix != '.mmz': + raise RuntimeError(f"{message_prefix}The 'mmz' argument should be a file with suffix '.mmz', not '{mmz_file}'") + +mmz_dir = mmz_file.parent +pdf_file = Path(args.pdf) if args.pdf else mmz_file.with_suffix('.pdf') +paranoia(pdf_file) +pdf = None +extern_pages = [] +new_mmz = [] +args.log is os.devnull or paranoia(Path(args.log)) +re_newextern = re.compile(r'\\mmzNewExtern *{(?P<extern_fn>(?P<prefix>.*?)(?P<code_md5sum>[0-9A-F]{32})-(?P<context_md5sum>[0-9A-F]{32})(?:-[0-9]+)?.pdf)}{(?P<page_n>[0-9]+)}{(?P<expected_width>[0-9.]*)pt}{(?P<expected_height>[0-9.]*)pt}') +tolerance = 0.01 +done_message = f"{message_prefix}Done (there was nothing to extract)." + +# Complication: I want to use 'with', but don't have to open stderr. +with open(args.log, 'w') as log: + log = sys.stderr if args.log is os.devnull else log + try: + mmz = mmz_file.open() + except FileNotFoundError: + print(f'''{message_prefix}File "{mmz_file}" does not exist, assuming there's nothing to do.''', + file = sys.stderr) + else: + if not args.quiet: + print(f"{message_prefix}Extracting externs from {pdf_file}") + for line in mmz: + if m := re_newextern.match(line): + extern_file = mmz_dir / m['extern_fn'] + paranoia(extern_file) + page_n = int(m['page_n'])-1 + c_memo = mmz_dir / (m['prefix'] + m['code_md5sum'] + '.memo') + cc_memo = mmz_dir / (m['prefix'] + m['code_md5sum'] + '-' + m['context_md5sum'] + '.memo') + if not (c_memo.exists() and cc_memo.exists()): + print(args.warning_template.replace('\warningtext', f'Not extracting page {page_n} into extern {extern_file}, because the associated (c)c-memo does not exist'), file = log) + continue + if not pdf: + try: + pdf = pdfrw.PdfReader(pdf_file) + except pdfrw.errors.PdfParseError: + print(f'{message_prefix}File "{pdf_file}" cannot be read, bailing out.', file = sys.stderr) + print(args.warning_template.replace('\warningtext', f'Cannot read file "{pdf_file}". Perhaps you have to load Memoize earlier in the preamble?'), file = log) + args.keep = True + break + extern = pdfrw.PdfWriter(extern_file) + page = pdf.pages[page_n] + expected_width_pt, expected_height_pt = float(m['expected_width']), float(m['expected_height']) + mb = page['/MediaBox'] + width_bp, height_bp = float(mb[2]) - float(mb[0]), float(mb[3]) - float(mb[1]) + width_pt = width_bp / 72 * 72.27 + height_pt = height_bp / 72 * 72.27 + warning = None + if abs(width_pt - expected_width_pt) > tolerance \ + or abs(height_pt - expected_height_pt) > tolerance: + warning = ( + f'I refuse to extract page {page_n+1} from "{pdf_file}", ' + f'because its size ({width_pt}pt x {height_pt}pt) is not ' + f'what I expected ({expected_width_pt}pt x {expected_height_pt}pt)') + print(args.warning_template.replace('\warningtext', warning), file = log) + if warning and not args.force: + extern_file.unlink(missing_ok = True) + else: + extern.addpage(page) + if not args.quiet: + print(f"{message_prefix} Page {page_n+1} --> {extern_file}", file = sys.__stdout__) + extern.write() + done_message = f"{message_prefix}Done." + if args.prune: + extern_pages.append(page_n) + if not args.keep: + line = '%' + line + if not args.keep: + new_mmz.append(line) + mmz.close() + if not args.quiet: + print(done_message) + if not args.keep: + paranoia(mmz_file) + with open(mmz_file, 'w') as mmz: + for line in new_mmz: + print(line, file = mmz, end = '') + if args.prune and extern_pages: + pruned_pdf = pdfrw.PdfWriter(pdf_file) + pruned_pdf.addpages( + page for n, page in enumerate(pdf.pages) if n not in extern_pages) + pruned_pdf.write() + if not args.quiet: + print(f"{message_prefix}The following extern pages were pruned out of the PDF:", + ",".join(str(page+1) for page in extern_pages)) + if args.log is not os.devnull: + print(r'\endinput', file = log) diff --git a/macros/generic/memoize/memoize.edtx b/macros/generic/memoize/memoize.edtx new file mode 100644 index 0000000000..10794c7a3b --- /dev/null +++ b/macros/generic/memoize/memoize.edtx @@ -0,0 +1,3597 @@ +% \iffalse +% +% memoize.edtx (this is not a .dtx file; to produce a .dtx, process it with edtx2dtx) +% +%% This file is a part of Memoize, a TeX package for externalization of +%% graphics and memoization of compilation results in general, available at +%% https://ctan.org/pkg/memoize and https://github.com/sasozivanovic/memoize. +%% +%% Copyright (c) 2020- Saso Zivanovic <saso.zivanovic@guest.arnes.si> +%% (Sa\v{s}o \v{Z}ivanovi\'{c}) +%% +%% This work may be distributed and/or modified under the conditions of the +%% LaTeX Project Public License, either version 1.3c of this license or (at +%% your option) any later version. The latest version of this license is in +%% https://www.latex-project.org/lppl.txt and version 1.3c or later is part of +%% all distributions of LaTeX version 2008 or later. +%% +%% This work has the LPPL maintenance status `maintained'. +%% The Current Maintainer of this work is Saso Zivanovic. +%% +%% The files belonging to this work and covered by LPPL are listed in +%% (<texmf>/doc/generic/memoize/)FILES. +% +% \fi +% +% \begin{macrocode} +% +% \relax +% +% This file contains the documented source code of +% package \href{https://ctan.org/pkg/memoize}{Memoize} and, somewhat +% unconventionally, its two independently distributed auxiliary +% packages \href{https://ctan.org/pkg/advice}{Advice} +% and \href{https://ctan.org/pkg/collargs}{CollArgs}. +% +% The source code of the \hologo{TeX} parts of the package resides in +% |memoize.edtx|, |advice.edtx| and |collargs.edtx|. These files are written +% in \href{https://ctan.org/pkg/easydtx}{EasyDTX}, a format of my own invention +% which is almost like the DTX format but eliminates the need for all those +% pesky |macrocode| environments: Any line introduced by a single comment +% counts as documentation, and to top it off, documentation lines may be +% indented. An |.edtx| file is converted to a |.dtx| by a little Perl script +% called |edtx2dtx|; there is also a rudimentary Emacs mode, implemented in +% |easydoctex-mode.el|, which takes care of fontification, indentation, and +% forward and inverse search. +% +% The |.edtx| files contain the code for all three formats supported by the +% three packages --- \hologo{LaTeX} (guard |latex|), \hologo{plainTeX} (guard +% |plain|) and \hologo{ConTeXt} (guard |context|) --- but upon reading the +% code, it will quickly become clear that Memoize was first developed for +% \hologo{LaTeX}. In \S\ref{sec:code:identification}, we manually define +% whatever \hologo{LaTeX} tools are ``missing'' in \hologo{plainTeX} and +% \hologo{ConTeXt}. Even worse, \hologo{ConTeXt} code is often just the same +% as \hologo{plainTeX} code, even in cases where I'm sure \hologo{ConTeXt} +% offers the relevant tools. This nicely proves that I have no clue about +% \hologo{ConTeXt}. If you are willing to \hologo{ConTeXt}-ualize my code --- +% please do so, your help is welcome! +% +% The runtimes of Memoize (and also Advice) comprise of more than just the main +% runtime for each format. Memoize ships with two additional stub packages, +% |nomemoize| and |memoizable|, and a \hologo{TeX}-based extraction script +% |memoize-extract-one|; Advice optionally offers a \TikZ; support defined in +% |advice-tikz.code.tex|. For the relation between guards and runtimes, +% consult the core of the |.ins| files below. +% +% \tcbset{ins listing/.style={listing only, fonttitle=\ttfamily\footnotesize, +% leftupper=-1.5mm, lefttitle=2mm, right=0mm, top=2mm, bottom=2.65mm, +% listing options app={basicstyle=\ttfamily\scriptsize}}} +% +% \begin{tcbraster}[raster columns=100] +% \tcbinputlisting{raster multicolumn=55, ins listing, top=1mm, bottom=1mm, title=memoize.ins,listing file=../memoize.ins, linerange={27-38}, leftupper=1mm} +% \begin{tcboxedraster}[raster columns=1]{blankest, raster multicolumn=45} +% \tcbinputlisting{ins listing, title=advice.ins, listing file=../advice.ins, linerange=28-31} +% \tcbinputlisting{ins listing, title=collar\raisebox{0pt}[\height][0pt]{g}s.ins, listing file=../collargs.ins, linerange=29-31} +% \end{tcboxedraster} +% \end{tcbraster} +% +% Memoize also contains two scripts, |memoize-extract| and |memoize-clean|. +% Both come in two functionally equivalent implementations: Perl (|.pl|) and a +% Python (|.py|). The code is listed in \S\ref{sec:code:scripts}, I believe it +% is self-explanatory enough to lack more than a occasional comment. +% +% +% \thispagestyle{empty} +% \clearpage +% \tableofcontents +% \clearpage +% +% \newgeometry{left=4cm,right=1cm,top=1cm,bottom=1cm, +% marginparwidth=3cm,marginparsep=0pt,nohead,includefoot} +% \settowidth\marginparsep{\ } +% +% \section{First things first} +% \label{sec:code:identification} +% +% \paragraph{Identification} of |memoize|, |memoizable| and |nomemoize|. +%<*mmz> +%<latex>\ProvidesPackage{memoize}[2023/10/10 v1.0.0 Fast and flexible externalization] +%<context>%D \module[ +%<context>%D file=t-memoize.tex, +%<context>%D version=1.0.0, +%<context>%D title=Memoize, +%<context>%D subtitle=Fast and flexible externalization, +%<context>%D author=Saso Zivanovic, +%<context>%D date=2023-10-10, +%<context>%D copyright=Saso Zivanovic, +%<context>%D license=LPPL, +%<context>%D ] +%<context>\writestatus{loading}{ConTeXt User Module / memoize} +%<context>\unprotect +%<context>\startmodule[memoize] +%<plain>% Package memoize 2023/10/10 v1.0.0 +%</mmz> +%<*mmzable> +%<latex>\ProvidesPackage{memoizable}[2023/10/10 v1.0.0 A programmer's stub for Memoize] +%<context>%D \module[ +%<context>%D file=t-memoizable.tex, +%<context>%D version=1.0.0, +%<context>%D title=Memoizable, +%<context>%D subtitle=A programmer's stub for Memoize, +%<context>%D author=Saso Zivanovic, +%<context>%D date=2023-10-10, +%<context>%D copyright=Saso Zivanovic, +%<context>%D license=LPPL, +%<context>%D ] +%<context>\writestatus{loading}{ConTeXt User Module / memoizable} +%<context>\unprotect +%<context>\startmodule[memoizable] +%<plain>% Package memoizable 2023/10/10 v1.0.0 +%</mmzable> +%<*nommz> +%<latex>\ProvidesPackage{nomemoize}[2023/10/10 v1.0.0 A no-op stub for Memoize] +%<context>%D \module[ +%<context>%D file=t-nomemoize.tex, +%<context>%D version=1.0.0, +%<context>%D title=Memoize, +%<context>%D subtitle=A no-op stub for Memoize, +%<context>%D author=Saso Zivanovic, +%<context>%D date=2023-10-10, +%<context>%D copyright=Saso Zivanovic, +%<context>%D license=LPPL, +%<context>%D ] +%<context>\writestatus{loading}{ConTeXt User Module / nomemoize} +%<context>\unprotect +%<context>\startmodule[nomemoize] +%<mmz>% Package nomemoize 2023/10/10 v1.0.0 +%</nommz> +% +% \paragraph{Required packages} and \hologo{LaTeX}ization of \hologo{plainTeX} +% and \hologo{ConTeXt}. +%<*(mmz,mmzable,nommz)&(plain,context)> +\input miniltx +%</(mmz,mmzable,nommz)&(plain,context)> +% Some stuff which is ``missing'' in |miniltx|, copied here from |latex.ltx|. +%<*mmz&(plain,context)> +\def\PackageWarning#1#2{{% + \newlinechar`\^^J\def\MessageBreak{^^J\space\space#1: }% + \message{#1: #2}}} +%</mmz&(plain,context)> +% Same as the official definition, but without |\outer|. Needed for record +% file declarations. +%<*mmz&plain> +\def\newtoks{\alloc@5\toks\toksdef\@cclvi} +\def\newwrite{\alloc@7\write\chardef\sixt@@n} +%</mmz&plain> +% |Nomemoize| has to load |pgfopts| as well, and process package options (right +% away, why not), otherwise \hologo{LaTeX} will complain. +%<*latex> +%<mmz,nommz>\RequirePackage{pgfopts} % pgfkeys-based package options +%<nommz>\pgfkeys{/memoize/package options/.unknown/.code={}} +%<nommz>\ProcessPgfPackageOptions{/memoize/package options} +%</latex> +% I can't really write any code without |etoolbox| \dots +%<*mmz> +%<latex>\RequirePackage{etoolbox} +%<plain,context>\input etoolbox-generic +% Setup the |memoize| namespace in \hologo{LuaTeX}. +\ifdefined\luatexversion + \directlua{memoize = {}} +\fi +% |pdftexcmds.sty| eases access to some PDF primitives, but I cannot manage to +% load it in \hologo{ConTeXt}, even if it's supposed to be a generic +% package. So let's load |pdftexcmds.lua| and copy--paste what we need from +% |pdftexcmds.sty|. +%<latex>\RequirePackage{pdftexcmds} +%<plain>\input pdftexcmds.sty +%<*context> +\directlua{% + require("pdftexcmds") + tex.enableprimitives('pdf@', {'draftmode'}) +} +\long\def\pdf@mdfivesum#1{% + \directlua{% + oberdiek.pdftexcmds.mdfivesum("\luaescapestring{#1}", "byte")% + }% +}% +\def\pdf@system#1{% + \directlua{% + oberdiek.pdftexcmds.system("\luaescapestring{#1}")% + }% +} +\let\pdf@primitive\primitive +% Lua function |oberdiek.pdftexcmds.filesize| requires the |kpse| library, +% which is not loaded in \hologo{ConTeXt}, +% see \url(https://){github.com/latex3/lua-uni-algos/issues/3}, so we define our +% own filesize function. +\directlua{% + function memoize.filesize(filename) + local filehandle = io.open(filename, "r") + % We can't easily use |~=|, as |~| is an active character, so the |else| + % workaround. + if filehandle == nil then + else + tex.write(filehandle:seek("end")) + io.close(filehandle) + end + end +}% +\def\pdf@filesize#1{% + \directlua{memoize.filesize("\luaescapestring{#1}")}% +} +%</context> +% Take care of some further differences between the engines. +\ifdef\pdftexversion{% +}{% + \def\pdfhorigin{1true in}% + \def\pdfvorigin{1true in}% + \ifdef\XeTeXversion{% + \let\quitvmode\leavevmode + }{% + \ifdef\luatexversion{% + \let\pdfpagewidth\pagewidth + \let\pdfpageheight\pageheight + \def\pdfmajorversion{\pdfvariable majorversion}% + \def\pdfminorversion{\pdfvariable minorversion}% + }{% + \PackageError{memoize}{Support for this TeX engine is not implemented}{}% + }% + }% +} +% +% In \hologo{ConTeXt}, |\unexpanded| means |\protected|, and the usual +% |\unexpanded| is available as |\normalunexpanded|. Option one: use dtx +% guards to produce the correct control sequence. I tried this option. I find +% it ugly, and I keep forgetting to guard. Option two: |\let| an internal +% control sequence, like |\mmz@unexpanded|, to the correct thing, and use that +% all the time. I never tried this, but I find it ugly, too, and I guess I +% would forget to use the new control sequence, anyway. Option three: use +% |\unexpanded| in the |.dtx|, and |sed| through the generated \hologo{ConTeXt} +% files to replace all its occurrences by |\normalunexpanded|. Oh yeah! +% +% +% \paragraph.{Shipout} +% We will next load our own auxiliary package, CollArgs, but before we do +% that, we need to grab |\shipout| in \hologo{plainTeX}. The problem is, +% Memoize needs to hack into the shipout routine, but it has best chances +% of working as intended if it redefines the \emph{primitive} |\shipout|. +% However, CollArgs loads |pgfkeys|, which in turn (and perhaps with no for +% reason) loads |atbegshi|, which redefines |\shipout|. For details, see +% section~\ref{sec:code:extern}. Below, we first check that the current +% meaning of |\shipout| is primitive, and then redefine it. +% +%<*plain> +\def\mmz@regular@shipout{% + \global\advance\mmzRegularPages1\relax + \mmz@primitive@shipout +} +\edef\mmz@temp{\string\shipout}% +\edef\mmz@tempa{\meaning\shipout}% +\ifx\mmz@temp\mmz@tempa + \let\mmz@primitive@shipout\shipout + \let\shipout\mmz@regular@shipout +\else + \PackageError{memoize}{Cannot grab \string\shipout, it is already redefined}{}% +\fi +%</plain> +% Our auxiliary package (\MS\ref{sec:ref:collargs}, \S\ref{sec:code:collargs}). +% We also need it in |nomemoize|, to collect manual environments. +%<latex>\RequirePackage{advice} +%<plain>\input advice +%<context>\input t-advice +%</mmz> +% +% \paragraph.{Loading order} |memoize| and |nomemoize| are mutually exclusive, +% and |memoizable| must be loaded before either of them. |\mmz@loadstatus|: 1 +% = memoize, 2 = memoizable, 3 = nomemoize. +%<*(plain,context)&(mmz,nommz,mmzable)> +\def\ifmmz@loadstatus#1{% + \ifnum#1=0\csname mmz@loadstatus\endcsname\relax + \expandafter\@firstoftwo + \else + \expandafter\@secondoftwo + \fi +} +%</(plain,context)&(mmz,nommz,mmzable)> +%<*mmz> +%<latex>\@ifpackageloaded{nomemoize}% +%<plain,context>\ifmmz@loadstatus{3}% +{% + \PackageError{memoize}{Cannot load the package, as "nomemoize" is already + loaded. Memoization will NOT be in effect}{Packages "memoize" and + "nomemoize" are mutually exclusive, please load either one or the other.}% + %<latex>\pgfkeys{/memoize/package options/.unknown/.code={}} + %<latex>\ProcessPgfPackageOptions{/memoize/package options} + \endinput +}{}% +%<latex>\@ifpackageloaded{memoizable}% +%<plain,context>\ifmmz@loadstatus{2}% +{% + \PackageError{memoize}{Cannot load the package, as "memoizable" is already + loaded}{Package "memoizable" is loaded by packages which support + memoization. Memoize must be loaded before all such packages. The + compilation log can help you figure out which package loaded "memoizable"; + please move + %<latex>"\string\usepackage{memoize}" + %<plain>"\string\input memoize" + %<context>"\string\usemodule[memoize]" + before the + %<latex>"\string\usepackage" + %<plain>"\string\input" + %<context>"\string\usemodule" + of that package.}% + %<latex>\pgfkeys{/memoize/package options/.unknown/.code={}} + %<latex>\ProcessPgfPackageOptions{/memoize/package options} + \endinput +}{}% +%<plain,context>\ifmmz@loadstatus{1}{\endinput}{}% +%<plain,context>\def\mmz@loadstatus{1}% +%</mmz> +%<*mmzable> +%<latex>\@ifpackageloaded{memoize}% +%<plain,context>\ifmmz@loadstatus{1}% +{\endinput}{} +%<latex>\@ifpackageloaded{nomemoize}% +%<plain,context>\ifmmz@loadstatus{3}% +{\endinput}{}% +%<plain,context>\ifmmz@loadstatus{2}{\endinput}{}% +%<plain,context>\def\mmz@loadstatus{2}% +%</mmzable> +%<*nommz> +%<latex>\@ifpackageloaded{memoize} +%<plain,context>\ifmmz@loadstatus{1}% +{% + \PackageError{nomemoize}{Cannot load the package, as "memoize" is already + loaded; memoization will remain in effect}{Packages "memoize" and + "nomemoize" are mutually exclusive, please load either one or the other.}% + \endinput }{}% +%<latex>\@ifpackageloaded{memoizable}% +%<plain,context>\ifmmz@loadstatus{2}% +{% + \PackageError{nomemoize}{Cannot load the package, as "memoizable" is already + loaded}{Package "memoizable" is loaded by packages which support + memoization. (No)Memoize must be loaded before all such packages. The + compilation log can help you figure out which package loaded + "memoizable"; please move + %<latex>"\string\usepackage{nomemoize}" + %<plain>"\string\input memoize" + %<context>"\string\usemodule[memoize]" + before the + %<latex>"\string\usepackage" + %<plain>"\string\input" + %<context>"\string\usemodule" + of that package.}% + \endinput +}{}% +%<plain,context>\ifmmz@loadstatus{3}{\endinput}{}% +%<plain,context>\def\mmz@loadstatus{3}% +%</nommz> +% +%<*mmz> +% +% \begin{macro}{\filetotoks} +% Read \hologo{TeX} file |#2| into token register |#1| (under the current +% category code regime); |\toksapp| is defined in CollArgs. +\def\filetotoks#1#2{% + \immediate\openin0{#2}% + #1={}% + \loop + \unless\ifeof0 + \read0 to \totoks@temp + % We need the |\expandafter|s for our |\toksapp| macro. + \expandafter\toksapp\expandafter#1\expandafter{\totoks@temp}% + \repeat + \immediate\closein0 +} +% \end{macro} +% +% \paragraph{Other} little things. +\newif\ifmmz@temp +\newtoks\mmz@temptoks +\newbox\mmz@box +\newwrite\mmz@out +% +% \section{The basic configuration} +% \label{sec:code:configuration} +% +% \begin{macro}{\mmzset} +% The user primarily interacts with |Memoize| through the |pgfkeys|-based +% configuration macro |\mmzset|, which executes keys in path |/mmz|. In +% |nomemoize| and |memoizable|, is exists as a no-op. +\def\mmzset#1{\pgfqkeys{/mmz}{#1}\ignorespaces} +%</mmz> +%<nommz,mmzable>\def\mmzset#1{\ignorespaces} +% \end{macro} +% +% \begin{macro}{\mmzset} +% Any |/mmz| keys used outside of |\mmzset| must be declared by this macro +% for |nomemoize| package to work. +%<mmz>\def\nommzkeys#1{} +%<*nommz,mmzable> +\def\nommzkeys{\pgfqkeys{/mmz}} +\pgfqkeys{/mmz}{.unknown/.code={\pgfkeysdef{\pgfkeyscurrentkey}{}}} +%</nommz,mmzable> +% \end{macro} +% +% \begin{key}{enable, disable} +% \begin{macro}{\ifmemoize} +% These keys set \hologo{TeX}-style conditional \cs{ifmemoize}, used as the +% central on/off switch for the functionality of the package --- it is +% inspected in |\Memoize| and by run conditions of automemoization handlers. +% +% If used in the preamble, the effect of these keys is delayed until the +% beginning of the document. The delay is implemented through a special +% style, |begindocument|, which is executed at |begindocument| hook in +% \hologo{LaTeX}; in other formats, the user must invoke it manually +% (\MS\ref{sec:ref:loading}). +% +% |Nomemoize| does not need the keys themselves, but it does need the +% underlying conditional --- which will be always false. +%<mmz,nommz,mmzable>\newif\ifmemoize +%<*mmz> +\mmzset{% + enable/.style={begindocument/.append code=\memoizetrue}, + disable/.style={begindocument/.append code=\memoizefalse}, + begindocument/.append style={ + enable/.code=\memoizetrue, + disable/.code=\memoizefalse, + }, + % Memoize is enabled at the beginning of the document, unless explicitly + % disabled by the user in the preamble. + enable, +} +% \end{macro} +% \end{key} +% +% \begin{key}{normal,readonly,recompile} +% When Memoize is enabled, it can be in one of three modes +% (\MS\ref{sec:tut:working-on-a-picture}): normal, readonly, and recompile. +% The numeric constants are defined below. The mode is stored in +% |\mmz@mode|, and only matters in |\Memoize| (and +% |\mmz@process@ccmemo|).\footnote{In fact, this code treats anything but 1 +% and 2 as normal.} +\def\mmz@mode@normal{0} +\def\mmz@mode@readonly{1} +\def\mmz@mode@recompile{2} +\let\mmz@mode\mmz@mode@normal +\mmzset{% + normal/.code={\let\mmz@mode\mmz@mode@normal}, + readonly/.code={\let\mmz@mode\mmz@mode@readonly}, + recompile/.code={\let\mmz@mode\mmz@mode@recompile}, +} +% \end{key} +% +% \begin{key}{path, path/relative, path/dir, path/prefix} +% Key |path| executes the given keylist in path |/mmz/path|, to determine the +% full \emph{path prefix} to memo and extern files +% (\MS\ref{sec:tut:memodir},\ref{sec:memos}): |relative|, true by default, +% determines whether the location of these files is relative to the current +% directory; |dir| sets their directory; and |prefix| sets the first, fixed +% part of their basename; the second part containing the MD5 sum(s) is not +% under user control, and neither is the suffix. These subkeys will be +% initialized a bit later, via |no memo dir|. +\mmzset{% + path/.code={\pgfqkeys{/mmz/path}{#1}}, + path/.cd, + relative/.is if=mmz@relativepath, + dir/.store in=\mmz@dir, + dir/.value required, + prefix/.store in=\mmz@prefix, + prefix/.value required, +} +\newif\ifmmz@relativepath +% Key |path| concludes by performing two post-path-setting actions: creating +% the given directory if |mkdir| is in effect, and noting the new path prefix +% in record files (by eventually executing |record/prefix|, which typically +% puts a |\mmzPrefix| line in the |.mmz| file). These actions are the reason +% for having the path setting keys grouped under |path| --- we don't want them +% to be triggered by changes of the individual components of the path. +% Similarly, we don't want them triggered by multiple invocations of |path| in +% the preamble; only the final setting matters, so |path| is only equipped with +% the action-triggering code at the beginning of the document. +\mmzset{% + begindocument/.append style={ + path/.append code=\mmz@maybe@mkmemodir\mmz@record@prefix, + }, + % Consequently, the post-path-setting actions must be triggered manually at + % the beginning of the document. Below, we trigger directory creation; + % |record/prefix| will be called from |record/begin|, which is executed at + % the beginning of the document, so it shouldn't be mentioned here. + begindocument/.append code=\mmz@maybe@mkmemodir, +} +% Define the paths to the memo directory and the prefix. +\def\mmz@dir@path{\ifmmz@relativepath.\fi/\mmz@dir} +\def\mmz@prefix@path{\mmz@dir@path/\mmz@prefix} +% \end{key} +% +% \begin{key}{mkdir, mkdir command} +% Should we create the memo/extern directory if it doesn't exist? And which +% command should we use to create it? Of course, shell escape must be +% properly configured for this to work (\MS\ref{sec:shell-escape}). +% ^^A todo: What about quoting the paths containing spaces on Windows? Will this work? +\mmzset{ + mkdir/.is if=mmz@mkdir, + mkdir command/.code={\def\mmz@mkdir@command##1{#1}}, + mkdir command={mkdir "#1"}, +} +% The underlying conditional \cs{ifmmz@mkdir} is only ever used in +% |\mmz@maybe@mkmemodir| below, which is itself only executed at the end of +% |path| and in |begindocument|. +\newif\ifmmz@mkdir +\def\mmz@maybe@mkmemodir{% + \ifmmz@mkdir + \pdf@system{\mmz@mkdir@command{\mmzOutputDirectory\mmz@dir@path}}% + \fi +} +% \end{key} +% +% \begin{key}{memo dir, no memo dir} +% Shortcuts for two common settings of |path| keys. The default |no memo +% dir| will place the memos and externs in the current directory, prefixed +% with |#1.|, where |#1| defaults to (unquoted) |\jobname|. Key |memo dir| +% places the memos and externs in a dedicated directory, |#1.memo.dir|; the +% filenames themselves have no prefix. Furthermore, |memo dir| triggers the +% creation of the directory. +\mmzset{% + memo dir/.style={ + mkdir, + path={ + relative, + dir={#1.memo.dir}, + prefix={}, + }, + }, + memo dir/.default=\mmzUnquote\jobname, + no memo dir/.style={ + mkdir=false, + path={ + relative, + dir={}, + prefix={#1.}, + }, + }, + no memo dir/.default=\mmzUnquote\jobname, + no memo dir, +} +% \end{key} +% +% \begin{macro}{\mmzUnquote} +% If the expanded argument is surrounded by double quotes, remove them. This +% relies on |#1| containing no quotes other than the potential surrounding +% quotes, which should be the case when applying the macro to |\jobname|. An +% empty |#1| is dealt with correctly, even if |\jobname| can hardly ever be +% empty (needs |openout_any=a|). +% +% We use this macro when we are passing a filename constructed from +% |\jobname| to external programs. +\def\mmzUnquote#1{\expanded{\noexpand\mmz@unquote#1}\mmz@unquote@end} +\def\mmz@unquote#1{% + \ifx\mmz@unquote@end#1% + \else + \ifx"#1% + \expandafter\expandafter\expandafter\mmz@unquote@quotes + \else + \expandafter\expandafter\expandafter\mmz@unquote@noquotes + \expandafter\expandafter\expandafter#1% + \fi + \fi +} +\def\mmz@unquote@quotes#1"\mmz@unquote@end{#1} +\def\mmz@unquote@noquotes#1\mmz@unquote@end{#1} +% \end{macro} +% +% \begin{key}{ignore spaces} +% The underlying conditional will be inspected by automemoization handlers, +% to maybe put |\ignorespaces| after the invocation of the handler. +\newif\ifmmz@ignorespaces +\mmzset{ + ignore spaces/.is if=mmz@ignorespaces, +} +% \end{key} +% +% \begin{key}{verbatim, verb, no verbatim} +% These keys are tricky. For one, there's |verbatim|, which sets all +% characters' category codes to other, and there's |verb|, which leaves +% braces untouched (well, honestly, it redefines them). But Memoize itself +% doesn't really care about this detail --- it only uses the underlying +% conditional \cs{ifmmz@verbatim}. It is CollArgs which cares about the +% difference between the ``long'' and the ``short'' verbatim, so we need to +% tell it about it. That's why the verbatim options ``append themselves'' to +% |\mmzRawCollectorOptions|, which is later passed on to +% |\CollectArgumentsRaw| as a part of its optional argument. +\newif\ifmmz@verbatim +\def\mmzRawCollectorOptions{} +\mmzset{ + verbatim/.code={% + \def\mmzRawCollectorOptions{\collargsVerbatim}% + \mmz@verbatimtrue + }, + verb/.code={% + \def\mmzRawCollectorOptions{\collargsVerb}% + \mmz@verbatimtrue + }, + no verbatim/.code={% + \def\mmzRawCollectorOptions{\collargsNoVerbatim}% + \mmz@verbatimfalse + }, +} +% \end{key} +% \section{Memoization} +% \label{sec:code:memoization} +% +% \subsection{Manual memoization} +% \label{sec:code:memoization:manual} +% +% \begin{macro}{\mmz} +% The core of this macro will be a simple invocation of |\Memoize|, but to +% get there, we have to collect the optional argument carefully, because we +% might have to collect the memoized code verbatim. +\protected\def\mmz{\futurelet\mmz@temp\mmz@i} +\def\mmz@i{% + % Anyone who wants to call |\Memoize| must open a group, because |\Memoize| + % will close a group. + \begingroup + % As the optional argument occurs after a control sequence (|\mmz|), any + % spaces were consumed and we can immediately test for the opening bracket. + \ifx\mmz@temp[%] + \def\mmz@verbatim@fix{}% + \expandafter\mmz@ii + \else + % If there was no optional argument, the opening brace (or the unlikely + % single token) of our mandatory argument is already tokenized. If we are + % requested to memoize in a verbatim mode, this non-verbatim tokenization + % was wrong, so we will use option |\collargsFixFromNoVerbatim| to ask + % CollArgs to fix the situation. (|\mmz@verbatim@fix| will only be used in + % the verbatim mode.) + \def\mmz@verbatim@fix{\noexpand\collargsFixFromNoVerbatim}% + % No optional argument, so we can skip |\mmz@ii|. + \expandafter\mmz@iii + \fi +} +\def\mmz@ii[#1]{% + % Apply the options given in the optional argument. + \mmzset{#1}% + \mmz@iii +} +\def\mmz@iii{% + % In the non-verbatim mode, we avoid collecting the single mandatory argument + % using |\CollectArguments|. + \ifmmz@verbatim + \expandafter\mmz@do@verbatim + \else + \expandafter\mmz@do + \fi +} +% This macro grabs the mandatory argument of |\mmz| and calls |\Memoize|. +\long\def\mmz@do#1{% + \Memoize{#1}{#1}% +}% +% +% The following macro uses |\CollectArgumentsRaw| of package CollArgs +% (\S\ref{sec:code:collargs}) to grab the argument verbatim; the appropriate +% verbatim mode triggering raw option was put in |\mmzRawCollectorOptions| by +% key |verb(atim)|. The macro also |\mmz@verbatim@fix| contains the potential +% request for a category code fix (\S\ref{sec:code:collargs:fix}). +\def\mmz@do@verbatim#1{% + \expanded{% + \noexpand\CollectArgumentsRaw{% + \noexpand\collargsCaller{\noexpand\mmz}% + \expandonce\mmzRawCollectorOptions + \mmz@verbatim@fix + }% + }{+m}\mmz@do +} +% \end{macro} +% +% \begin{environment}{memoize} +% The definition of the manual memoization environment proceeds along the +% same lines as the definition of |\mmz|, except that we also have to +% implement space-trimming, and that we will collect the environment using +% |\CollectArguments| in both the verbatim and the non-verbatim and mode. +% +% We define the \hologo{LaTeX}, \hologo{plainTeX} and \hologo{ConTeXt} +% environments in parallel. The definition of the \hologo{plainTeX} and +% \hologo{ConTeXt} version is complicated by the fact that space-trimming is +% affected by the presence vs.\ absence of the optional argument (for +% purposes of space-trimming, it counts as present even if it is empty). +%<*latex> +% We define the \hologo{LaTeX} environment using |\newenvironment|, which +% kindly grabs any spaces in front of the optional argument, if it exists --- +% and if doesn't, we want to trim spaces at the beginning of the environment +% body anyway. +\newenvironment{memoize}[1][\mmz@noarg]{% + % We close the environment right away. We'll collect the environment body, + % complete with the end-tag, so we have to reintroduce the end-tag somewhere. + % Another place would be after the invocation of |\Memoize|, but that would + % put memoization into a double group and |\mmzAfterMemoization| would not work. + \end{memoize}% + % % We open the group which will be closed by |\Memoize|. + \begingroup + % As with |\mmz| above, if there was no optional argument, we have to ask + % Collargs for a fix. The difference is that, as we have collected the + % optional argument via |\newcommand|, we have to test for its presence in a + % roundabout way. + \def\mmz@temp{#1}% + \ifx\mmz@temp\mmz@noarg + \def\mmz@verbatim@fix{\noexpand\collargsFixFromNoVerbatim}% + \else + \def\mmz@verbatim@fix{}% + \mmzset{#1}% + \fi + \mmz@env@iii +}{} +\def\mmz@noarg{\mmz@noarg} +%</latex> +%<plain>\def\memoize{% +%<context>\def\startmemoize{% +%<*plain,context> + \begingroup + % In \hologo{plainTeX} and \hologo{ConTeXt}, we don't have to worry about any + % spaces in front of the optional argument, as the environments are opened by + % a control sequence. + \futurelet\mmz@temp\mmz@env@i +} +\def\mmz@env@i{% + \ifx\mmz@temp[%] + \def\mmz@verbatim@fix{}% + \expandafter\mmz@env@ii + \else + \def\mmz@verbatim@fix{\noexpand\collargsFixFromNoVerbatim}% + \expandafter\mmz@env@iii + \fi +} +\def\mmz@env@ii[#1]{% + \mmzset{#1}% + \mmz@env@iii +} +%</plain,context> +\def\mmz@env@iii{% + \long\edef\mmz@do##1{% + % |\unskip| will ``trim'' spaces at the end of the environment body. + \noexpand\Memoize{##1}{##1\unskip}% + }% + \expanded{% + \noexpand\CollectArgumentsRaw{% + % |\CollectArgumentsRaw| will adapt the caller to the format automatically. + \noexpand\collargsCaller{memoize}% + % |verb(atim)| is in here if it was requested. + \expandonce\mmzRawCollectorOptions + % The category code fix, if needed. + \ifmmz@verbatim\mmz@verbatim@fix\fi + }% + % Spaces at the beginning of the environment body are trimmed by setting + % the first argument to |!t<space>| and disappearing it with + % |\collargsAppendPostwrap{}|; note that this removes any number of space + % tokens. |\CollectArgumentsRaw| automatically adapts the argument type + % |b| to the format. + % + }{&&{\collargsAppendPostwrap{}}!t{ }+b{memoize}}{\mmz@do}% +}% +%</mmz> +% \end{environment} +% +% \begin{macro}{\nommz} +% We throw away the optional argument if present, and replace the opening +% brace with begin-group plus |\memoizefalse|. This way, the ``argument'' of +% |\nommz| will be processed in a group (with Memoize disabled) and even the +% verbatim code will work because the ``argument'' will not have been +% tokenized. +% +% As a user command, |\nommz| has to make it into package |nomemoize| as +% well, and we'll |\let| |\mmz| equal it there; it is not needed in +% |mmzable|. +%<*mmz,nommz> +\protected\def\nommz#1#{% + \afterassignment\nommz@i + \let\mmz@temp +} +\def\nommz@i{% + \bgroup + \memoizefalse +} +%<nommz>\let\mmz\nommz +% \end{macro} +% +% \begin{environment}{nomemoize} +% We throw away the optional argument and take care of the spaces at the +% beginning and at the end of the body. +%<*latex> +\newenvironment{nomemoize}[1][]{% + \memoizefalse + \ignorespaces +}{% + \unskip +} +%</latex> +%<*plain,context> +%<plain>\def\nomemoize{% +%<context>\def\startnomemoize{% +% Start a group to delimit |\memoizefalse|. + \begingroup + \memoizefalse + \futurelet\mmz@temp\nommz@env@i +} +\def\nommz@env@i{% + \ifx\mmz@temp[%] + \expandafter\nommz@env@ii + % No optional argument, no problems with spaces. + \fi +} +\def\nommz@env@ii[#1]{% + \ignorespaces +} +%<plain>\def\endnomemoize{% +%<context>\def\stopnomemoize{% + \endgroup + \unskip +} +%</plain,context> +%<*nommz> +%<plain,latex>\let\memoize\nomemoize +%<plain,latex>\let\endmemoize\endnomemoize +%<context>\let\startmemoize\startnomemoize +%<context>\let\stopmemoize\stopnomemoize +%</nommz> +% \end{environment} +% +% \subsection{The memoization process} +% \label{sec:code:memoization-process} +% +% \begin{macro}{\ifmemoizing} +% This conditional is set to true when we start memoization (but not when +% we start regular compilation or utilization); it should never be set +% anywhere else. It is checked by |\Memoize| to prevent nested +% memoizations, deployed in advice run conditions set by |run only if +% memoizing|, etc. +\newif\ifmemoizing +% \end{macro} +% +% \begin{macro}{\ifinmemoize} +% This conditional is set to true when we start either memoization or +% regular compilation (but not when we start utilization); it should never +% be set anywhere else. It is deployed in the default advice run conditions, +% making sure that automemoized commands are not handled even when we're +% regularly compiling some code submitted to memoization. +\newif\ifinmemoize +% \end{macro} +% +% \begin{macro}{\mmz@maybe@scantokens} +% An auxiliary macro which rescans the given +% code using |\scantokens| if the verbatim mode is active. We also need it +% in NoMemoize, to properly grab verbatim manually memoized code. +%</mmz,nommz> +%<*mmz> +\def\mmz@maybe@scantokens{% + \ifmmz@verbatim + \expandafter\mmz@scantokens + \else + \expandafter\@firstofone + \fi +} +% Without |\newlinechar=13|, |\scantokens| would see receive the entire +% argument as one long line --- but it would not \emph{see} the entire +% argument, but only up to the first newline character, effectively losing most +% of the tokens. (We need to manually save and restore |\newlinechar| because +% we don't want to execute the memoized code in yet another group.) +\long\def\mmz@scantokens#1{% + \expanded{% + \newlinechar=13 + \unexpanded{\scantokens{#1\endinput}}% + \newlinechar=\the\newlinechar + }% +} +% \end{macro} +% +% \begin{macro}{\Memoize} +% Memoization is invoked by executing |\Memoize|. This macro is a decision +% hub. It test for the existence of the memos and externs associated with +% the memoized code, and takes the appropriate action (memoization: +% |\mmz@memoize|; regular compilation: |\mmz@compile|, utilization: +% |\mmz@process@cmemo| plus |\mmz@process@ccmemo| plus further complications) +% depending on the memoization mode (normal, readonly, recompile). Note that +% one should open a \hologo{TeX} group prior to executing |\Memoize|, because +% |\Memoize| will close a group (\MS\ref{sec:Memoize}). +% +% |\Memoize| takes two arguments, which contain two potentially different +% versions of the code submitted to memoization: |#1| contains the code which +% \meta{code MD5 sum} is computed off of, while |#2| contains the code which +% is actually executed during memoization and regular compilation. The +% arguments will contain the same code in the case of manual memoization, but +% they will differ in the case of automemoization, where the executable code +% will typically prefixed by |\AdviceOriginal|. As the two +% codes will be used not only by |\Memoize| but also by macros called from +% |\Memoize|, |\Memoize| stores them into dedicated toks registers, declared +% below. +\newtoks\mmz@mdfive@source +\newtoks\mmz@exec@source +% Finally, the definition of the macro. In package NoMemoize, we should simply +% execute the code in the second argument. But in Memoize, we have work to do. +\let\Memoize\@secondoftwo +\long\def\Memoize#1#2{% + % We store the first argument into token register |\mmz@mdfive@source| + % because we might have to include it in tracing info (when |trace| is in + % effect), or paste it into the c-memo (depending on |include source in + % cmemo|). + \mmz@mdfive@source{#1}% + % We store the executable code in |\mmz@exec@source|. In the verbatim mode, + % the code will have to be rescanned. This is implemented by + % |\mmz@maybe@scantokens|, and we wrap the code into this macro right away, + % once and for all. Even more, we pre-expand |\mmz@maybe@scantokens| (three + % times), effectively applying the current \cs{ifmmz@verbatim} and + % eliminating the need to save and restore this conditional in + % |\mmz@compile|, which (regularly) compiles the code \emph{after} closing + % the |\Memoize| group --- after this pre-expansion, |\mmz@exec@source| will + % contain either |\mmz@scantokens{...}| or |\@firstofone{...}|. + \expandafter\expandafter\expandafter\expandafter + \expandafter\expandafter\expandafter + \mmz@exec@source + \expandafter\expandafter\expandafter\expandafter + \expandafter\expandafter\expandafter + {% + \mmz@maybe@scantokens{#2}% + }% + \mmz@trace@Memoize + % In most branches below, we end up with regular compilation, so let this be + % the default action. + \let\mmz@action\mmz@compile + % If Memoize is disabled, or if memoization is currently taking place, we + % will perform a regular compilation. + \ifmemoizing + \else + \ifmemoize + % Compute \meta{code md5sum} off of the first argument, and globally + % store it into |\mmz@code@mdfivesum| --- globally, because we need it in + % utilization to include externs, but the |\Memoize| group is closed (by + % |\mmzMemo|) while inputting the cc-memo. + \xdef\mmz@code@mdfivesum{\pdf@mdfivesum{\the\mmz@mdfive@source}}% + \mmz@trace@code@mdfive + % Recompile mode forces memoization. + \ifnum\mmz@mode=\mmz@mode@recompile\relax + \ifnum\pdf@draftmode=0 + \let\mmz@action\mmz@memoize + \fi + \else + % In the normal and the readonly mode, we try to utilize the memos. The + % c-memo comes first. If the c-memo does not exist (or if something is + % wrong with it), |\mmz@process@cmemo| (defined in + % \S\ref{sec:code:c-memo}) will set |\ifmmz@abort| to true. It might + % also set |\ifmmzUnmemoizable| which means we should compile normally + % regardless of the mode. + \mmz@process@cmemo + \ifmmzUnmemoizable + \mmz@trace@cmemo@unmemoizable + \else + \ifmmz@abort + % If there is no c-memo, or it is invalid, we memoize, unless the + % read-only mode is in effect. + \mmz@trace@process@cmemo@fail + \ifnum\mmz@mode=\mmz@mode@readonly\relax + \else + \ifnum\pdf@draftmode=0 + \let\mmz@action\mmz@memoize + \fi + \fi + \else + \mmz@trace@process@cmemo@ok + % If the c-memo was fine, the formal action decided upon is to try + % utilizing the cc-memo. If it exists and everything is fine with + % it, |\mmz@process@ccmemo| (defined in + % section~\ref{sec:code:cc-memo}) will utilize it, i.e.\ the core + % of the cc-memo (the part following |\mmzMemo|) will be executed + % (typically including the single extern). Otherwise, + % |\mmz@process@ccmemo| will trigger either memoization (in the + % normal mode) or regular compilation (in the readonly mode). This + % final decision is left to |\mmz@process@ccmemo| because if we + % made it here, the code would get complicated, as the cc-memo must + % be processed outside the |\Memoize| group and all the + % conditionals in this macro. + \let\mmz@action\mmz@process@ccmemo + \fi + \fi + \fi + \fi + \fi + \mmz@action +} +% \end{macro} +% +% \begin{macro}{\mmz@compile} +% This macro performs regular compilation --- this is signalled to the +% memoized code and the memoization driver by setting \cs{ifinmemoize} to +% true for the duration of the compilation; \cs{ifmemoizing} is not touched. +% The group opened prior to the invocation of |\Memoize| is closed before +% executing the code in |\mmz@exec@source|, so that compiling the code has +% the same local effect as if was not submitted to memoization; it is closing +% this group early which complicates the restoration of \cs{ifinmemoize} at +% the end of compilation. Note that |\mmz@exec@source| is already set to +% properly deal with the current verbatim mode, so any further inspection of +% \cs{ifmmz@verbatim} is unnecessary; the same goes for +% \cs{ifmmz@ignorespaces}, which was (or at least should be) taken care of by +% whoever called |\Memoize|. +\def\mmz@compile{% + \mmz@trace@compile + \expanded{% + \endgroup + \noexpand\inmemoizetrue + \the\mmz@exec@source + \ifinmemoize\noexpand\inmemoizetrue\else\noexpand\inmemoizefalse\fi + }% +} +% \end{macro} +% \begin{luafun}{abortOnError} +% \begin{macro}{\mmz@lua@atbeginmemoization,\mmz@lua@atendmemoization} +% In \hologo{LuaTeX}, we can whether an error occurred during memoization, +% and abort if it did. (We're going through |memoize.abort|, because +% |tex.print| does not seem to work during error handling.) We omit all +% this in \hologo{ConTeXt}, as it appears to stop on any error? +%<*!context> +\ifdefined\luatexversion + \directlua{% + luatexbase.add_to_callback( + "show_error_message", + function() + memoize.abort = true + texio.write_nl(status.lasterrorstring) + end, + "Abort memoization on error" + ) + }% + \def\mmz@lua@atbeginmemoization{% + \directlua{memoize.abort = false}% + }% + \def\mmz@lua@atendmemoization{% + \directlua{% + if memoize.abort then + tex.print("\noexpand\\mmzAbort") + end + }% + }% +\else +%</!context> + \let\mmz@lua@atbeginmemoization\relax + \let\mmz@lua@atendmemoization\relax +%<!context>\fi +% \end{macro} +% \end{luafun} +% \begin{macro}{\mmz@memoize} +% This macro performs memoization --- this is signalled to the memoized code +% and the memoization driver by setting both \cs{ifinmemoize} and +% \cs{ifinmemoizing} to true. +\def\mmz@memoize{% + \mmz@trace@memoize + \memoizingtrue + \inmemoizetrue + % Initialize the various macros and registers used in memoization (to be + % described below, or later). Note that most of these are global, as they + % might be adjusted arbitrarily deep within the memoized code. + \edef\memoizinggrouplevel{\the\currentgrouplevel}% + \global\mmz@abortfalse + \global\mmzUnmemoizablefalse + \global\mmz@seq 0 + \global\setbox\mmz@tbe@box\vbox{}% + \global\mmz@ccmemo@resources{}% + \global\mmzCMemo{}% + \global\mmzCCMemo{}% + \global\mmzContextExtra{}% + \gdef\mmzAtEndMemoizationExtra{}% + \gdef\mmzAfterMemoizationExtra{}% + \mmz@lua@atbeginmemoization + % Execute the pre-memoization hook, the memoized code (wrapped in the + % driver), and the post-memoization hook. + \mmzAtBeginMemoization + \mmzDriver{\the\mmz@exec@source}% + \mmzAtEndMemoization + \mmzAtEndMemoizationExtra + \mmz@lua@atendmemoization + \ifmmzUnmemoizable + % To permanently prevent memoization, we have to write down the c-memo + % (containing |\mmzUnmemoizabletrue|). We don't need the extra context in + % this case. + \global\mmzContextExtra{}% + \gtoksapp\mmzCMemo{\global\mmzUnmemoizabletrue}% + \mmz@write@cmemo + \mmz@trace@endmemoize@unmemoizable + \PackageWarning{memoize}{Marking this code as unmemoizable}% + \else + \ifmmz@abort + % If memoization was aborted, we create an empty c-memo, to make sure that + % no leftover c-memo tricks Memoize into thinking that the code was + % successfully memoized. + \mmz@trace@endmemoize@aborted + \PackageWarning{memoize}{Memoization was aborted}% + \mmz@write@empty@cmemo + \else + % If memoization was not aborted, we compute the \meta{context md5sum}, + % open and write out the memos, and shipout the externs (as pages into the + % document). + \mmz@compute@context@mdfivesum + \mmz@write@cmemo + \mmz@write@ccmemo + \mmz@shipout@externs + \mmz@trace@endmemoize@ok + \fi + \fi + % After closing the group, we execute the final, after-memoization hook (we + % pre-expand the regular macro; the extra macro was assigned to globally). In + % the after-memoization code, |\mmzIncludeExtern| points to a macro which can + % include the extern from |\mmz@tbe@box|, which makes it possible to typeset + % the extern by dropping the contents of |\mmzCCMemo| into this hook --- but + % note that this will only work if |\ifmmzkeepexterns| was in effect at the + % end of memoization. + \expandafter\endgroup + \expandafter\let + \expandafter\mmzIncludeExtern\expandafter\mmz@include@extern@from@tbe@box + \mmzAfterMemoization + \mmzAfterMemoizationExtra +} +% \end{macro} +% +% \begin{macro}{\memoizinggrouplevel} +% This macro stores the group level at the beginning of memoization. It is +% deployed by |\IfMemoizing|, normally used by integrated drivers. +\def\memoizinggrouplevel{-1}% +% \end{macro} +% +% \begin{macro}{\mmzAbort} +% Memoized code may execute this macro to abort memoization. +\def\mmzAbort{\global\mmz@aborttrue} +% \end{macro} +% +% \begin{macro}{\ifmmz@abort} +% This conditional serves as a signal that something went wrong during +% memoization (where it is set to true by |\mmzAbort|), or c(c)-memo +% processing. The assignment to this conditional should always be global +% (because it may be set during memoization). +\newif\ifmmz@abort +% \end{macro} +% +% \begin{macro}{\mmzUnmemoizable} +% Memoized code may execute |\mmzUnmemoizable| to abort memoization and mark +% (in the c-memo) that memoization should never be attempted again. The +% c-memo is composed by |\mmz@memoize|. +\def\mmzUnmemoizable{\global\mmzUnmemoizabletrue} +% \end{macro} +% +% \begin{macro}{\ifmmzUnmemoizable} +% This conditional serves as a signal that the code should never be +% memoized. It can be set (a) during memoization (that's why it should be +% assigned globally), after which it is inspected by |\mmz@memoize|, and +% (b) from the c-memo, in which case it is inspected by |\Memoize|. +\newif\ifmmzUnmemoizable +% \end{macro} +% +% \begin{macro}{\mmzAtBeginMemoization,\mmzAtEndMemoization,\mmzAfterMemoization} +% \begin{key}{at begin memoization, at end memoization, after memoization} +% The memoization hooks and their keys. The hook macros may be set either +% before or during memoization. In the former case, one should modify the +% primary macro (|\mmzAtBeginMemoization|, |\mmzAtEndMemoization|, +% |\mmzAfterMemoization|), and the assignment should be local. In the +% latter case, one should modify the extra macro +% (|\mmzAtEndMemoizationExtra|, |\mmzAfterMemoizationExtra|; there is no +% |\mmzAtBeginMemoizationExtra|), and the assignment should be global. The +% keys automatically adapt to the situation, by appending either to the +% primary or the the extra macro; if |at begin memoization| is used during +% memoization, the given code is executed immediately. We will use this +% ``extra'' approach and the auto-adapting keys for other options, like +% |context|, as well. +\def\mmzAtBeginMemoization{} +\def\mmzAtEndMemoization{} +\def\mmzAfterMemoization{} +\mmzset{ + at begin memoization/.code={% + \ifmemoizing + \expandafter\@firstofone + \else + \expandafter\appto\expandafter\mmzAtBeginMemoization + \fi + {#1}% + }, + at end memoization/.code={% + \ifmemoizing + \expandafter\gappto\expandafter\mmzAtEndMemoizationExtra + \else + \expandafter\appto\expandafter\mmzAtEndMemoization + \fi + {#1}% + }, + after memoization/.code={% + \ifmemoizing + \expandafter\gappto\expandafter\mmzAfterMemoizationExtra + \else + \expandafter\appto\expandafter\mmzAfterMemoization + \fi + {#1}% + }, +} +% \end{key} +% \end{macro} +% +% \begin{key}{driver} +% This key sets the (formal) memoization driver. The function of the driver +% is to produce the memos and externs while executing the submitted code. +\mmzset{ + driver/.store in=\mmzDriver, + driver=\mmzSingleExternDriver, +} +% \end{key} +% +% \begin{macro}{\ifmmzkeepexterns} +% This conditional causes Memoize not to empty out |\mmz@tbe@box|, holding +% the externs collected during memoization, while shipping them out. +\newif\ifmmzkeepexterns +% \end{macro} +% +% \begin{macro}{\mmzSingleExternDriver} +% The default memoization driver externalizes the submitted code. It always +% produces exactly one extern, and including the extern will be the only +% effect of inputting the cc-memo (unless the memoized code contained some +% commands, like |\label|, which added extra instructions to the cc-memo.) +% The macro (i) adds |\quitvmode| to the cc-memo, if we're capturing into a +% horizontal box, and it puts it to the very front, so that it comes before +% any |\label| and |\index| replications, guaranteeing (hopefully) that they +% refer to the correct page; (ii) takes the code and typesets it in a box +% (|\mmz@box|); (iii) submits the box for externalization; (iv) adds the +% extern-inclusion code to the cc-memo, and (v) puts the box into the +% document (again prefixing it with |\quitvmode| if necessary). (The listing +% region markers help us present this code in the manual.) +% \begin{listingregion}{single-extern-driver.tex} +\long\def\mmzSingleExternDriver#1{% + \xtoksapp\mmzCCMemo{\mmz@maybe@quitvmode}% + \setbox\mmz@box\mmz@capture{#1}% + \mmzExternalizeBox\mmz@box\mmz@temptoks + \xtoksapp\mmzCCMemo{\the\mmz@temptoks}% + \mmz@maybe@quitvmode\box\mmz@box +} +% \end{listingregion} +% \end{macro} +% +% \begin{key}{capture} +% The default memoization driver uses |\mmz@capture| and +% |\mmz@maybe@quitvmode|, which are set by this key. |\mmz@maybe@quitvmode| +% will be expanded, but for \hologo{XeTeX}, we have defined |\quitvmode| as a +% synonym for |\leavevmode|, which is a macro rather than a primitive, so we +% have to prevent its expansion in that case. It is easiest to just add +% |\noexpand|, regardless of the engine used. +\mmzset{ + capture/.is choice, + capture/hbox/.code={% + \let\mmz@capture\hbox + \def\mmz@maybe@quitvmode{\noexpand\quitvmode}% + }, + capture/vbox/.code={% + \let\mmz@capture\vbox + \def\mmz@maybe@quitvmode{}% + }, + capture=hbox, +} +% \end{key} +% The memoized code may be memoization-aware; in such a case, we say that the +% driver is \emph{integrated} into the code. Code containing an integrated +% driver must take care to execute it only when memoizing, and not during a +% regular compilation. The following key and macro can help here, see +% \MS\ref{sec:memoization-complex-single-driver} for details. +% +% \begin{mmzautokey}{integrated driver} +% This is an advice key, residing in |/mmz/auto|. Given \meta{suffix} as the +% only argument, it declares conditional \cs{ifmemoizing}\meta{suffix}, and +% sets the driver for the automemoized command to a macro which sets this +% conditional to true. The declared conditional is \emph{internal} and +% should not be used directly, but only via |\IfMemoizing| --- because it +% will not be declared when package NoMemoize or only Memoizable is loaded. +\mmzset{ + auto/integrated driver/.style={ + after setup={\expandafter\newif\csname ifmmz@memoizing#1\endcsname}, + driver/.expand once={% + \csname mmz@memoizing#1true\endcsname + % Without this, we would introduce an extra group around the memoized + % code. + \@firstofone + }% + }, +} +% \end{mmzautokey} +% +% \begin{macro}{\IfMemoizing} +% Without the optional argument, the condition is satisfied when the internal +% conditional \cs{ifmemoizing}\meta{suffix}, declared by |integrated driver|, +% is true. With the optional argument \meta{offset}, the current group level +% must additionally match the memoizing group level, modulo \meta{offset} --- +% this makes sure that the conditional comes out as false in a regular +% compilation embedded in a memoization. +\newcommand\IfMemoizing[2][\mmz@Ifmemoizing@nogrouplevel]{%>\fi +\csname ifmmz@memoizing#2\endcsname%>\if + % One |\relax| is for the |\numexpr|, another for |\ifnum|. Complications + % arise when |#1| is the optional argument default (defined below). In + % that case, the content of |\mmz@Ifmemoizing@nogrouplevel| closes off the + % |\ifnum| conditional (with both the true and the false branch empty), and + % opens up a new one, |\iftrue|. Effectively, we're not testing for the + % group level match. + \ifnum\currentgrouplevel=\the\numexpr\memoizinggrouplevel+#1\relax\relax + \expandafter\expandafter\expandafter\@firstoftwo + \else + \expandafter\expandafter\expandafter\@secondoftwo + \fi +\else + \expandafter\@secondoftwo +\fi +} +\def\mmz@Ifmemoizing@nogrouplevel{0\relax\relax\fi\iftrue} +% \end{macro} +% +% \paragraph.{Tracing} We populate the hooks which send the tracing info to the +% terminal. +\def\mmz@trace#1{\immediate\write16{[tracing memoize] #1}} +\def\mmz@trace@context{\mmz@trace{\space\space + Context: "\expandonce{\mmz@context@key}" --> \mmz@context@mdfivesum}} +\def\mmz@trace@Memoize@on{% + \mmz@trace{% + Entering \noexpand\Memoize (% + \ifmemoize enabled\else disabled\fi, + \ifnum\mmz@mode=\mmz@mode@recompile recompile\fi + \ifnum\mmz@mode=\mmz@mode@readonly readonly\fi + \ifnum\mmz@mode=\mmz@mode@normal normal\fi + \space mode) on line \the\inputlineno + }% + \mmz@trace{\space\space Code: \the\mmz@mdfive@source}% +} +\def\mmz@trace@code@mdfive@on{\mmz@trace{\space\space + Code md5sum: \mmz@code@mdfivesum}} +\def\mmz@trace@compile@on{\mmz@trace{\space\space Compiling}} +\def\mmz@trace@memoize@on{\mmz@trace{\space\space Memoizing}} +\def\mmz@trace@endmemoize@ok@on{\mmz@trace{\space\space + Memoization completed}}% +\def\mmz@trace@endmemoize@aborted@on{\mmz@trace{\space\space + Memoization was aborted}} +\def\mmz@trace@endmemoize@unmemoizable@on{\mmz@trace{\space\space + Marking this code as unmemoizable}} +% No need for |\mmz@trace@endmemoize@fail|, as abortion results in a package +% warning anyway. +\def\mmz@trace@process@cmemo@on{\mmz@trace{\space\space + Attempting to utilize c-memo \mmz@cmemo@path}} +\def\mmz@trace@process@no@cmemo@on{\mmz@trace{\space\space + C-memo does not exist}} +\def\mmz@trace@process@cmemo@ok@on{\mmz@trace{\space\space + C-memo was processed successfully}\mmz@trace@context} +\def\mmz@trace@process@cmemo@fail@on{\mmz@trace{\space\space + C-memo input failed}} +\def\mmz@trace@cmemo@unmemoizable@on{\mmz@trace{\space\space + This code was marked as unmemoizable}} +\def\mmz@trace@process@ccmemo@on{\mmz@trace{\space\space + Attempting to utilize cc-memo \mmz@ccmemo@path\space + (\ifmmz@direct@ccmemo@input\else in\fi direct input)}} +\def\mmz@trace@resource@on#1{\mmz@trace{\space\space + Extern file does not exist: #1}} +\def\mmz@trace@process@ccmemo@ok@on{% + \mmz@trace{\space\space Utilization successful}} +\def\mmz@trace@process@no@ccmemo@on{% + \mmz@trace{\space\space CC-memo does not exist}} +\def\mmz@trace@process@ccmemo@fail@on{% + \mmz@trace{\space\space Cc-memo input failed}} +% +% \begin{key}{tracing} +% \begin{macro}{\mmzTracingOn,\mmzTracingOff} +% The user interface for switching the tracing on and off; initially, it is +% off. Note that there is no underlying conditional. The off version +% simply |\let|s all the tracing hooks to |\relax|, so that the overhead of +% having the tracing functionality available is negligible. +\mmzset{% + trace/.is choice, + trace/.default=true, + trace/true/.code=\mmzTracingOn, + trace/false/.code=\mmzTracingOff, +} +\def\mmzTracingOn{% + \let\mmz@trace@Memoize\mmz@trace@Memoize@on + \let\mmz@trace@code@mdfive\mmz@trace@code@mdfive@on + \let\mmz@trace@compile\mmz@trace@compile@on + \let\mmz@trace@memoize\mmz@trace@memoize@on + \let\mmz@trace@process@cmemo\mmz@trace@process@cmemo@on + \let\mmz@trace@endmemoize@ok\mmz@trace@endmemoize@ok@on + \let\mmz@trace@endmemoize@unmemoizable\mmz@trace@endmemoize@unmemoizable@on + \let\mmz@trace@endmemoize@aborted\mmz@trace@endmemoize@aborted@on + \let\mmz@trace@process@cmemo\mmz@trace@process@cmemo@on + \let\mmz@trace@process@cmemo@ok\mmz@trace@process@cmemo@ok@on + \let\mmz@trace@process@no@cmemo\mmz@trace@process@no@cmemo@on + \let\mmz@trace@process@cmemo@fail\mmz@trace@process@cmemo@fail@on + \let\mmz@trace@cmemo@unmemoizable\mmz@trace@cmemo@unmemoizable@on + \let\mmz@trace@process@ccmemo\mmz@trace@process@ccmemo@on + \let\mmz@trace@resource\mmz@trace@resource@on + \let\mmz@trace@process@ccmemo@ok\mmz@trace@process@ccmemo@ok@on + \let\mmz@trace@process@no@ccmemo\mmz@trace@process@no@ccmemo@on + \let\mmz@trace@process@ccmemo@fail\mmz@trace@process@ccmemo@fail@on +} +\def\mmzTracingOff{% + \let\mmz@trace@Memoize\relax + \let\mmz@trace@code@mdfive\relax + \let\mmz@trace@compile\relax + \let\mmz@trace@memoize\relax + \let\mmz@trace@process@cmemo\relax + \let\mmz@trace@endmemoize@ok\relax + \let\mmz@trace@endmemoize@unmemoizable\relax + \let\mmz@trace@endmemoize@aborted\relax + \let\mmz@trace@process@cmemo\relax + \let\mmz@trace@process@cmemo@ok\relax + \let\mmz@trace@process@no@cmemo\relax + \let\mmz@trace@process@cmemo@fail\relax + \let\mmz@trace@cmemo@unmemoizable\relax + \let\mmz@trace@process@ccmemo\relax + \let\mmz@trace@resource\@gobble + \let\mmz@trace@process@ccmemo@ok\relax + \let\mmz@trace@process@no@ccmemo\relax + \let\mmz@trace@process@ccmemo@fail\relax +} +\mmzTracingOff +% \end{macro} +% \end{key} +% +%</mmz> +%<nommz,mmzable>\newcommand\IfMemoizing[2][]{\@secondoftwo} +%<*mmz> +% +% \subsection{Context} +% \label{sec:code:context} +% +% \begin{macro}{\mmzContext,\mmzContextExtra} +% The context expression is stored in two token registers. Outside +% memoization, we will locally assign to |\mmzContext|; during memoization, +% we will globally assign to |\mmzContextExtra|. +\newtoks\mmzContext +\newtoks\mmzContextExtra +% \end{macro} +% \begin{key}{context, clear context} +% The user interface keys for context manipulation hide the complexity +% underlying the context storage from the user. +\mmzset{% + context/.code={% + \ifmemoizing + \expandafter\gtoksapp\expandafter\mmzContextExtra + \else + \expandafter\toksapp\expandafter\mmzContext + \fi + % We append a comma to the given context chunk, for disambiguation. + {#1,}% + }, + clear context/.code={% + \ifmemoizing + \expandafter\global\expandafter\mmzContextExtra + \else + \expandafter\mmzContext + \fi + {}% + }, + clear context/.value forbidden, + % \end{key} + % \begin{key}{meaning to context, csname meaning to context, key meaning to + % context, key value to context, /handlers/.meaning to context, + % /handlers/.value to context} + % Utilities to put the meaning of various stuff into |context|. + meaning to context/.code={\forcsvlist\mmz@mtoc{#1}}, + csname meaning to context/.code={\mmz@mtoc@cs{#1}}, + key meaning to context/.code={\forcsvlist\mmz@mtoc\mmz@mtoc@keycmd{#1}}, + key value to context/.code={\forcsvlist\mmz@mtoc@key{#1}}, + /handlers/.meaning to context/.code={% + \expanded{\noexpand\mmz@mtoc@cs{pgfk@\pgfkeyscurrentpath/.@cmd}}}, + /handlers/.value to context/.code={% + \expanded{\noexpand\mmz@mtoc@cs{pgfk@\pgfkeyscurrentpath}}}, +} +\def\mmz@mtoc#1{% + \collargs@cs@cases{#1}% + {\mmz@mtoc@cmd{#1}}% + {\mmz@mtoc@error@notcsorenv{#1}}% + {% + \mmz@mtoc@cs{% + %<context>start% + #1}% + \mmz@mtoc@cs{% + %<latex,plain>end% + %<context>stop% + #1}% + }% +} +\def\mmz@mtoc@cmd#1{% + \begingroup + \escapechar=-1 + \expandafter\endgroup + \expandafter\mmz@mtoc@cs\expandafter{\string#1}% +} +\def\mmz@mtoc@cs#1{% + \pgfkeysvalueof{/mmz/context/.@cmd}% + \expandafter\string\csname#1\endcsname={\expandafter\meaning\csname#1\endcsname}% + \pgfeov +} +\def\mmz@mtoc@key#1{\mmz@mtoc@cs{pgfk@#1}} +\def\mmz@mtoc@key#1{\mmz@mtoc@cs{pgfk@#1/.@cmd}} +\def\mmz@mtoc@error@notcsorenv#1{% + \PackageError{memoize}{'\detokenize{#1}' passed to key 'meaning to context' is neither a command nor an environment}{}% +} +% \end{key} +% +% +% \subsection{C-memos} +% \label{sec:code:c-memo} +% +% \paragraph{The path} to a c-memo consists of the path prefix, the MD5 sum of +% the memoized code, and suffix |.memo|. +\def\mmz@cmemo@path{\mmz@prefix@path\mmz@code@mdfivesum.memo} +% \begin{macro}{\mmzCMemo} +% The additional, free-form content of the c-memo is collected in this token +% register. +\newtoks\mmzCMemo +% \end{macro} +% +% \begin{key}{include source in cmemo} +% \begin{macro}{\ifmmz@include@source} +% Should we include the memoized code in +% the c-memo? By default, yes. \bigskip +\mmzset{% + include source in cmemo/.is if=mmz@include@source, +} +\newif\ifmmz@include@source +\mmz@include@sourcetrue +% \end{macro} +% \end{key} +% +% \begin{macro}{\mmz@write@cmemo} +% This macro creates the c-memo from the contents of |\mmzContextExtra| and +% |\mmzCMemo|. +\def\mmz@write@cmemo{% + % Open the file for writing. + \immediate\openout\mmz@out{\mmz@cmemo@path}% + % The memo starts with the |\mmzMemo| marker (a signal that the memo is valid). + \immediate\write\mmz@out{\noexpand\mmzMemo}% + % We store the content of |\mmzContextExtra| by writing out a command that + % will (globally) assign its content back into this register. + \immediate\write\mmz@out{% + \global\mmzContextExtra{\the\mmzContextExtra}\collargs@percentchar + }% + % Write out the free-form part of the c-memo. + \immediate\write\mmz@out{\the\mmzCMemo\collargs@percentchar}% + % When |include source in cmemo| is in effect, add the memoized code, hiding + % it behind the |\mmzSource| marker. + \ifmmz@include@source + \immediate\write\mmz@out{\noexpand\mmzSource}% + \immediate\write\mmz@out{\the\mmz@mdfive@source}% + \fi + % Close the file. + \immediate\closeout\mmz@out + % Record that we wrote a new c-memo. + \pgfkeysalso{/mmz/record/new cmemo={\mmz@cmemo@path}}% +} +% \end{macro} +% +% \begin{macro}{\mmz@write@empty@cmemo} +% This macro is used to create an empty c-memo on aborted memoization, to +% make sure that no leftover c-memo tricks Memoize into thinking that the +% code was successfully memoized. +\def\mmz@write@empty@cmemo{% + \immediate\openout\mmz@out{\mmz@cmemo@path}% + \immediate\closeout\mmz@out +} +% \end{macro} +% +% +% \begin{macro}{\mmzSource} +% The c-memo memoized code marker. This macro is synonymous with |\endinput|, +% so the source following it is ignored when inputting the c-memo. +\let\mmzSource\endinput +% \end{macro} +% +% \begin{macro}{\mmz@process@cmemo} +% This macro inputs the c-memo, which will update the context code, which we +% can then compute the MD5 sum of. +\def\mmz@process@cmemo{% + \mmz@trace@process@cmemo + % |\ifmmz@abort| serves as a signal that the c-memo exists and is of correct + % form. + \global\mmz@aborttrue + % If c-memo sets |\ifmmzUnmemoizable|, we will compile regularly. + \global\mmzUnmemoizablefalse + \def\mmzMemo{\global\mmz@abortfalse}% + % Just a safeguard \dots\ c-memo assigns to |\mmzContextExtra| anyway. + \global\mmzContextExtra{}% + % Input the c-memo, if it exists, and record that we have used it. + \IfFileExists{\mmz@cmemo@path}{% + \input{\mmz@cmemo@path}% + \pgfkeysalso{/mmz/record/used cmemo={\mmz@cmemo@path}}% + }{% + \mmz@trace@process@no@cmemo + }% + % Compute the context MD5 sum. + \mmz@compute@context@mdfivesum +} +% \end{macro} +% +% \begin{macro}{\mmz@compute@context@mdfivesum} +% This macro computes the MD5 sum of the concatenation of |\mmzContext| and +% |\mmzContextExtra|, and writes out the tracing info when |trace context| is +% in effect. The argument is the tracing note. +% \end{macro} +\def\mmz@compute@context@mdfivesum{% + \xdef\mmz@context@key{\the\mmzContext\the\mmzContextExtra}% + % A special provision for padding, which occurs in the context by default, + % and may contain otherwise undefined macros referring to the extern + % dimensions. We make sure that when we expand the context key, + % |\mmz@paddings| contains the stringified |\width| etc., while these macros + % (which may be employed by the end user in the context expression), are + % returned to their original definitions. + \begingroup + \begingroup + \def\width{\string\width}% + \def\height{\string\height}% + \def\depth{\string\depth}% + \edef\mmz@paddings{\mmz@paddings}% + \expandafter\endgroup + \expandafter\def\expandafter\mmz@paddings\expandafter{\mmz@paddings}% + % We pre-expand the concatenated context, for tracing\slash inclusion in the + % cc-memo. In \hologo{LaTeX}, we protect the expansion, as the context + % expression may contain whatever. + %<latex>\protected@xdef + %<!latex>\xdef + \mmz@context@key{\mmz@context@key}% + \endgroup + % Compute the MD5 sum. We have to assign globally, because this macro is (also) + % called after inputting the c-memo, while the resulting MD5 sum is used to + % input the cc-memo, which happens outside the |\Memoize| group. + % |\mmz@context@mdfivesum|. + \xdef\mmz@context@mdfivesum{\pdf@mdfivesum{\expandonce\mmz@context@key}}% +} +% +% +% \subsection{Cc-memos} +% \label{sec:code:cc-memo} +% +% \paragraph{The path} to a cc-memo consists of the path prefix, the +% hyphen-separated MD5 sums of the memoized code and the (evaluated) context, +% and suffix |.memo|. +\def\mmz@ccmemo@path{% + \mmz@prefix@path\mmz@code@mdfivesum-\mmz@context@mdfivesum.memo} +% +% \paragraph{The structure} of a cc-memo: +% \begin{itemize} +% \item the list of resources consisting of calls to |\mmzResource|; +% \item the core memo code (which includes the externs when executed), +% introduced by marker |\mmzMemo|; and, +% \item optionally, the context expansion, introduced by marker +% |\mmzThisContext|. +% \end{itemize} +% +% We begin the cc-memo with a list of extern files included by the core memo +% code so that we can check whether these files exist prior to executing the +% core memo code. Checking this on the fly, while executing the core memo +% code, would be too late, as that code is arbitrary (and also executed outside +% the |\Memoize| group). +% +% \begin{macro}{\mmzCCMemo} +% During memoization, the core content of the cc-memo is collected into this +% token register. +\newtoks\mmzCCMemo +% \end{macro} +% +% \begin{key}{include context in ccmemo} +% \begin{macro}{\ifmmz@include@context} +% Should we include the context expansion in the cc-memo? By default, no. +% \bigskip +\newif\ifmmz@include@context +\mmzset{% + include context in ccmemo/.is if=mmz@include@context, +} +% \end{macro} +% \end{key} +% +% \begin{key}{direct ccmemo input} +% \begin{macro}{\ifmmz@direct@ccmemo@input} +% When this conditional is false, the +% cc-memo is read indirectly, via a token register, to facilitate +% inverse search. \bigskip +\newif\ifmmz@direct@ccmemo@input +\mmzset{% + direct ccmemo input/.is if=mmz@direct@ccmemo@input, +} +% \end{macro} +% \end{key} +% +% \begin{macro}{\mmz@write@ccmemo} +% This macro creates the cc-memo from the list of resources in +% |\mmz@ccmemo@resources| and the contents of |\mmzCCMemo|. +\def\mmz@write@ccmemo{% + % Open the cc-memo file for writing. Note that the filename contains the + % context MD5 sum, which can only be computed after memoization, as the + % memoized code can update the context. This is one of the two reasons why + % we couldn't write the cc-memo directly into the file, but had to collect + % its contents into token register |\mmzCCMemo|. + \immediate\openout\mmz@out{\mmz@ccmemo@path}% + % Token register |\mmz@ccmemo@resources| consists of calls to + % |\mmz@ccmemo@append@resource|, so the following code writes down the list + % of created externs into the cc-memo. Wanting to have this list at the top + % of the cc-memo is the other reason for the roundabout creation of the + % cc-memo --- the resources become known only during memoization, as well. + \begingroup + \the\mmz@ccmemo@resources + \endgroup + % Write down the content of |\mmzMemo|, but first introduce it by the + % |\mmzMemo| marker. + \immediate\write\mmz@out{\noexpand\mmzMemo}% + \immediate\write\mmz@out{\the\mmzCCMemo\collargs@percentchar}% + % Write down the context tracing info when |include context in ccmemo| is in + % effect. + \ifmmz@include@context + \immediate\write\mmz@out{\noexpand\mmzThisContext}% + \immediate\write\mmz@out{\expandonce{\mmz@context@key}}% + \fi + % Insert the end-of-file marker and close the file. + \immediate\write\mmz@out{\noexpand\mmzEndMemo}% + \immediate\closeout\mmz@out + % Record that we wrote a new cc-memo. + \pgfkeysalso{/mmz/record/new ccmemo={\mmz@ccmemo@path}}% +} +% \end{macro} +% +% \begin{macro}{\mmz@ccmemo@append@resource} +% Append the resource to the cc-memo (we are nice to external utilities and +% put each resource on its own line). |#1| is the sequential number of the +% extern belonging to the memoized code; below, we assign it to |\mmz@seq|, +% which appears in |\mmz@extern@name|. Note that |\mmz@extern@name| only +% contains the extern filename --- without the path, so that externs can be +% used by several projects, or copied around. +\def\mmz@ccmemo@append@resource#1{% + \mmz@seq=#1\relax + \immediate\write\mmz@out{% + \string\mmzResource{\mmz@extern@name}\collargs@percentchar}% +} +% \end{macro} +% +% \begin{macro}{\mmzResource} +% A list of these macros is located at the top of a cc-memo. The macro +% checks for the existence of the extern file, given as |#1|. If the extern +% does not exist, we redefine |\mmzMemo| to |\endinput|, so that the core +% content of the cc-memo is never executed; see also |\mmz@process@ccmemo| +% above. +\def\mmzResource#1{% + % We check for existence using |\pdffilesize|, because an empty PDF, which + % might be produced by a failed \hologo{TeX}-based extraction, should count + % as no file. The |0| behind |\ifnum| is there because |\pdffilesize| + % returns an empty string when the file does not exist. + \ifnum0\pdf@filesize{\mmz@dir@path/#1}=0 + \ifmmz@direct@ccmemo@input + \let\mmzMemo\endinput + \else + % With indirect cc-memo input, we simulate end-of-input by grabbing + % everything up to the end-of-memo marker. In the indirect cc-memo + % input, a |\par| token shows up after |\mmzEndMemo|, I'm not sure why + % (|\everyeof={}| does not help). + \long\def\mmzMemo##1\mmzEndMemo\par{}% + \fi + \mmz@trace@resource{#1}% + \fi +} +% \end{macro} +% +% \begin{macro}{\mmz@process@ccmemo,\mmzThisContext,\mmzEndMemo} +% This macro processes the cc-memo. +\def\mmz@process@ccmemo{% + \mmz@trace@process@ccmemo + % The following conditional signals whether cc-memo was successfully + % utilized. If the cc-memo file does not exist, |\ifmmz@abort| will remain + % true. If it exists, it is headed by the list of resources. If a + % resource check fails, |\mmzMemo| (which follows the list of resources) is + % redefined to |\endinput|, so |\ifmmz@abort| remains true. However, if + % all resource checks are successful, |\mmzMemo| marker is reached with + % the below definition in effect, so |\ifmmz@abort| becomes false. Note + % that this marker also closes the |\Memoize| group, so that the core + % cc-memo content is executed in the original group --- and that this + % does not happen if anything goes wrong! + \global\mmz@aborttrue + % Note that |\mmzMemo| may be redefined by |\mmzResource| upon an unavailable + % extern file. + \def\mmzMemo{% + \endgroup + \global\mmz@abortfalse + % We |\let| the control sequence used for extern inclusion in the cc-memo to + % the macro which includes the extern from the extern file. + \let\mmzIncludeExtern\mmz@include@extern + }% + % Define |\mmzEndMemo| wrt |\ifmmz@direct@ccmemo@input|, whose value will be + % lost soon because |\mmMemo| will close the group --- that's also why this + % definition is global. + \xdef\mmzEndMemo{% + \ifmmz@direct@ccmemo@input + \noexpand\endinput + \else + % In the indirect cc-memo input, a |\par| token shows up after + % |\mmzEndMemo|, I'm not sure why (|\everyeof={}| does not help). + \unexpanded{% + \def\mmz@temp\par{}% + \mmz@temp + }% + \fi + }% + % The cc-memo context marker, again wrt |\ifmmz@direct@ccmemo@input| and + % globally. With direct cc-memo input, this macro is synonymous with + % |\endinput|, so the (expanded) context following it is ignored when + % inputting the cc-memo. With indirect input, we simulate end-of-input by + % grabbing everything up to the end-of-memo marker (plus gobble the |\par| + % mentioned above). + \xdef\mmzThisContext{% + \ifmmz@direct@ccmemo@input + \noexpand\endinput + \else + \unexpanded{% + \long\def\mmz@temp##1\mmzEndMemo\par{}% + \mmz@temp + }% + \fi + }% + % Input the cc-memo if it exists. + \IfFileExists{\mmz@ccmemo@path}{% + \ifmmz@direct@ccmemo@input + \input{\mmz@ccmemo@path}% + \else + % Indirect cc-memo input reads the cc-memo into a token register and + % executes the contents of this register. + \filetotoks\toks@{\mmz@ccmemo@path}% + \the\toks@ + \fi + % Record that we have used the cc-memo. + \pgfkeysalso{/mmz/record/used ccmemo={\mmz@ccmemo@path}}% + }{% + \mmz@trace@process@no@ccmemo + }% + \ifmmz@abort + % The cc-memo doesn't exist, or some of the resources don't. We need to + % memoize, but we'll do it only if |readonly| is not in effect, otherwise + % we'll perform a regular compilation. (Note that we are still in the group + % opened prior to executing |\Memoize|.) + \mmz@trace@process@ccmemo@fail + \ifnum\mmz@mode=\mmz@mode@readonly\relax + \expandafter\expandafter\expandafter\mmz@compile + \else + \expandafter\expandafter\expandafter\mmz@memoize + \fi + \else + \mmz@trace@process@ccmemo@ok + \fi +} +% \end{macro} +% +% \subsection{The externs} +% \label{sec:code:extern} +% +% \paragraph{The path} to an extern is like the path to a cc-memo, modulo +% suffix |.pdf|, of course. However, in case memoization of a chunk produces +% more than one extern, the filename of any non-first extern includes +% |\mmz@seq|, the sequential number of the extern as well (we start the +% numbering at $0$). We will have need for several parts of the full path to +% an extern: the basename, the filename, the path without the suffix, and the +% full path. +\newcount\mmz@seq +\def\mmz@extern@basename{% + \mmz@prefix\mmz@code@mdfivesum-\mmz@context@mdfivesum + \ifnum\mmz@seq>0 -\the\mmz@seq\fi +} +\def\mmz@extern@name{\mmz@extern@basename.pdf} +\def\mmz@extern@basepath{\mmz@dir@path/\mmz@extern@basename} +\def\mmz@extern@path{\mmz@extern@basepath.pdf} +% +% \begin{key}{padding left, padding right, padding top, padding bottom} +% These options set the amount of space surrounding the bounding box of the +% externalized graphics in the resulting PDF, i.e.\ in the extern file. This +% allows the user to deal with \TikZ; overlays, |\rlap| and |\llap|, etc. +\mmzset{ + padding left/.store in=\mmz@padding@left, + padding right/.store in=\mmz@padding@right, + padding top/.store in=\mmz@padding@top, + padding bottom/.store in=\mmz@padding@bottom, +% \end{key} +% +% \begin{key}{padding} +% A shortcut for setting all four paddings at once. + padding/.style={ + padding left=#1, padding right=#1, + padding top=#1, padding bottom=#1 + }, + % The default padding is what \hologo{pdfTeX} puts around the page anyway, 1 + % inch, but we'll use |1 in| rather than |1 true in|, which is the true + % default value of |\pdfhorigin| and |\pdfvorigin|, as we want the padding to + % adjust with magnification. + padding=1in, +% \end{key} +% + % \begin{key}{padding to context} + % This key adds padding to the context. Note that we add the padding + % expression (|\mmz@paddings|, defined below, refers to all the individual + % padding macros), not the actual value (at the time of expansion). This + % is so because |\width|, |\height| and |\depth| are not defined outside + % extern shipout routines, and the context is evaluated elsewhere. + padding to context/.style={ + context={padding=(\mmz@paddings)}, + }, + % \end{key} + % + % Padding nearly always belongs into the context --- the exception being + % memoized code which produces no externs (\MS\ref{sec:pure-memoization}) --- + % so we execute this key immediately. + padding to context, +} +\def\mmz@paddings{% + \mmz@padding@left,\mmz@padding@bottom,\mmz@padding@right,\mmz@padding@top +} +% +% \begin{macro}{\mmzExternalizeBox} +% This macro is the public interface to externalization. In Memoize itself, +% it is called from the default memoization driver, |\mmzSingleExternDriver|, +% but it should be called by any driver that wishes to produce an extern, see +% \MS\ref{sec:memoization-drivers} for details. It takes two arguments: +% \begin{enumerate}[\tt\#1] +% \item The box that we want to externalize. It's content will remain intact. +% The box may be given either as a control sequence, declared via |\newbox|, +% or as box number (say, 0). +% \item The token register which will receive the code that includes the extern +% into the document; it is the responsibility of the memoization driver to +% (globally) include the contents of the register in the cc-memo, i.e.\ in +% token register |\mmzCCMemo|. This argument may be either a control +% sequence, declared via |\newtoks|, or a |\toks|\meta{token register +% number}. +% \end{enumerate} +\def\mmzExternalizeBox#1#2{% + \begingroup + % A courtesy to the user, so they can define padding in terms of the size of + % the externalized graphics. + \def\width{\wd#1 }% + \def\height{\ht#1 }% + \def\depth{\dp#1 }% + % Store the extern-inclusion code in a temporary macro, which will be + % smuggled out of the group. + \xdef\mmz@global@temp{% + % Executing |\mmzIncludeExtern| from the cc-memo will include the extern + % into the document. + \noexpand\mmzIncludeExtern + % |\mmzIncludeExtern| identifies the extern by its sequence number, + % |\mmz@seq|. + {\the\mmz@seq}% + % What kind of box? We |\noexpand| the answer just in case someone + % redefined them. + \ifhbox#1\noexpand\hbox\else\noexpand\vbox\fi + % The dimensions of the extern. + {\the\wd#1}% + {\the\ht#1}% + {\the\dp#1}% + % The padding values. + {\the\dimexpr\mmz@padding@left}% + {\the\dimexpr\mmz@padding@bottom}% + {\the\dimexpr\mmz@padding@right}% + {\the\dimexpr\mmz@padding@top}% + }% + % Prepend the new extern box into the global extern box where we collect all + % the externs of this memo. Note that we |\copy| the extern box, retaining + % its content --- we will also want to place the extern box in its regular + % place in the document. + \global\setbox\mmz@tbe@box\vbox{\copy#1\unvbox\mmz@tbe@box}% + % Add the extern to the list of resources, which will be included at the top + % of the cc-memo, to check whether the extern files exists at the time the + % cc-memo is utilized. In the cc-memo, the list will contain full extern + % filenames, which are currently unknown, but no matter; right now, providing + % the extern sequence number suffices, the full extern filename will be + % produced at the end of memoization, once the context MD5 sum is known. + \xtoksapp\mmz@ccmemo@resources{% + \noexpand\mmz@ccmemo@append@resource{\the\mmz@seq}% + }% + % Increment the counter containing the sequence number of the extern within + % this memo. + \global\advance\mmz@seq1 + % Assign the extern-including code into the token register given in |#2|. + % This register may be given either as a control sequence or as + % |\toks|\meta{token register number}, and this is why we have temporarily + % stored the code (into |\mmz@global@temp|) globally: a local storage with + % |\expandafter\endgroup\expandafter| here would fail with the receiving + % token register given as |\toks|\meta{token register number}. + \endgroup + #2\expandafter{\mmz@global@temp}% +} +% \end{macro} +% +% \begin{macro}{\mmz@ccmemo@resources} +% This token register, populated by |\mmz@externalize@box| and used by +% |\mmz@write@ccmemo|, holds the list of externs produced by memoization of +% the current chunk. +\newtoks\mmz@ccmemo@resources +% \end{macro} +% +% \begin{macro}{\mmz@tbe@box} +% |\mmz@externalize@box| does not directly dump the extern into the document +% (as a special page). Rather, the externs are collected into +% |\mmz@tbe@box|, whose contents are dumped into the document at the end of +% memoization of the current chunk. In this way, we guarantee that aborted +% memoization does not pollute the document. +\newbox\mmz@tbe@box +% \end{macro} +% +% \begin{macro}{\mmz@shipout@externs} +% This macro is executed at the end of +% memoization, when the externs are waiting for us in |\mmz@tbe@box| and need +% to be dumped into the document. It loops through the contents of +% |\mmz@tbe@box|,\footnote{The looping code is based on TeX.SE +% answer \url(https://){tex.stackexchange.com/a/25142/16819} by Bruno Le +% Floch.}, putting each extern into |\mmz@box| and calling +% |\mmz@shipout@extern|. Note that the latter macro is executed within the +% group opened by |\vbox| below. +\def\mmz@shipout@externs{% + \global\mmz@seq 0 + \setbox\mmz@box\vbox{% + % Set the macros below to the dimensions of the extern box, so that the + % user can refer to them in the padding specification (which is in turn + % used in the page setup in |\mmz@shipout@extern|). + \def\width{\wd\mmz@box}% + \def\height{\ht\mmz@box}% + \def\depth{\dp\mmz@box}% + \vskip1pt + \ifmmzkeepexterns\expandafter\unvcopy\else\expandafter\unvbox\fi\mmz@tbe@box + \@whilesw\ifdim0pt=\lastskip\fi{% + \setbox\mmz@box\lastbox + \mmz@shipout@extern + }% + }% +} +% \end{macro} +% +% \begin{macro}{\mmz@shipout@extern} +% This macro ships out a single extern, which +% resides in |\mmz@box|, and records the creation of the new extern. +\def\mmz@shipout@extern{% + % Calculate the expected width and height. We have to do this now, before we + % potentially adjust the box size and paddings for magnification. + \edef\expectedwidth{\the\dimexpr + (\mmz@padding@left) + \wd\mmz@box + (\mmz@padding@right)}% + \edef\expectedheight{\the\dimexpr + (\mmz@padding@top) + \ht\mmz@box + \dp\mmz@box + (\mmz@padding@bottom)}% + % Apply the inverse magnification, if |\mag| is not at the default + % value. We'll do this in a group, which will last until shipout. + \begingroup + \ifnum\mag=1000 + \else + \mmz@shipout@mag + \fi + % Setup the geometry of the extern page. In \hologo{plainTeX} and + % \hologo{LaTeX}, setting |\pdfpagewidth| and |\pdfpageheight| seems to do + % the trick of setting the extern page dimensions. In \hologo{ConTeXt}, + % however, the resulting extern page ends up with the PDF |/CropBox| + % specification of the current regular page, which is then used (ignoring our + % |mediabox| requirement) when we're including the extern into the document + % by |\mmzIncludeExtern|. Typically, this results in a page-sized extern. + % I'm not sure how to deal with this correctly. In the workaround below, we + % use Lua function |backends.codeinjections.setupcanvas| to set up page + % dimensions: we first remember the current page dimensions + % (|\edef\mmz@temp|), then set up the extern page dimensions + % (|\expanded{...}|), and finally, after shipping + % out the extern page, revert to the current page dimensions by executing + % |\mmz@temp| at the very end of this macro. + %<*plain,latex> + \pdfpagewidth\dimexpr + (\mmz@padding@left) + \wd\mmz@box + (\mmz@padding@right)\relax + \pdfpageheight\dimexpr + (\mmz@padding@top) + \ht\mmz@box + \dp\mmz@box+ (\mmz@padding@bottom)\relax + %</plain,latex> + %<*context> + \edef\mmz@temp{% + \noexpand\directlua{ + backends.codeinjections.setupcanvas({ + paperwidth=\the\numexpr\pagewidth, + paperheight=\the\numexpr\pageheight + }) + }% + }% + \expanded{% + \noexpand\directlua{ + backends.codeinjections.setupcanvas({ + paperwidth=\the\numexpr\dimexpr + \mmz@padding@left + \wd\mmz@box + \mmz@padding@right\relax, + paperheight=\the\numexpr\dimexpr + \mmz@padding@top + \ht\mmz@box + \dp\mmz@box+ \mmz@padding@bottom\relax + }) + }% + }% + %</context> + % + % We complete the page setup by setting the content offset. + \hoffset\dimexpr\mmz@padding@left - \pdfhorigin\relax + \voffset\dimexpr\mmz@padding@top - \pdfvorigin\relax + % We shipout the extern page using the |\shipout| primitive, so that the + % extern page is not modified, or even registered, by the shipout code of the + % format or some package. I can't imagine those shipout routines ever + % needing to know about the extern page. In fact, most often knowing about + % it would be undesirable. For example, \hologo{LaTeX} and \hologo{ConTeXt} + % count the ``real'' pages, but usually to know whether they are shipping out + % an odd or an even page, or to make the total number of pages available to + % subsequent compilations. Taking the extern pages into account would + % disrupt these mechanisms. + % + % Another thing: delayed |\write|s. We have to make sure that any + % \hologo{LaTeX}-style protected stuff in those is not expanded. We don't + % bother introducing a special group, as we'll close the |\mag| group right + % after the shipout anyway. + %<latex>\let\protect\noexpand + \pdf@primitive\shipout\box\mmz@box + %<context>\mmz@temp + \endgroup + % Advance the counter of shipped-out externs. We do this before preparing + % the recording information below, because the extern extraction tools expect + % the extern page numbering to start with $1$. + \global\advance\mmzExternPages1 + % Prepare the macros which may be used in |record/<type>/new extern| code. + \edef\externbasepath{\mmz@extern@basepath}% + % Adding up the counters below should result in the real page number of the + % extern. Macro |\mmzRegularPages| holds the number of pages which were + % shipped out so far using the regular shipout routine of the format; + % |\mmzExternPages| holds the number of shipped-out extern pages; and + % |\mmzExtraPages| holds, or at least should hold, the number of pages + % shipped out using any other means. + \edef\pagenumber{\the\numexpr\mmzRegularPages + % In \hologo{LaTeX}, the |\mmzRegularPages| holds to number of pages + % already shipped out. In \hologo{ConTeXt}, the counter is already + % increased while processing the page, so we need to subtract $1$. +%<context> -1% + +\mmzExternPages+\mmzExtraPages}% + % Record the creation of the new extern. We do this after shipping out the + % extern page, so that the recording mechanism can serve as an after-shipout + % hook, for the unlikely situation that some package really needs to do + % something when our shipout happens. Note that we absolutely refuse to + % provide a before-shipout hook, because we can't allow anyone messing with + % our extern, and that using this after-shipout ``hook'' is unnecessary for + % counting extern shipouts, as we already provide this information in the + % public counter |\mmzExternPages|. + \mmzset{record/new extern/.expanded=\mmz@extern@path}% + % Advance the sequential number of the extern, in the context of the current + % memoized code chunk. This extern numbering starts at 0, so we only do this + % after we wrote the cc-memo and called |record/new extern|. + \global\advance\mmz@seq1 +} +% \end{macro} +% +% \begin{macro}{\mmz@shipout@mag} +% This macro applies the inverse magnification, so that the extern ends up +% with its natural size on the extern page. +\def\mmz@shipout@mag{% + % We scale the extern box using the PDF primitives: |q| and |Q| save and + % restore the current graphics state; |cm| applies the given coordinate + % transformation matrix. ($a$ $b$ $c$ $d$ $e$ $f$ |cm| transforms $(x,y)$ + % into $(ax+cy+e,bx+dy+f)$.) + \setbox\mmz@box\hbox{% + \pdfliteral{q \mmz@inverse@mag\space 0 0 \mmz@inverse@mag\space 0 0 cm}% + \copy\mmz@box\relax + \pdfliteral{Q}% + }% + % We first have to scale the paddings, as they might refer to the |\width| + % etc.\ of the extern. + \dimen0=\dimexpr\mmz@padding@left\relax + \edef\mmz@padding@left{\the\dimexpr\mmz@inverse@mag\dimen0}% + \dimen0=\dimexpr\mmz@padding@bottom\relax + \edef\mmz@padding@bottom{\the\dimexpr\mmz@inverse@mag\dimen0}% + \dimen0=\dimexpr\mmz@padding@right\relax + \edef\mmz@padding@right{\the\dimexpr\mmz@inverse@mag\dimen0}% + \dimen0=\dimexpr\mmz@padding@top\relax + \edef\mmz@padding@top{\the\dimexpr\mmz@inverse@mag\dimen0}% + % Scale the extern box. + \wd\mmz@box=\mmz@inverse@mag\wd\mmz@box\relax + \ht\mmz@box=\mmz@inverse@mag\ht\mmz@box\relax + \dp\mmz@box=\mmz@inverse@mag\dp\mmz@box\relax +} +% \end{macro} +% +% \begin{macro}{\mmz@inverse@mag} +% The inverse magnification factor, i.e.\ the number we have to multiply the +% extern dimensions with so that they will end up in their natural size. We +% compute it, once and for all, at the beginning of the document. To do +% that, we borrow the little macro |\Pgf@geT| from |pgfutil-common| (but +% rename it). +{\catcode`\p=12\catcode`\t=12\gdef\mmz@Pgf@geT#1pt{#1}} +\mmzset{begindocument/.append code={% + \edef\mmz@inverse@mag{\expandafter\mmz@Pgf@geT\the\dimexpr 1000pt/\mag}% + }} +% \end{macro} +% +% \begin{macro}{\mmzRegularPages} +% This counter holds the number of pages shipped out by the format's shipout +% routine. \hologo{LaTeX} and \hologo{ConTeXt} keep track of this in +% dedicated counters, so we simply use those. In \hologo{plainTeX}, we have +% to hack the |\shipout| macro to install our own counter. In fact, we +% already did this while loading the required packages, in order to avoid it +% being redefined by |atbegshi| first. All that is left to do here is to +% declare the counter. +%<latex>\let\mmzRegularPages\ReadonlyShipoutCounter +%<context>\let\mmzRegularPages\realpageno +%<plain>\newcount\mmzRegularPages +% \end{macro} +% +% \begin{macro}{\mmzExternPages} +% This counter holds the number of extern pages shipped out so far. +\newcount\mmzExternPages +% \end{macro} +% +% The total number of new externs is announced at the end of the compilation, +% so that \hologo{TeX} editors, |latexmk| and such can propose recompilation. +\mmzset{ + enddocument/afterlastpage/.append code={% + \ifnum\mmzExternPages>0 + \PackageWarning{memoize}{The compilation produced \the\mmzExternPages\space + new extern\ifnum\mmzExternPages>1 s\fi.}% + \fi + }, +} +% +% \begin{macro}{\mmzExtraPages} +% This counter will probably remain at zero forever. It should be advanced by +% any package which (like Memoize) ships out pages bypassing the regular +% shipout routine of the format. +\newcount\mmzExtraPages +% \end{macro} +% +% \begin{macro}{\mmz@include@extern} +% This macro, called from cc-memos as |\mmzIncludeExtern|, inserts an extern +% file into the document. |#1| is the sequential number, |#2| is either +% |\hbox| or |\vbox|, |#3|, |#4| and |#5| are the (expected) width, height +% and the depth of the externalized box; |#6|--|#9| are the paddings (left, +% bottom, right, and top). +\def\mmz@include@extern#1#2#3#4#5#6#7#8#9{% + % Set the extern sequential number, so that we open the correct extern file + % (|\mmz@extern@basename|). + \mmz@seq=#1\relax + % Use the primitive PDF graphics inclusion commands to include the extern + % file. Set the correct depth or the resulting box, and shift it as + % specified by the padding. + \setbox\mmz@box=#2{% + \setbox0=\hbox{% + \lower\dimexpr #5+#7\relax\hbox{% + \hskip -#6\relax + \setbox0=\hbox{% + \mmz@insertpdfpage{\mmz@extern@path}{1}% + }% + \unhbox0 + }% + }% + \wd0 \dimexpr\wd0-#8\relax + \ht0 \dimexpr\ht0-#9\relax + \dp0 #5\relax + \box0 + }% + % Check whether the size of the included extern is as expected. There is no + % need to check |\dp|, we have just set it. (|\mmz@if@roughly@equal| is + % defined in section~\ref{sec:code:extract:tex}.) + \mmz@tempfalse + \mmz@if@roughly@equal{\mmz@tolerance}{#3}{\wd\mmz@box}{% + \mmz@if@roughly@equal{\mmz@tolerance}{#4}{\ht\mmz@box}{% + \mmz@temptrue + }{}}{}% + \ifmmz@temp + \else + \mmz@use@memo@warning{\mmz@extern@path}{#3}{#4}{#5}% + \fi + % Use the extern box, with the precise size as remembered at memoization. + \wd\mmz@box=#3\relax + \ht\mmz@box=#4\relax + \box\mmz@box + % Record that we have used this extern. + \pgfkeysalso{/mmz/record/used extern={\mmz@extern@path}}% +} +% \end{macro} +\def\mmz@use@memo@warning#1#2#3#4{% + \PackageWarning{memoize}{Unexpected size of extern "#1"; + expected #2\space x \the\dimexpr #3+#4\relax, + got \the\wd\mmz@box\space x \the\dimexpr\the\ht\mmz@box+\the\dp\mmz@box\relax}% +} +% \begin{macro}{\mmz@insertpdfpage} +% This macro inserts a page from the PDF into the document. We define it +% according to which engine is being used. Note that \hologo{ConTeXt} always +% uses \hologo{LuaTeX}. +%<latex,plain>\ifdef\luatexversion{% + \def\mmz@insertpdfpage#1#2{% #1 = filename, #2 = page number + \saveimageresource page #2 mediabox {#1}% + \useimageresource\lastsavedimageresourceindex + }% +%<*latex,plain> +}{% + \ifdef\XeTeXversion{% + \def\mmz@insertpdfpage#1#2{% + \XeTeXpdffile #1 page #2 media + }% + }{% pdfLaTeX + \def\mmz@insertpdfpage#1#2{% + \pdfximage page #2 mediabox {#1}% + \pdfrefximage\pdflastximage + }% + }% +} +%</latex,plain> +% \end{macro} +% +% \begin{macro}{\mmz@include@extern@from@tbe@box} +% Include the extern number |#1| residing in |\mmz@tbe@box| into the +% document. This helper macro makes it possible for a complex memoization +% driver to process the cc-memo right after memoization --- by using the +% |\mmzkeepexternstrue\xtoksapp\mmzAfterMemoizationExtra{\the\mmzCCMemo}| +% trick --- to ensure that the result of the memoizing compilation matches +% the result of inputting the cc-memo. The rest of the arguments are +% gobbled, as we don't have to do any size adjustment or checking here, and +% the box is of the correct type. +\def\mmz@include@extern@from@tbe@box#1#2#3#4#5#6#7#8#9{% + \setbox0\vbox{% + \@tempcnta#1\relax + \vskip1pt + \unvcopy\mmz@tbe@box + \@whilenum\@tempcnta>0\do{% + \setbox0\lastbox + \advance\@tempcnta-1\relax + }% + \global\setbox1\lastbox + \@whilesw\ifdim0pt=\lastskip\fi{% + \setbox0\lastbox + }% + \box\mmz@box + }% + \box1 +} +% \end{macro} +% +% \section{Extraction} +% \label{sec:code:extract} +% +% \subsection{Extraction mode and method} +% \label{sec:code:extraction-mode-method} +% +% \begin{key}{extract} +% This key selects the extraction mode and method. It normally occurs in the +% package options list, less commonly in the preamble, and never in the +% document body. +\def\mmzvalueof#1{\pgfkeysvalueof{/mmz/#1}} +\mmzset{ + extract/.estore in=\mmz@extraction@method, + extract/.value required, + begindocument/.append style={extract/.code=\mmz@preamble@only@warning}, + % \end{key} + % + % \begin{key}{extract/perl, extract/python} + % Any other value will select internal extraction with the given method. + % Memoize ships with two extraction scripts, a Perl script and a Python + % script, which are selected by |extract=perl| (the default) and + % |extract=python|, respectively. We run the scripts in verbose mode + % (without |-q|), and keep the |.mmz| file as is, i.e.\ we're not commenting + % out the |\mmzNewExtern| lines, because we're about to overwrite it + % anyway. We also request the log file, which will contain + % |\mmzExtractionSuccessful| if extraction was successful. + extract/perl/.code={% + \mmz@clear@extraction@log + \pdf@system{% + \mmzvalueof{perl extraction command}\space + \mmzvalueof{perl extraction options}% + }% + \mmz@check@extraction@log{perl}% + \def\mmz@mkdir@command##1{\mmzvalueof{perl extraction command}\space --mkdir "##1"}% + }, + perl extraction command/.initial=memoize-extract.pl, + perl extraction options/.initial={% +% \begin{listingregion}{perl-extraction-options.tex} + -e -l "\mmzOutputDirectory\mmzUnquote\jobname.mmz.log" -w + %<latex>"\string\PackageWarning{memoize (perl-based extraction)}{\string\warningtext}" + %<plain>"\string\warning{memoize (perl-based extraction): \string\warningtext}" + %<context>"\string\warning{memoize (perl-based extraction): \string\warningtext}" + "\mmzOutputDirectory\mmzUnquote\jobname.mmz" +% \end{listingregion} + }, + extract=perl, + extract/python/.code={% + \mmz@clear@extraction@log + \pdf@system{% + \mmzvalueof{python extraction command}\space + \mmzvalueof{python extraction options}% + }% + \mmz@check@extraction@log{python}% + \def\mmz@mkdir@command##1{\mmzvalueof{python extraction command}\space --mkdir "##1"}% + }, + python extraction command/.initial=memoize-extract.py, + python extraction options/.initial={% + -e -l "\mmzOutputDirectory\mmzUnquote\jobname.mmz.log" -w + %<latex>"\string\PackageWarning{memoize (python-based extraction)}{\string\warningtext}" + %<plain>"\string\warning{memoize (python-based extraction): \string\warningtext}" + %<context>"\string\warning{memoize (python-based extraction): \string\warningtext}" + "\mmzOutputDirectory\mmzUnquote\jobname.mmz" + }, +} +\def\mmz@preamble@only@warning{% + \PackageWarning{memoize}{% + Ignoring the invocation of "\pgfkeyscurrentkey". + This key may only be executed in the preamble}% +} +% \end{key} +% +% \paragraph*{The extraction log} +% As we cannot access the exit status of a system command in \hologo{TeX}, we +% communicate with the system command via the ``extraction log file,'' produced +% by both \hologo{TeX}-based extraction and the Perl and Python extraction +% script. This file signals whether the embedded extraction was successful --- +% if it is, the file contains |\mmzExtractionSuccessful| --- and also contains +% any size-mismatch warnings (these are currently only thrown by the +% \hologo{TeX}-based extraction). As the log is really a \hologo{TeX} file, +% the idea is to simply input it after extracting each extern (for +% \hologo{TeX}-based extraction) or after the extraction of all externs (for +% the external scripts). +\def\mmz@clear@extraction@log{% + \begingroup + \immediate\openout0{\mmzUnquote\jobname.mmz.log"}% + \immediate\closeout0 + \endgroup +} +% |#1| is the extraction method. +\def\mmz@check@extraction@log#1{% + \begingroup \def\extractionmethod{#1}% + \mmz@tempfalse \let\mmz@orig@endinput\endinput + \def\endinput{\mmz@temptrue\mmz@orig@endinput}% + \@input{\jobname.mmz.log}% + \ifmmz@temp \else \mmz@extraction@error \fi \endgroup } +\def\mmz@extraction@error{% + \PackageWarning{memoize}{Extraction of externs from document "\mmzUnquote\jobname.pdf" + using method "\extractionmethod" was unsuccessful. Have you set the + shell escape mode as suggested in chapter 1 of the manual?}{}} +% +% \subsection{The record files} +% \label{sec:code:record} +% +% \begin{key}{record} +% This key activates a record \meta{type}: the hooks defined by that record +% \meta{type} will henceforth be executed at the appropriate places. +% +% A \meta{hook} of a particular \meta{type} resides in |pgfkeys| path +% |/mmz/record/|\meta{type}|/|\meta{hook}, and is invoked via +% |/mmz/record/|\meta{hook}. Record type activation thus appends a call of +% the former to the latter. It does so using handler |.try|, so that +% unneeded hooks may be left undefined. +% +\mmzset{ + record/.style={% + record/begin/.append style={ + /mmz/record/#1/begin/.try, + % The |begin| hook also executes the |prefix| hook, so that |\mmzPrefix| + % surely occurs at the top of the |.mmz| file. Listing each prefix type + % separately in this hook ensures that |prefix| of a certain type is + % executed after that type's |begin|. + /mmz/record/#1/prefix/.try/.expanded=\mmz@prefix@path, + }, + record/prefix/.append style={/mmz/record/#1/prefix/.try={##1}}, + record/new extern/.append style={/mmz/record/#1/new extern/.try={##1}}, + record/used extern/.append style={/mmz/record/#1/used extern/.try={##1}}, + record/new cmemo/.append style={/mmz/record/#1/new cmemo/.try={##1}}, + record/new ccmemo/.append style={/mmz/record/#1/new ccmemo/.try={##1}}, + record/used cmemo/.append style={/mmz/record/#1/used cmemo/.try={##1}}, + record/used ccmemo/.append style={/mmz/record/#1/used ccmemo/.try={##1}}, + record/end/.append style={/mmz/record/#1/end/.try}, + }, +} +% \end{key} +% +% \begin{key}{no record} +% This key deactivates all record types. Below, we use it to initialize the +% relevant keys; in the user code, it may be used to deactivate the +% preactivated |mmz| record type. +\mmzset{ + no record/.style={% + % The |begin| hook clears itself after invocation, to prevent double + % execution. Consequently, |record/begin| may be executed by the user in + % the preamble, without any ill effects. + record/begin/.style={record/begin/.style={}}, + % The |prefix| key invokes itself again when the group closes. This way, + % we can correctly track the path prefix changes in the |.mmz| even if + % |path| is executed in a group. + record/prefix/.code={\aftergroup\mmz@record@prefix}, + record/new extern/.code={}, + record/used extern/.code={}, + record/new cmemo/.code={}, + record/new ccmemo/.code={}, + record/used cmemo/.code={}, + record/used ccmemo/.code={}, + % The |end| hook clears itself after invocation, to prevent double + % execution. Consequently, |record/end| may be executed by the user before + % the end of the document, without any ill effects. + record/end/.style={record/end/.code={}}, + } +} +% \end{key} +% We define this macro because |\aftergroup|, used in |record/prefix|, only +% accepts a token. +\def\mmz@record@prefix{% + \mmzset{/mmz/record/prefix/.expanded=\mmz@prefix@path}% +} +% \paragraph{Initialize} the hook keys, preactivate |mmz| record type, and +% execute hooks |begin| and |end| at the edges of the document. +\mmzset{ + no record, + record=mmz, + begindocument/.append style={record/begin}, + enddocument/afterlastpage/.append style={record/end}, +} +% +% \subsubsection{The \texttt{.mmz} file} +% \label{sec:code:record:mmz} +% +% Think of the |.mmz| record file as a \hologo{TeX}-readable log file, which +% lets the extraction procedure know what happened in the previous compilation. +% The file is in \hologo{TeX} format, so that we can trigger internal +% \hologo{TeX}-based extraction by simply inputting it. The commands it +% contains are intentionally as simple as possible (just a macro plus braced +% arguments), to facilitate parsing by the external scripts. +% +% These hooks simply put the calls of the corresponding macros into the file. +% All but hooks but |begin| and |end| receive the full path to the relevant +% file as the only argument (ok, |prefix| receives the full path prefix, as set +% by key |path|). +% +% \begin{key}{record/mmz/...} +\mmzset{ + record/mmz/begin/.code={% + \newwrite\mmz@mmzout + % The record file has a fixed name (the jobname plus the |.mmz| suffix) and + % location (the current directory, i.e.\ the directory where \hologo{TeX} + % is executed from; usually, this will be the directory containing the + % \hologo{TeX} source). + \immediate\openout\mmz@mmzout{\jobname.mmz}% + }, + % The |\mmzPrefix| is used by the clean-up script, which will remove all + % files with the given path prefix but (unless called with |--all|) those + % mentioned in the |.mmz|. Now this script could in principle figure out + % what to remove by inspecting the paths to utilized\slash created + % memos\slash externs in the |.mmz| file, but this method could lead to + % problems in case of an incomplete (perhaps empty) |.mmz| file created by a + % failed compilation. Recording the path prefix in the |.mmz| radically + % increases the chances of a successful clean-up, which is doubly important, + % because a clean-up is sometimes precisely what we need to do to recover + % after a failed compilation. + record/mmz/prefix/.code={% + \immediate\write\mmz@mmzout{\noexpand\mmzPrefix{#1}}% + }, + record/mmz/new extern/.code={% + % While this key receives a single formal argument, Memoize also prepares + % macros |\externbasepath| (|#1| without the |.pdf| suffix), |\pagenumber| + % (of the extern page in the document PDF), and |\expectedwidth| and + % |\expectedheight| (of the extern page). + \immediate\write\mmz@mmzout{% + \noexpand\mmzNewExtern{#1}{\pagenumber}{\expectedwidth}{\expectedheight}% + }% + }, + record/mmz/new cmemo/.code={% + \immediate\write\mmz@mmzout{\noexpand\mmzNewCMemo{#1}}% + }, + record/mmz/new ccmemo/.code={% + \immediate\write\mmz@mmzout{\noexpand\mmzNewCCMemo{#1}}% + }, + record/mmz/used extern/.code={% + \immediate\write\mmz@mmzout{\noexpand\mmzUsedExtern{#1}}% + }, + record/mmz/used cmemo/.code={% + \immediate\write\mmz@mmzout{\noexpand\mmzUsedCMemo{#1}}% + }, + record/mmz/used ccmemo/.code={% + \immediate\write\mmz@mmzout{\noexpand\mmzUsedCCMemo{#1}}% + }, + record/mmz/end/.code={% + % Add the |\endinput| marker to signal that the file is complete. + \immediate\write\mmz@mmzout{\noexpand\endinput}% + \immediate\closeout\mmz@mmzout + }, + % + % \end{key} + % \subsubsection{The shell scripts} +% +% We define two shell script record types: |sh| for Linux, and |bat| for +% Windows. +% +% \begin{key}{sh, bat} +% These keys set the shell script filenames. +% \bigskip + sh/.store in=\mmz@shname, + sh=memoize-extract.\jobname.sh, + bat/.store in=\mmz@batname, + bat=memoize-extract.\jobname.bat, +% \end{key} +% +% \begin{key}{record/sh/...} Define the Linux shell script record type. + record/sh/begin/.code={% + \newwrite\mmz@shout + \immediate\openout\mmz@shout{\mmz@shname}% + }, + record/sh/new extern/.code={% + \begingroup + % Macro |\mmz@tex@extraction@systemcall| is customizable through |tex + % extraction command|, |tex extraction options| and |tex extraction + % script|. + \immediate\write\mmz@shout{\mmz@tex@extraction@systemcall}% + \endgroup + }, + record/sh/end/.code={% + \immediate\closeout\mmz@shout + }, +% \end{key} +% +% \begin{key}{record/bat/...} +% Rinse and repeat for Windows. + record/bat/begin/.code={% + \newwrite\mmz@batout + \immediate\openout\mmz@batout{\mmz@batname}% + }, + record/bat/new extern/.code={% + \begingroup + \immediate\write\mmz@batout{\mmz@tex@extraction@systemcall}% + \endgroup + }, + record/bat/end/.code={% + \immediate\closeout\mmz@batout + }, +% \end{key} +% +% \subsubsection{The Makefile} +% +% The implementation of the Makefile record type is the most complex so far, as +% we need to keep track of the targets. +% +% \begin{key}{makefile} +% This key sets the makefile filename. + makefile/.store in=\mmz@makefilename, + makefile=memoize-extract.\jobname.makefile, +} +% \end{key} +% +% We need to define a macro which expands to the tab character of catcode +% ``other'', to use as the recipe prefix. +\begingroup +\catcode`\^^I=12 +\gdef\mmz@makefile@recipe@prefix{^^I}% +\endgroup +% \begin{key}{record/makefile/...} +% Define the Makefile record type. +\mmzset{ + record/makefile/begin/.code={% + % We initialize the record type by opening the file and setting makefile + % variables |.DEFAULT_GOAL| and |.PHONY|. + \newwrite\mmz@makefileout + \newtoks\mmz@makefile@externs + \immediate\openout\mmz@makefileout{\mmz@makefilename}% + \immediate\write\mmz@makefileout{.DEFAULT_GOAL = externs}% + \immediate\write\mmz@makefileout{.PHONY: externs}% + }, + % The crucial part, writing out the extraction rule. The target comes first, + % then the recipe, which is whatever the user has set by |tex extraction + % command|, |tex extraction options| and |tex extraction script|. + record/makefile/new extern/.code={% + % The target extern file: + \immediate\write\mmz@makefileout{#1:}% + \begingroup + % The recipe is whatever the user set by |tex extraction + % command|, |tex extraction options| and |tex extraction script|. + \immediate\write\mmz@makefileout{% + \mmz@makefile@recipe@prefix\mmz@tex@extraction@systemcall}% + \endgroup + % Append the extern file to list of targets. + \xtoksapp\mmz@makefile@externs{#1\space}% + }, + record/makefile/end/.code={% + % Before closing the file, we list the extern files as the prerequisites of + % our phony default target, |externs|. + \immediate\write\mmz@makefileout{externs: \the\mmz@makefile@externs}% + \immediate\closeout\mmz@makefileout + }, +} +% \end{key} +% +% +% \subsection{\hologo{TeX}-based extraction} +% \label{sec:code:extract:tex} +% +% \begin{key}{extract/tex} +% We trigger the \hologo{TeX}-based extraction by inputting the |.mmz| record +% file. +\mmzset{ + extract/tex/.code={% + \begingroup + \@input{\jobname.mmz}% + \endgroup + }, +} +% \end{key} +% +% \begingroup +% \setlength\MacroIndent{2em} +% \begin{macro}{\mmzUsedCMemo,\mmzUsedCCMemo,\mmzUsedExtern,\mmzNewCMemo,\mmzNewCCMemo,\mmzPrefix} +% We can ignore everything but |\mmzNewExtern|s. All these macros receive a +% single argument. +\def\mmzUsedCMemo#1{} +\def\mmzUsedCCMemo#1{} +\def\mmzUsedExtern#1{} +\def\mmzNewCMemo#1{} +\def\mmzNewCCMemo#1{} +\def\mmzPrefix#1{} +% \end{macro} +% \endgroup +% +% \begin{macro}{\mmzNewExtern} +% Command |\mmzNewExtern| takes four arguments. It instructs us to extract +% page |#2| of document |\jobname.pdf| to file |#1|. During the extraction, +% we will check whether the size of the extern matches the given expected +% width (|#3|) and total height (|#4|). +% +% We perform the extraction by an embedded \hologo{TeX} call. The system +% command that gets executed is stored in |\mmz@tex@extraction@systemcall|, +% which is set by |tex extraction command| and friends; by default, we +% execute |pdftex|. +% +\def\mmzNewExtern#1{% + % The \hologo{TeX} executable expects the basename as the argument, so we + % strip away the |.pdf| suffix. + \mmz@new@extern@i#1\mmz@temp +} +\def\mmz@new@extern@i#1.pdf\mmz@temp#2#3#4{% + \begingroup + % Define the macros used in |\mmz@tex@extraction@systemcall|. + \def\externbasepath{#1}% + \def\pagenumber{#2}% + \def\expectedwidth{#3}% + \def\expectedheight{#4}% + % Empty out the extraction log. + \mmz@clear@extraction@log + % Extract. + \pdf@system{\mmz@tex@extraction@systemcall}% + % Was the extraction successful? We temporarily redefine the extraction + % error message macro (suited for the external extraction scripts, which + % extract all externs in one go) to report the exact problematic extern page. + \let\mmz@extraction@error\mmz@pageextraction@error + \mmz@check@extraction@log{tex}% + \endgroup +} +% \end{macro} +\def\mmz@pageextraction@error{% + \PackageError{memoize}{Extraction of extern page \pagenumber\space from + document "\mmzUnquote\jobname.pdf" using method "\extractionmethod" was unsuccessful. + Have you set the shell escape mode as suggested in chapter 1 of the + manual?}{If "\mmzvalueof{tex extraction command}" was executed, + shell escape mode is not the problem, and inspecting "\externbasepath.log" + might give you a clue what's wrong}} +% +% \begin{key}{tex extraction command, tex extraction options, tex extraction script} +% Using these keys, we set the system call +% which will be invoked for each extern page. The value of this key is +% expanded when executing the system command. The user may deploy the +% following macros in the value of these keys: +% \begin{itemize} +% \item |\externbasepath|: the extern PDF that should be produced, minus the +% |.pdf| suffix; +% \item |\pagenumber|: the page number to be extracted; +% \item |\expectedwidth|: the expected width of the extracted page; +% \item |\expectedheight|: the expected total height of the extracted page; +% \end{itemize} +\def\mmz@tex@extraction@systemcall{% + \mmzvalueof{tex extraction command}\space + \mmzvalueof{tex extraction options}\space + "\mmzvalueof{tex extraction script}"% +} +% \end{key} +% +% \paragraph{The default} system call for \hologo{TeX}-based extern extraction. +% As this method, despite being \hologo{TeX}-based, shares no code with the +% document, we're free to implement it with any engine and format we want. For +% reasons of speed, we clearly go for the plain \hologo{pdfTeX}.\footnote{I +% implemented the first version of \hologo{TeX}-based extraction using +% \hologo{LaTeX} and package \texttt{graphicx}, and it was (running with +% \hologo{pdfTeX} engine) almost four times slower than the current plain +% \hologo{TeX} implementation.} We perform the extraction by a little +% \hologo{TeX} script, |memoize-extract-one|, inputted at the end of the value +% given to |tex extraction script|. +\mmzset{ + tex extraction command/.initial=pdftex, + tex extraction options/.initial={% +% \begin{listingregion}{tex-extraction-options.tex} + -halt-on-error + -interaction=batchmode + -jobname "\externbasepath" + \ifdefempty\mmzOutputDirectory{}{-output-directory "\mmzOutputDirectory"} +% \end{listingregion} + }, + tex extraction script/.initial={% +% \begin{listingregion}{tex-extraction-script.tex} ^^A todo: context + \def\noexpand\fromdocument{"\mmzOutputDirectory"\jobname.pdf}% + \def\noexpand\pagenumber{\pagenumber}% + \def\noexpand\expectedwidth{\expectedwidth}% + \def\noexpand\expectedheight{\expectedheight}% + \def\noexpand\logfile{\jobname.mmz.log}% + \unexpanded{% + \def\warningtemplate{% + %<latex>\noexpand\PackageWarning{memoize}{\warningtext}% + %<plain>\warning{memoize: \warningtext}% + %<context>\warning{memoize: \warningtext}% + }}% + \ifdef\XeTeXversion{}{% + \def\noexpand\mmzpdfmajorversion{\the\pdfmajorversion}% + \def\noexpand\mmzpdfminorversion{\the\pdfminorversion}% + }% + \noexpand\input memoize-extract-one +% \end{listingregion} + }, +} +%</mmz> +% +% \subsubsection{\texttt{memoize-extract-one.tex}} +% +% The rest of the code of this section resides in file +% |memoize-extract-one.tex|. It is used to extract a single extern page from +% the document; it also checks whether the extern page dimensions are as +% expected, and passes a warning to the main job if that is not the case. For +% the reason of speed, the extraction script is in \hologo{plainTeX} format. +% For the same reason, it is compiled by \hologo{pdfTeX} engine by default, but +% we nevertheless take care that it will work with other (supported) engines as +% well. +% +%<*extract-one> +\catcode`\@11\relax +\def\@firstoftwo#1#2{#1} +\def\@secondoftwo#1#2{#2} +% +% Set the PDF version (maybe) passed to the script via |\mmzpdfmajorversion| +% and |\mmzpdfminorversion|. +\ifdefined\XeTeXversion +\else + \ifdefined\luatexversion + \def\pdfmajorversion{\pdfvariable majorversion}% + \def\pdfminorversion{\pdfvariable minorversion}% + \fi + \ifdefined\mmzpdfmajorversion + \pdfmajorversion\mmzpdfmajorversion\relax + \fi + \ifdefined\mmzpdfminorversion + \pdfminorversion\mmzpdfminorversion\relax + \fi +\fi +% Allocate a new output stream, always --- |\newwrite| is |\outer| and thus +% cannot appear in a conditional. +\newwrite\extractionlog +% Are we requested to produce a log file? +\ifdefined\logfile + \immediate\openout\extractionlog{\logfile}% + % Define a macro which both outputs the warning message and writes it to the + % extraction log. + \def\doublewarning#1{% + \message{#1}% + \def\warningtext{#1}% + % This script will be called from different formats, so it is up to the + % main job to tell us, by defining macro |\warningtemplate|, how to throw a + % warning in the log file. + \immediate\write\extractionlog{% + \ifdefined\warningtemplate\warningtemplate\else\warningtext\fi + }% + }% +\else + \let\doublewarning\message +\fi +\newif\ifforce +\ifdefined\force + \csname force\force\endcsname +\fi +% \begin{macro}{\mmz@if@roughly@equal} +% This macro checks whether the given dimensions (|#2| and |#3|) are equal +% within the tolerance given by |#1|. We use the macro both in the +% extraction script and in the main package. (We don't use |\ifpdfabsdim|, +% because it is unavailable in \hologo{XeTeX}.) +%</extract-one> +%<*mmz,extract-one> +\def\mmz@tolerance{0.01pt} +\def\mmz@if@roughly@equal#1#2#3{% + \dimen0=\dimexpr#2-#3\relax + \ifdim\dimen0<0pt + \dimen0=-\dimen0\relax + \fi + \ifdim\dimen0>#1\relax + \expandafter\@secondoftwo + \else + % The exact tolerated difference is, well, tolerated. This is a must to + % support |tolerance=0pt|. + \expandafter\@firstoftwo + \fi +}% +%</mmz,extract-one> +%<*extract-one> +% Grab the extern page from the document and put it in a box. +\ifdefined\XeTeXversion + \setbox0=\hbox{\XeTeXpdffile \fromdocument\space page \pagenumber media}% +\else + \ifdefined\luatexversion + \saveimageresource page \pagenumber mediabox {\fromdocument}% + \setbox0=\hbox{\useimageresource\lastsavedimageresourceindex}% + \else + \pdfximage page \pagenumber mediabox {\fromdocument}% + \setbox0=\hbox{\pdfrefximage\pdflastximage}% + \fi +\fi +% Check whether the extern page is of the expected size. +\newif\ifbaddimensions +\ifdefined\expectedwidth + \ifdefined\expectedheight + \mmz@if@roughly@equal{\mmz@tolerance}{\wd0}{\expectedwidth}{% + \mmz@if@roughly@equal{\mmz@tolerance}{\ht0}{\expectedheight}% + {}% + {\baddimensionstrue}% + }{\baddimensionstrue}% + \fi +\fi +% We'll setup the page geometry of the extern file and shipout the extern --- +% if all is well, or we're forced to do it. +\ifdefined\luatexversion + \let\pdfpagewidth\pagewidth + \let\pdfpageheight\pageheight + \def\pdfhorigin{\pdfvariable horigin}% + \def\pdfvorigin{\pdfvariable vorigin}% +\fi +\def\do@shipout{% + \pdfpagewidth=\wd0 + \pdfpageheight=\ht0 + \ifdefined\XeTeXversion + \hoffset -1 true in + \voffset -1 true in + \else + \pdfhorigin=0pt + \pdfvorigin=0pt + \fi + \shipout\box0 +} +\ifbaddimensions + \doublewarning{I refuse to extract page \pagenumber\space from + "\fromdocument", because its size (\the\wd0 \space x \the\ht0) is not + what I expected (\expectedwidth\space x \expectedheight)}% + \ifforce\do@shipout\fi +\else + \do@shipout +\fi +% If logging is in effect and the extern dimensions were not what we expected, +% write a warning into the log. +\ifdefined\logfile + \immediate\write\extractionlog{\noexpand\endinput}% + \immediate\closeout\extractionlog +\fi +\bye +%</extract-one> +% \end{macro} +% +% \section{Automemoization} +% \label{sec:code:automemoization} +% +% \paragraph{Install} the advising framework implemented by our auxiliary +% package Advice, which automemoization depends on. This will define keys +% |auto|, |activate| etc.\ in our keypath. +%<*mmz> +\mmzset{ + .install advice={setup key=auto, activation=deferred}, + % We switch to the immediate activation at the end of the preamble. + begindocument/before/.append style={activation=immediate}, +} +% +% \begin{key}{manual} +% Unless the user switched on |manual|, we perform the deferred +% (de)activations at the beginning of the document (and then clear the style, +% so that any further deferred activations will start with a clean slate). +% In \hologo{LaTeX}, we will use the latest possible hook, +% |begindocument/end|, as we want to hack into commands defined by other +% packages. (The \hologo{TeX} conditional needs to be defined before using +% it in |.append code| below. +\newif\ifmmz@manual +\mmzset{ + manual/.is if=mmz@manual, + begindocument/end/.append code={% + \ifmmz@manual + \else + \pgfkeysalso{activate deferred,activate deferred/.code={}}% + \fi + }, +% \end{key} +% +% \paragraph{Announce} Memoize's run conditions and handlers. + auto/.cd, + run if memoization is possible/.style={ + run conditions=\mmz@auto@rc@if@memoization@possible + }, + run if memoizing/.style={run conditions=\mmz@auto@rc@if@memoizing}, + apply options/.style={ + bailout handler=\mmz@auto@bailout, + outer handler=\mmz@auto@outer, + }, + memoize/.style={ + run if memoization is possible, + apply options, + inner handler=\mmz@auto@memoize + }, + %<*latex> + noop/.style={run if memoization is possible, noop \AdviceType}, + noop command/.style={apply options, inner handler=\mmz@auto@noop}, + noop environment/.style={ + outer handler=\mmz@auto@noop@env, bailout handler=\mmz@auto@bailout}, + %</latex> + %<plain,context>noop/.style={inner handler=\mmz@auto@noop}, + nomemoize/.style={noop, options=disable}, + replicate/.style={run if memoizing, inner handler=\mmz@auto@replicate}, +} +% +% \paragraph{Abortion} +% We cheat and let the |run conditions| do the work --- it is cheaper to just +% always abort than to invoke the outer handler. (As we don't set +% |\AdviceRuntrue|, the run conditions will never be satisfied.) +% \begin{listingregion}{_auto-abort.tex} +\mmzset{ + auto/abort/.style={run conditions=\mmzAbort}, +} +% \end{listingregion} +% And the same for |unmemoizable|: +\mmzset{ + auto/unmemoizable/.style={run conditions=\mmzUnmemoizable}, +} +% For one, we abort upon |\pdfsavepos| (called |\savepos| in \hologo{LuaTeX}). +% Second, unless in \hologo{LuaTeX}, we submit |\errmessage|, which allows us +% to detect at least some errors --- in \hologo{LuaTeX}, we have a more +% bullet-proof system of detecting errors, see |\mmz@memoize| in +% \S\ref{sec:code:memoization-process}. +\ifdef\luatexversion{% + \mmzset{auto=\savepos{abort}} +}{% + \mmzset{ + auto=\pdfsavepos{abort}, + auto=\errmessage{abort}, + } +} +% +% \begin{mmzautokey}{run if memoization is possible} +% \begin{macro}{\mmz@auto@rc@if@memoization@possible} +% These run conditions are used by |memoize| and |noop|: Memoize should be +% enabled, but we should not be already within Memoize, i.e.\ memoizing or +% normally compiling some code submitted to memoization. +% \begin{listingregion}{_auto-run-if-memoization-is-possible.tex} +\def\mmz@auto@rc@if@memoization@possible{% + \ifmemoize + \ifinmemoize + \else + \AdviceRuntrue + \fi + \fi +} +% \end{listingregion} +% \end{macro} +% \end{mmzautokey} +% +% \begin{mmzautokey}{run if memoizing} +% \begin{macro}{\mmz@auto@rc@if@memoizing} +% These run conditions are used by |\label| and |\ref|: they should +% be handled only during memoization (which implies that Memoize is enabled). +\def\mmz@auto@rc@if@memoizing{% + \ifmemoizing\AdviceRuntrue\fi +} +% \end{macro} +% \end{mmzautokey} +% +% \begin{macro}{\mmznext} +% The next-options, set by this macro, will be applied to the next, and only +% next instance of automemoization. We set the next-options globally, so +% that only the linear order of the invocation matters. Note that |\mmznext|, +% being a user command, must also be defined in package |nomemoize|. +%</mmz> +%<nommz>\def\mmznext#1{\ignorespaces} +%<*mmz> +\def\mmznext#1{\gdef\mmz@next{#1}\ignorespaces} +\mmznext{}% +% \end{macro} +% +% \begin{mmzautokey}{apply options} +% \begin{macro}{\mmz@auto@outer,\mmz@auto@bailout} +% The outer and the bailout handler defined here work as a team. The outer +% handler's job is to apply the auto- and the next-options; therefore, the +% bailout handler must consume the next-options as well. To keep the +% option application local, the outer handler opens a group, which is +% expected to be closed by the inner handler. This key is used by +% |memoize| and |noop command|. +% \begin{listingregion}{_auto-memoize-outer.tex} +\def\mmz@auto@outer{% + \begingroup + \mmzAutoInit + \AdviceCollector +} +% \end{listingregion} +% \begin{listingregion}{_auto-memoize-bailout.tex} +\def\mmz@auto@bailout{% + \mmznext{}% +} +% \end{listingregion} +% \bigskip +% \end{macro} +% \end{mmzautokey} +% +% \begin{macro}{\mmzAutoInit} +% Apply first the auto-options, and then the next-options (and clear the +% latter). Finally, if we have any extra collector options (set by the +% |verbatim| keys), append them to Advice's (raw) collector options. +\def\mmzAutoInit{% + \ifdefempty\AdviceOptions{}{\expandafter\mmzset\expandafter{\AdviceOptions}}% + \ifdefempty\mmz@next{}{\expandafter\mmzset\expandafter{\mmz@next}\mmznext{}}% + \eappto\AdviceRawCollectorOptions{\expandonce\mmzRawCollectorOptions}% +} +% \end{macro} +% +% \begin{mmzautokey}{memoize} +% \begin{macro}{\mmz@auto@memoize} +% This key installs the inner handler for memoization. If you compare this +% handler to the definition of |\mmz| in +% section~\ref{sec:code:memoization:manual}, you will see that the only +% thing left to do here is to start memoization with |\Memoize|, everything +% else is already done by the advising framework, as customized by Memoize. +% +% The first argument to |\Memoize| is the memoization key (which the code +% md5sum is computed off of); it consists of the handled code (the contents +% of |\AdviceReplaced|) and its arguments, which were collected into |##1|. +% The second argument is the code which the memoization driver will +% execute. |\AdviceOriginal|, if invoked right away, would execute the +% original command; but as this macro is only guaranteed to refer to this +% command within the advice handlers, we expand it before calling |\Memoize|. +% that command. +% +% Note that we don't have to define different handlers for commands and +% environments, and for different \hologo{TeX} formats. When memoizing +% command |\foo|, |\AdviceReplaced| contains |\foo|. When memoizing +% environment |foo|, |\AdviceReplaced| contains |\begin{foo}|, |\foo| or +% |\startfoo|, depending on the format, while the closing tag +% (|\end{foo}|, |\endfoo| or |\stopfoo|) occurs at the end of the +% collected arguments, because |apply options| appended +% |\collargsEndTagtrue| to |raw collector options|. +% +% This macro has no formal parameters, because the collected arguments will +% be grabbed by |\mmz@marshal|, which we have to go through because +% executing |\Memoize| closes the memoization group and we lose the current +% value of |\ifmmz@ignorespaces|. (We also can't use |\aftergroup|, +% because closing the group is not the final thing |\Memoize| does.) +% \begin{listingregion}{_auto-memoize-inner.tex} +\long\def\mmz@auto@memoize#1{% + \expanded{% + \noexpand\Memoize + {\expandonce\AdviceReplaced\unexpanded{#1}}% + {\expandonce\AdviceOriginal\unexpanded{#1}}% + \ifmmz@ignorespaces\ignorespaces\fi + }% +} +% \end{listingregion} +% \end{macro} +% \end{mmzautokey} +% +% \begin{mmzautokey}{noop} +% \begin{macro}{\mmz@auto@noop,\mmz@auto@noop@env} +% The no-operation handler can be used to apply certain options for the +% span of the execution of the handled command or environment. This is +% exploited by |auto/nomemoize|, which sets |disable| as an auto-option. +% +% The handler for commands and non-\hologo{LaTeX} environments is +% implemented as an inner handler. On its own, it does nothing except +% honor |verbatim| and |ignore spaces| (only takes care of |verbatim| and +% |ignore spaces| (in the same way as the memoization handler above), but +% it is intended to be used alongside the default outer handler, which +% applies the auto- and the next-options. As that handler opens a group +% (and this handler closes it), we have effectively delimited the effect of +% those options to this invocation of the handled command or environment. +\long\def\mmz@auto@noop#1{% + \expandafter\mmz@maybe@scantokens\expandafter{\AdviceOriginal#1}% + \expandafter\endgroup + \ifmmz@ignorespaces\ignorespaces\fi +} +% In \hologo{LaTeX}, and only there, commands and environments need separate +% treatment. As \hologo{LaTeX} environments introduce a group of their own, we +% can simply hook our initialization into the beginning of the environment (as +% a one-time hook). Consequently, we don't need to collect the environment +% body, so this can be an outer handler. +%<*latex> +\def\mmz@auto@noop@env{% + \AddToHookNext{env/\AdviceName/begin}{% + \mmzAutoInit + \ifmmz@ignorespaces\ignorespacesafterend\fi + }% + \AdviceOriginal +} +%</latex> +% \end{macro} +% \end{mmzautokey} +% +% \begin{mmzautokey}{replicate} +% \begin{macro}{\mmz@auto@replicate} +% This inner handler writes a copy of the handled command or environment's +% invocation into the cc-memo (and then executes it). As it is used +% alongside |run if memoizing|, the replicated command in the cc-memo will +% always execute the original command. The system works even if +% replication is off when the cc-memo is input; in that case, the control +% sequence in the cc-memo directly executes the original command. +% +% This handler takes an option, |expanded| --- if given, the collected +% arguments will be expanded (under protection) before being written into +% the cc-memo. +\def\mmz@auto@replicate#1{% + \begingroup + \let\mmz@auto@replicate@expansion\unexpanded + \expandafter\pgfqkeys\expanded{{/mmz/auto/replicate}{\AdviceOptions}}% + %<latex>\let\protect\noexpand + \expanded{% + \endgroup + \noexpand\gtoksapp\noexpand\mmzCCMemo{% + \expandonce\AdviceReplaced\mmz@auto@replicate@expansion{#1}}% + \expandonce\AdviceOriginal\unexpanded{#1}% + }% +} +\pgfqkeys{/mmz/auto/replicate}{ + expanded/.code={\let\mmz@auto@replicate@expansion\@firstofone}, +} +% \end{macro} +% \end{mmzautokey} +% +% \subsection{\hologo{LaTeX}-specific handlers} +% +% We handle cross-referencing (both the |\label| and the |\ref| side) and +% indexing. Note that the latter is a straightforward instance of replication. +% +%<*latex> +% \begin{listingregion}{_auto-memoize-ref.tex} +\mmzset{ + auto/.cd, + ref/.style={outer handler=\mmz@auto@ref\mmzNoRef, run if memoizing}, + force ref/.style={outer handler=\mmz@auto@ref\mmzForceNoRef, run if memoizing}, +} +% \end{listingregion} +\mmzset{ + auto=\ref{ref}, + auto=\pageref{ref}, + auto=\label{run if memoizing, outer handler=\mmz@auto@label}, + auto=\index{replicate, args=m, expanded}, +} +% +% \begin{mmzautokey}{ref, force ref} +% \begin{macro}{\mmz@auto@ref} +% These keys install an outer handler which appends a cross-reference to the +% context. |force ref| does this even if the reference key is undefined, +% while |ref| aborts memoization in such a case --- the idea is that it makes +% no sense to memoize when we expect the context to change in the next +% compilation anyway. +% +% Any command taking a mandatory braced reference key argument potentially +% preceded by optional arguments of (almost) any kind may be submitted to +% these keys. This follows from the parameter list of |\mmz@auto@ref@i|, +% where |#2| grabs everything up to the first opening brace. The downside of +% the flexibility regarding the optional arguments is that unbraced +% single-token reference keys will cause an error, but as such usages of +% |\ref| and friends should be virtually inexistent, we let the bug stay. +% +% |#1| should be either |\mmzNoRef| or |\mmzForceNoRef|. |#2| will receive +% any optional arguments of |\ref| (or |\pageref|, or whatever), and |#3| in +% |\mmz@auto@ref@i| is the cross-reference key. +% \begin{listingregion}{_auto-memoize-ref.tex} +\def\mmz@auto@ref#1#2#{\mmz@auto@ref@i#1{#2}} +\def\mmz@auto@ref@i#1#2#3{% + #1{#3}% + \AdviceOriginal#2{#3}% +} +% \end{listingregion} +% \end{macro} +% \end{mmzautokey} +% +% \begin{macro}{\mmzForceNoRef,\mmzNoRef} +% These macros do the real job in the outer handlers for cross-referencing, +% but it might be useful to have them publicly available. |\mmzForceNoRef| +% appends the reference key to the context. |\mmzNoRef| only does that if +% the reference is defined, otherwise it aborts the memoization. +\def\mmzForceNoRef#1{% + \ifmemoizing + \expandafter\gtoksapp\expandafter\mmzContextExtra + \else + \expandafter\toksapp\expandafter\mmzContext + \fi + {r@#1={\csname r@#1\endcsname}}% + \ignorespaces +} +\def\mmzNoRef#1{% + \ifcsundef{r@#1}{\mmzAbort}{\mmzForceNoRef{#1}}% + \ignorespaces +} +% \end{macro} +% +% \begin{mmzautokey}{refrange, force refrange} +% \begin{macro}{\mmz@auto@refrange} +% Let's rinse and repeat for reference ranges. The code is virtually the +% same as above, but we grab two reference key arguments (|#3| and |#4|) in +% the final macro.\indentmacrocode +\mmzset{ + auto/.cd, + refrange/.style={outer handler=\mmz@auto@refrange\mmzNoRef, + bailout handler=\relax, run if memoizing}, + force refrange/.style={outer handler=\mmz@auto@refrange\mmzForceNoRef, + bailout handler=\relax, run if memoizing}, +} +% \noindentmacrocode +\def\mmz@auto@refrange#1#2#{\mmz@auto@refrange@i#1{#2}} +\def\mmz@auto@refrange@i#1#2#3#4{% + #1{#3}% + #1{#4}% + \AdviceOriginal#2{#3}{#4}% +} +% \end{macro} +% \end{mmzautokey} +% +% \begin{mmzautokey}{multiref, force multiref} +% \begin{macro}{\mmz@auto@multiref} +% And one final time, for ``multi-references'', such as |cleveref|'s +% |\cref|, which can take a comma-separated list of reference keys in the +% sole argument. Again, only the final macro is any different, this time +% distributing |#1| (|\mmzNoRef| or |\mmzForceNoRef|) over |#3| by +% |\forcsvlist|. +\mmzset{ + auto/.cd, + multiref/.style={outer handler=\mmz@auto@multiref\mmzNoRef, + bailout handler=\relax, run if memoizing}, + force multiref/.style={outer handler=\mmz@auto@multiref\mmzForceNoRef, + bailout handler=\relax, run if memoizing}, +} +\def\mmz@auto@multiref#1#2#{\mmz@auto@multiref@i#1{#2}} +\def\mmz@auto@multiref@i#1#2#3{% + \forcsvlist{#1}{#3}% + \AdviceOriginal#2{#3}% +} +% \end{macro} +% \end{mmzautokey} +% +% \begin{macro}{\mmz@auto@label} +% The outer handler for \cs{label} must be defined specifically for this +% command. The generic replicating handler is not enough here, as we need to +% replicate both the invocation of |\label| and the definition of +% |\@currentlabel|. +\def\mmz@auto@label#1{% + \xtoksapp\mmzCCMemo{% + \noexpand\mmzLabel{#1}{\expandonce\@currentlabel}% + }% + \AdviceOriginal{#1}% +} +% \end{macro} +% +% \begin{macro}{\mmzLabel} +% This is the macro that |\label|'s handler writes into the cc-memo. The +% first argument is the reference key; the second argument is the value of +% |\@currentlabel| at the time of invocation |\label| during memoization, +% which this macro temporarily restores. +\def\mmzLabel#1#2{% + \begingroup + \def\@currentlabel{#2}% + \label{#1}% + \endgroup +} +%</latex> +%</mmz> +% +% \end{macro} +% +% \section{Support for various classes and packages} +% +% \subsection{\TikZ;} +% \label{sec:code:mmz:tikz} +% +% In this section, we activate \TikZ; support (the collector is defined by +% Advice). All the action happens at the end of the preamble, so that we can +% detect whether \TikZ; was loaded (regardless of whether Memoize was loaded +% before \TikZ;, or vice versa), but still input the definitions. +%<*mmz> +\mmzset{ + begindocument/before/.append code={% + %<latex>\@ifpackageloaded{tikz}{% + %<plain,context>\ifdefined\tikz + \input advice-tikz.code.tex + %<latex>}{}% + %<plain,context>\fi + % We define and activate the automemoization handlers for the \TikZ; command + % and environment. + \mmzset{% + % \begin{listingregion}{_auto-tikz-collector.tex} + auto=\tikz{memoize, collector=\AdviceCollectTikZArguments}, + % \end{listingregion} + %/utils/exec={\tracingall}, + auto={tikzpicture}{memoize}, + % A hack to prevent memoizing pictures which are accidentally marked as + % remembered --- accidentally in the sense that because the document + % changed, the |.aux| file contains a |\pgfsyspdfmark| command which + % erroneously refers to the picture being memoized. We know that + % memoizing a remembered picture can't be right, as we always abort on + % |\pdfsavepos|. This is implemented by hacking into PGF's + % |\pgfsys@getposition|, and aborting memoization if the mark does not + % equal |\relax|. (We have to duplicate |#| because of |.append code|.) + auto=\pgfsys@getposition{ + run if memoizing, outer handler=\mmz@pgfsys@getposition}, + }% + \def\mmz@pgfsys@getposition##1{% + \expandafter\ifx\csname pgf@sys@pdf@mark@pos@##1\endcsname\relax + \else + \mmzAbort + \fi + \AdviceOriginal{##1}% + }% + }, +} +%</mmz> +% +% +% \subsection{Forest} +% \label{sec:code:forest} +% +% Forest will soon feature extensive memoization support, but for now, let's +% just enable the basic, single extern externalization. +%<*mmz> +%<*latex> +\mmzset{ + begindocument/before/.append code={% + \@ifpackageloaded{forest}{% + \mmzset{ + auto={forest}{memoize}, +% Yes, |\Forest| is defined using |xparse|. + auto=\Forest{memoize}, + }% + }{}% + }, +} +%</latex> +% +% \subsection{Beamer} +% \label{sec:code:beamer} +% +% The Beamer code is explained in \MS\ref{sec:per-overlay}. +% +%<*latex> +\AddToHook{begindocument/before}{\@ifclassloaded{beamer}{% + % \begin{listingregion}{per-overlay.tex} + \mmzset{per overlay/.style={ + /mmz/context={% + overlay=\csname beamer@overlaynumber\endcsname, + pauses=\ifmemoizing + \mmzBeamerPauses + \else + \expandafter\the\csname c@beamerpauses\endcsname + \fi + }, + /mmz/at begin memoization={% + \xdef\mmzBeamerPauses{\the\c@beamerpauses}% + \xtoksapp\mmzCMemo{% + \noexpand\mmzSetBeamerOverlays{\mmzBeamerPauses}{\beamer@overlaynumber}}% + \gtoksapp\mmzCCMemo{% + \only<\mmzBeamerOverlays>{}}% + }, + /mmz/at end memoization={% + \xtoksapp\mmzCCMemo{% + \noexpand\setcounter{beamerpauses}{\the\c@beamerpauses}}% + }, + /mmz/per overlay/.code={}, + }} + \def\mmzSetBeamerOverlays#1#2{% + \ifnum\c@beamerpauses=#1\relax + \gdef\mmzBeamerOverlays{#2}% + \ifnum\beamer@overlaynumber<#2\relax \mmz@temptrue \else \mmz@tempfalse \fi + \else + \mmz@temptrue + \fi + \ifmmz@temp + \appto\mmzAtBeginMemoization{% + \gtoksapp\mmzCMemo{\mmzSetBeamerOverlays{#1}{#2}}}% + \fi + }% + % \end{listingregion} +}{}} +%</latex> +% +% +% \section{Initialization} +% \label{sec:code:initialization} +% +% \begin{key}{begindocument/before, begindocument, begindocument/end, +% enddocument/afterlastpage} +% These styles contain the initialization +% and the finalization code. They were populated throughout the source. +% Hook |begindocument/before| contains the package support code, which must +% be loaded while still in the preamble. Hook |begindocument| contains the +% initialization code whose execution doesn't require any particular timing, +% as long as it happens at the beginning of the document. Hook +% |begindocument/end| is where the commands are activated; this must +% crucially happen as late as possible, so that we successfully override +% foreign commands (like |hyperref|'s definitions). In \hologo{LaTeX}, we +% can automatically execute these hooks at appropriate places: +%<*latex> +\AddToHook{begindocument/before}{\mmzset{begindocument/before}} +\AddToHook{begindocument}{\mmzset{begindocument}} +\AddToHook{begindocument/end}{\mmzset{begindocument/end}} +\AddToHook{enddocument/afterlastpage}{\mmzset{enddocument/afterlastpage}} +%</latex> +% In \hologo{plainTeX}, the user must execute these hooks manually; but at +% least we can group them together and given them nice names. Provisionally, +% manual execution is required in \hologo{ConTeXt} as well, as I'm not sure +% where to execute them --- please help! +%<*plain,context> +\mmzset{ + begin document/.style={begindocument/before, begindocument, begindocument/end}, + end document/.style={enddocument/afterlastpage}, +} +%</plain,context> +% \end{key} +% +% \paragraph{\texttt{memoize.cfg}} Load the configuration file. Note that +% |nomemoize| must input this file as well, because any special +% memoization-related macros defined by the user should be available; for +% example, my |memoize.cfg| defines |\ifregion| (see +% \MS\ref{sec:tut:multifile}). +%</mmz> +%<mmz,nommz>\InputIfFileExists{memoize.cfg}{}{} +%<*mmz> +% +% Formats other than \hologo{plainTeX} need a way to prevent extraction during +% package-loading. +\mmzset{ + %<!plain>extract/no/.code={}, +% \begin{key}{output directory} +% \begin{macro}{mmzOutputDirectory} +% Set the |-output-directory| --- manually, as there is no other way. +% \indentmacrocode + output-directory/.store in=\mmzOutputDirectory, +} +% \end{macro} +% \end{key} +% +% \paragraph{Process} the package options (except in \hologo{plainTeX}). +%<latex>\ProcessPgfPackageOptions{/mmz} +%<context>\expandafter\mmzset\expandafter{\currentmoduleparameters} +% +% Define |\mmzOutputDirectory| if |output-directory| was not given. +\ifdefined\mmzOutputDirectory +\else + \def\mmzOutputDirectory{}% +\fi +\mmzset{output directory/.code={\PackageError{memoize}{Key "output-directory" + may only be used as a package option}{}}} +\mmzset{ + % \paragraph{Extract} the externs using the method given by |memoize.cfg| or + % the package options --- unless we're running \hologo{plainTeX}. + %<*!plain> + extract/\mmz@extraction@method, + % In non-\hologo{plainTeX} formats, also disable |extract| in the preamble. + extract/.code={\PackageError{memoize}{Key "extract" is only allowed as a + package option.}{If you really want to extract in the preamble, execute + "extract/<method>".}}, + %</!plain> + %<*plain> + % In \hologo{plainTeX}, where extraction must be invoked after + % loading the package, we now have to redefine |extract|, so that it will + % immediately trigger extraction. + extract/.is choice, + extract/.default=\mmz@extraction@method, + % But only once: + extract/.append style={ + extract/.code={\PackageError{memoize}{Key "extract" is only allowed to + be used once.}{If you really want to extract again, execute + "extract/<method>".}}, + }, + %</plain> +} +% +% Memoize was not really born for the draft mode, as it cannot produce new +% externs there. But we don't want to disable the package, as utilization is +% still perfectly valid in this mode, so let's just warn the user. +\ifnum\pdf@draftmode=1 + \PackageWarning{memoize}{No memoization will be performed in the draft mode}% +\fi +%</mmz> +% +% \paragraph{The end} of |memoize|, |nomemoize| and |memoizable|. +%<*mmz,nommz,mmzable> +%<plain>\resetatcatcode +%<context>\stopmodule +%<context>\protect +%</mmz,nommz,mmzable> +% That's all, folks! +% +% \end{macrocode} +% +% \endinput +% +% Local Variables: +% TeX-engine: luatex +% TeX-master: "doc/memoize-code.tex" +% TeX-auto-save: nil +% End:
\ No newline at end of file diff --git a/macros/generic/memoize/memoize.ins b/macros/generic/memoize/memoize.ins new file mode 100644 index 0000000000..f288e8e555 --- /dev/null +++ b/macros/generic/memoize/memoize.ins @@ -0,0 +1,39 @@ +%% memoize.ins +%% +%% This file is a part of Memoize, a TeX package for externalization of +%% graphics and memoization of compilation results in general, available at +%% https://ctan.org/pkg/memoize and https://github.com/sasozivanovic/memoize. +%% +%% Copyright (c) 2020- Saso Zivanovic <saso.zivanovic@guest.arnes.si> +%% (Sa\v{s}o \v{Z}ivanovi\'{c}) +%% +%% This work may be distributed and/or modified under the conditions of the +%% LaTeX Project Public License, either version 1.3c of this license or (at +%% your option) any later version. The latest version of this license is in +%% https://www.latex-project.org/lppl.txt and version 1.3c or later is part of +%% all distributions of LaTeX version 2008 or later. +%% +%% This work has the LPPL maintenance status `maintained'. +%% The Current Maintainer of this work is Saso Zivanovic. +%% +%% The files belonging to this work and covered by LPPL are listed in +%% (<texmf>/doc/generic/memoize/)FILES. +%% +\input docstrip.tex +\keepsilent +\preamble +\endpreamble +\askforoverwritefalse +\generate{% + \file{memoize.sty}{\from{memoize.dtx}{mmz,latex}}% + \file{memoize.tex}{\from{memoize.dtx}{mmz,plain}}% + \file{t-memoize.tex}{\from{memoize.dtx}{mmz,context}}% + \file{nomemoize.sty}{\from{memoize.dtx}{nommz,latex}}% + \file{nomemoize.tex}{\from{memoize.dtx}{nommz,plain}}% + \file{t-nomemoize.tex}{\from{memoize.dtx}{nommz,context}}% + \file{memoizable.sty}{\from{memoize.dtx}{mmzable,latex}}% + \file{memoizable.tex}{\from{memoize.dtx}{mmzable,plain}}% + \file{t-memoizable.tex}{\from{memoize.dtx}{mmzable,context}}% + \file{memoize-extract-one.tex}{\from{memoize.dtx}{extract-one}}% +} +\endbatchfile |