% 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 %% (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 %% (/doc/generic/memoize/)FILES. % % \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-40}, 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|). Their code is listed in \S\ref{sec:code:scripts}. % % % \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> %\ProvidesPackage{memoize}[2024/04/02 v1.3.0 Fast and flexible externalization] %%D \module[ %%D file=t-memoize.tex, %%D version=1.3.0, %%D title=Memoize, %%D subtitle=Fast and flexible externalization, %%D author=Saso Zivanovic, %%D date=2024-04-02, %%D copyright=Saso Zivanovic, %%D license=LPPL, %%D ] %\writestatus{loading}{ConTeXt User Module / memoize} %\unprotect %\startmodule[memoize] %% Package memoize 2024/04/02 v1.3.0 % %<*mmzable> %\ProvidesPackage{memoizable}[2024/04/02 v1.3.0 A programmer's stub for Memoize] %%D \module[ %%D file=t-memoizable.tex, %%D version=1.3.0, %%D title=Memoizable, %%D subtitle=A programmer's stub for Memoize, %%D author=Saso Zivanovic, %%D date=2024-04-02, %%D copyright=Saso Zivanovic, %%D license=LPPL, %%D ] %\writestatus{loading}{ConTeXt User Module / memoizable} %\unprotect %\startmodule[memoizable] %% Package memoizable 2024/04/02 v1.3.0 % %<*nommz> %\ProvidesPackage{nomemoize}[2024/04/02 v1.3.0 A no-op stub for Memoize] %%D \module[ %%D file=t-nomemoize.tex, %%D version=1.3.0, %%D title=Memoize, %%D subtitle=A no-op stub for Memoize, %%D author=Saso Zivanovic, %%D date=2024-04-02, %%D copyright=Saso Zivanovic, %%D license=LPPL, %%D ] %\writestatus{loading}{ConTeXt User Module / nomemoize} %\unprotect %\startmodule[nomemoize] %% Package nomemoize 2024/04/02 v1.3.0 % % % \paragraph{Required packages} and \hologo{LaTeX}ization of \hologo{plainTeX} % and \hologo{ConTeXt}. %<*(mmz,mmzable,nommz)&(plain,context)> \input miniltx % % 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}}} % % 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} % % I can't really write any code without |etoolbox| \dots %<*mmz> %\RequirePackage{etoolbox} %\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|. %\RequirePackage{pdftexcmds} %\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}")}% } % % 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! % % Load |pgfkeys| in |nomemoize| and |memoizable|. Not necessary in |memoize|, % as it is already loaded by CollArgs. %<*nommz,mmzable> %\RequirePackage{pgfkeys} %\input pgfkeys %\input t-pgfkey % % % Different formats of |memoizable| merely load |memoizable.code.tex|, which % exists so that |memoizable| can be easily loaded by generic code, like a % |tikz| library. %\input memoizable.code.tex % % \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. % %<*mmz> %<*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 % % Our auxiliary package (\MS\ref{sec:ref:collargs}, \S\ref{sec:code:collargs}). % We also need it in |nomemoize|, to collect manual environments. %\RequirePackage{advice} %\input advice %\input t-advice % % % \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. %<*mmz,nommz> \def\ifmmz@loadstatus#1{% \ifnum#1=0\csname mmz@loadstatus\endcsname\relax \expandafter\@firstoftwo \else \expandafter\@secondoftwo \fi } % %<*mmz> \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.}% %\pgfkeys{/memoize/package options/.unknown/.code={}} %\ProcessPgfPackageOptions{/memoize/package options} \endinput }{}% \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 %"\string\usepackage{memoize}" %"\string\input memoize" %"\string\usemodule[memoize]" before the %"\string\usepackage" %"\string\input" %"\string\usemodule" of that package.}% %\pgfkeys{/memoize/package options/.unknown/.code={}} %\ProcessPgfPackageOptions{/memoize/package options} \endinput }{}% \ifmmz@loadstatus{1}{\endinput}{}% \def\mmz@loadstatus{1}% % %<*mmzable&generic> \ifcsname mmz@loadstatus\endcsname\endinput\fi \def\mmz@loadstatus{2}% % %<*nommz> \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 }{}% \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 %"\string\usepackage{nomemoize}" %"\string\input memoize" %"\string\usemodule[memoize]" before the %"\string\usepackage" %"\string\input" %"\string\usemodule" of that package.}% \endinput }{}% \ifmmz@loadstatus{3}{\endinput}{}% \def\mmz@loadstatus{3}% % % %<*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} % %<*nommz,mmzable&generic> \def\mmzset#1{\ignorespaces} % % \end{macro} % % \begin{macro}{\nommzkeys} % Any |/mmz| keys used outside of |\mmzset| must be declared by this macro % for |nomemoize| package to work. %\def\nommzkeys#1{} %<*nommz,mmzable&generic> \def\nommzkeys{\pgfqkeys{/mmz}} \pgfqkeys{/mmz}{.unknown/.code={\pgfkeysdef{\pgfkeyscurrentkey}{}}} % % \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&generic> \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}{options} % Execute the given value as a keylist of Memoize settings. options/.style={#1}, } % \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}{prefix} % Key |prefix| determines the location of memo and extern files % (|\mmz@prefix@dir|) and the first, fixed part of their basename % (|\mmz@prefix@name|). \mmzset{% prefix/.code={\mmz@parse@prefix{#1}}, } % \begin{macro}{\mmz@split@prefix} % This macro stores the detokenized expansion of |#1| into |\mmz@prefix|, % which it then splits into |\mmz@prefix@dir| and |\mmz@prefix@name| at the % final |/|. The slash goes into |\mmz@prefix@dir|. If there is no slash, % |\mmz@prefix@dir| is empty; in particular, it is empty under |no memo dir|. \begingroup \catcode`\/=12 \gdef\mmz@parse@prefix#1{% \edef\mmz@prefix{\detokenize\expandafter{\expanded{#1}}}% \def\mmz@prefix@dir{}% \def\mmz@prefix@name{}% \expandafter\mmz@parse@prefix@i\mmz@prefix/\mmz@eov } \gdef\mmz@parse@prefix@i#1/#2{% \ifx\mmzeov#2% \def\mmz@prefix@name{#1}% \else \appto\mmz@prefix@dir{#1/}% \expandafter\mmz@parse@prefix@i\expandafter#2% \fi } \endgroup % \end{macro} % % Key |prefix| concludes by performing two actions: it creates the given % directory if |mkdir| is in effect, and notes the new prefix in record files % (by eventually executing |record/prefix|, which typically puts a |\mmzPrefix| % line in the |.mmz| file). In the preamble, only the final setting of % |prefix| matters, so this key is only equipped with the action-triggering % code at the beginning of the document. \mmzset{% begindocument/.append style={ prefix/.append code=\mmz@maybe@mkmemodir\mmz@record@prefix, }, % Consequently, the post-prefix-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, } % \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? Initially, we attempt to create this % directory, and we attempt to do this via |memoize-extract.pl --mkdir|. The % roundabout way of setting the initial value of |mkdir command| allows % |extract=python| to change the initial value to |memoize-extract.py % --mkdir| only in the case the user did not modify it. \def\mmz@initial@mkdir@command{\mmzvalueof{perl extraction command} --mkdir} \mmzset{ % This conditional is perhaps a useless leftover from the early versions, but % we let it be. mkdir/.is if=mmz@mkdir, mkdir command/.store in=\mmz@mkdir@command, mkdir command/.expand once=\mmz@initial@mkdir@command, } % The underlying conditional \cs{ifmmz@mkdir} is only ever used in % |\mmz@maybe@mkmemodir| below, which is itself only executed at the end of % |prefix| and in |begindocument|. \newif\ifmmz@mkdir \mmz@mkdirtrue % We only attempt to create the memo directory if |\ifmmz@mkdir| is in effect % and if both |\mmz@mkdir@command| and |\mmz@prefix@dir| are specified (i.e.\ % non-empty). In particular, no attempt to create it will be made when |no % memo dir| is in effect. \def\mmz@maybe@mkmemodir{% \ifmmz@mkdir \ifdefempty\mmz@mkdir@command{}{% \ifdefempty\mmz@prefix@dir{}{% \mmz@remove@quotes{\mmz@prefix@dir}\mmz@temp \pdf@system{\mmz@mkdir@command\space"\mmz@temp"}% }% }% \fi } % \end{key} % % \begin{key}{memo dir, no memo dir} % Shortcuts for two handy settings of |prefix|. Key |no memo dir| will % place the memos and externs in the current directory, prefixed with |#1.|, % where |#1| defaults to (unquoted) |\jobname|. The default |memo dir| % places the memos and externs in a dedicated directory, |#1.memo.dir|; the % filenames themselves have no prefix. \mmzset{% memo dir/.style={prefix={#1.memo.dir/}}, memo dir/.default=\jobname, no memo dir/.style={prefix={#1.}}, no memo dir/.default=\jobname, memo dir, } % \end{key} % % \begin{macro}{\mmz@remove@quotes} % This macro removes fully expands |#1|, detokenizes the expansion and then % removes all double quotes the string. The result is stored in the control % sequence given in |#2|. % % We use this macro when we are passing a filename constructed from % |\jobname| to external programs. \def\mmz@remove@quotes#1#2{% \def\mmz@remove@quotes@end{\let#2\mmz@temp}% \def\mmz@temp{}% \expanded{% \noexpand\mmz@remove@quotes@i \detokenize\expandafter{\expanded{#1}}% "\noexpand\mmz@eov }% } \def\mmz@remove@quotes@i{% \CollectArgumentsRaw {\collargsReturnPlain \collargsNoDelimiterstrue \collargsAppendExpandablePostprocessor{{\the\collargsArg}}% }% {u"u\mmz@eov}% \mmz@remove@quotes@ii } \def\mmz@remove@quotes@ii#1#2{% \appto\mmz@temp{#1}% \ifx&% \mmz@remove@quotes@end \expandafter\@gobble \else \expandafter\@firstofone \fi {\mmz@remove@quotes@i#2\mmz@eov}% } % \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} % %\def\memoize{% %\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 } % \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| and disappearing it with % |\collargsAppendExpandablePostprocessor{}|; note that this removes any number of space % tokens. |\CollectArgumentsRaw| automatically adapts the argument type % |b| to the format. % }{&&{\collargsAppendExpandablePostprocessor{}}!t{ }+b{memoize}}{\mmz@do}% }% % % \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 } %\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 } % %<*plain,context> %\def\nomemoize{% %\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 } %\def\endnomemoize{% %\def\stopnomemoize{% \endgroup \unskip } % %<*nommz> %\let\memoize\nomemoize %\let\endmemoize\endnomemoize %\let\startmemoize\startnomemoize %\let\stopmemoize\stopnomemoize % % % \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. %<*mmz,nommz,mmzable&generic> \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> \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 % \let\mmz@lua@atbeginmemoization\relax \let\mmz@lua@atendmemoization\relax %\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 \PackageInfo{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 \PackageInfo{memoize}{Memoization was aborted}% \mmz@compute@context@mdfivesum \mmz@write@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{\advice@typeout{[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} % % % \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|. % \indentmacrocode meaning to context/.code={\forcsvlist\mmz@mtoc{#1}}, csname meaning to context/.code={\mmz@mtoc@csname{#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@csname{pgfk@\pgfkeyscurrentpath/.@cmd}}}, /handlers/.value to context/.code={% \expanded{\noexpand\mmz@mtoc@csname{pgfk@\pgfkeyscurrentpath}}}, } % \noindentmacrocode \def\mmz@mtoc#1{% \collargs@cs@cases{#1}% {\mmz@mtoc@cmd{#1}}% {\mmz@mtoc@error@notcsorenv{#1}}% {% \mmz@mtoc@csname{% %start% #1}% \mmz@mtoc@csname{% %end% %stop% #1}% }% } \def\mmz@mtoc@cmd#1{% \begingroup \escapechar=-1 \expandafter\endgroup \expandafter\mmz@mtoc@csname\expandafter{\string#1}% } \def\mmz@mtoc@csname#1{% \pgfkeysvalueof{/mmz/context/.@cmd}% \detokenize{#1}={\expandafter\meaning\csname#1\endcsname}% \pgfeov } \def\mmz@mtoc@key#1{\mmz@mtoc@csname{pgfk@#1}} \def\mmz@mtoc@keycmd#1{\mmz@mtoc@csname{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\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}{\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. %\protected@xdef %\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\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@prefix@dir#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. % \indentmacrocode \def\mmz@process@ccmemo{% \mmz@trace@process@ccmemo % \noindentmacrocode % 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@name\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@prefix@dir\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 % %<*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 }) }% }% % % % 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. %\let\protect\noexpand \pdf@primitive\shipout\box\mmz@box %\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//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$. % -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. %\let\mmzRegularPages\ReadonlyShipoutCounter %\let\mmzRegularPages\realpageno %\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}. %\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 }% }% } % % \end{macro} % % \begin{macro}{\mmz@include@extern@from@tbe@box} % Include the extern number |#1| residing in |\mmz@tbe@box| into the % document. It may be called as |\mmzIncludeExtern| from |after memoization| % hook if |\ifmmzkeepexterns| was set to true during memoization. The macro % takes the same arguments as |\mmzIncludeExtern| but disregards all but % the first one, the extern sequential number. Using this macro, a complex % memoization driver can process the cc-memo right after memoization, by % issuing % |\global\mmzkeepexternstrue\xtoksapp\mmzAfterMemoizationExtra{\the\mmzCCMemo}|. \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@error}, % \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 (without |-k|), i.e.\ % we're not commenting out the |\mmzNewExtern| lines, because we're about % to overwrite it anyway. We inform the script about the format of the % document (|-F|). extract/perl/.code={% \mmz@clear@extraction@log \pdf@system{% \mmzvalueof{perl extraction command}\space \mmzvalueof{perl extraction options}% }% \mmz@check@extraction@log{perl}% }, perl extraction command/.initial=memoize-extract.pl, perl extraction options/.initial={\space %-F latex %-F plain %-F context \jobname\space }, 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}% % Change the initial value of |mkdir command| to |memoize-extract.py % --mkdir|, but only in the case the user did not modify it. \ifx\mmz@mkdir@command\mmz@initial@mkdir@command \def\mmz@mkdir@command{\mmzvalueof{python extraction command} --mkdir}% \fi }, python extraction command/.initial=memoize-extract.py, python extraction options/.initial={\space %-F latex %-F plain %-F context \jobname\space }, } \def\mmz@preamble@only@error{% \PackageError{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 ends if |\endinput| --- and also contains any warnings and % errors thrown by the script. 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{\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{% \PackageError{memoize}{Extraction of externs from document "\jobname.pdf" using method "\extractionmethod" was unsuccessful}{The extraction script "\mmzvalueof{\extractionmethod\space extraction command}" wasn't executed or didn't finish execution properly.}} % % \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, }, 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}% } % \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. % % \begin{key}{record/mmz/...} % 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|). \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}% }% % Support |latexmk|: %\typeout{No file #1}% }, 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} % % \begin{macro}{\mmzUsedCMemo,\mmzUsedCCMemo,\mmzUsedExtern,\mmzNewCMemo,\mmzNewCCMemo,\mmzPrefix} % We can ignore everything but |\mmzNewExtern|s. All these macros receive a % single argument. % \indentmacrocode \def\mmzUsedCMemo#1{} \def\mmzUsedCCMemo#1{} \def\mmzUsedExtern#1{} \def\mmzNewCMemo#1{} \def\mmzNewCCMemo#1{} \def\mmzPrefix#1{} % \end{macro} % % \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 "jobname.pdf" using method "\extractionmethod" was unsuccessful.}{Check the log file to see if the extraction script was executed at all, and if it finished successfully. You might also want to inspect "\externbasepath.log", the log file of the embedded TeX compilation which ran the extraction script}} % % \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" % \end{listingregion} }, tex extraction script/.initial={% % \begin{listingregion}{tex-extraction-script.tex} ^^A todo: context \def\noexpand\fromdocument{\jobname.pdf}% \def\noexpand\pagenumber{\pagenumber}% \def\noexpand\expectedwidth{\expectedwidth}% \def\noexpand\expectedheight{\expectedheight}% \def\noexpand\logfile{\jobname.mmz.log}% \unexpanded{% \def\warningtemplate{% %\noexpand\PackageWarning{memoize}{\warningtext}% %\warning{memoize: \warningtext}% %\warning{memoize: \warningtext}% }}% \ifdef\XeTeXversion{}{% \def\noexpand\mmzpdfmajorversion{\the\pdfmajorversion}% \def\noexpand\mmzpdfminorversion{\the\pdfminorversion}% }% \noexpand\input memoize-extract-one % \end{listingregion} }, } % % % \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}.) % %<*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 }% % %<*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 % % \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}, % %noop/.style={inner handler=\mmz@auto@noop}, nomemoize/.style={noop, options=disable}, replicate/.style={run if memoizing, inner handler=\mmz@auto@replicate}, to context/.style={run if memoizing, outer handler=\mmz@auto@tocontext}, } % % \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|. % %\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 } % % \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}}% %\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} % % \begin{mmzautokey}{to context} % \begin{macro}{\mmz@auto@tocontext} % This outer handler appends the original definition of the handled command % to the context. The |\expandafter| are there to expand |\AdviceName| % once before fully expanding |\AdviceGetOriginalCsname|. \def\mmz@auto@tocontext{% \expanded{% \noexpand\pgfkeysvalueof{/mmz/context/.@cmd}% original "\AdviceNamespace" csname "\AdviceCsname"={% \noexpand\expanded{% \noexpand\noexpand\noexpand\meaning \noexpand\AdviceCsnameGetOriginal{\AdviceNamespace}{\AdviceCsname}% }% }% }% \pgfeov \AdviceOriginal } % \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{% \mmz@mtoc@csname{r@#1}% \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 } % % % \end{macro} % % \section{Support for various classes and packages} % %<*latex> \AddToHook{shipout/before}[memoize]{\global\advance\mmzExtraPages-1\relax} \AddToHook{shipout/after}[memoize]{\global\advance\mmzExtraPages1\relax} \mmzset{auto=\DiscardShipoutBox{ outer handler=\global\advance\mmzExtraPages1\relax\AdviceOriginal}} % % % Utility macro for clarity below. |#1| is the name of the package which should % be loaded (used with \hologo{LaTeX}) and |#2| is the name of the command % which should be defined (used with \hologo{plainTeX} and \hologo{ConTeXt}) % for |#3| to be executed at the beginning of the document. We make sure that % we can use |#1| etc.\ inside |#3|. \def\mmz@if@package@loaded#1#2#3{% \mmzset{% begindocument/before/.append code={% %\@ifpackageloaded{#1}{% %\ifdefined#2% #3% %\fi %}{}% }% }% } % % \subsection[PGF]{\pkg{PGF}} % \mmz@if@package@loaded{pgf}{% %\pgfpicture %\startpgfpicture }{% \def\mmzPgfAtBeginMemoization{% \edef\mmz@pgfpictureid{% \the\pgf@picture@serial@count }% }% \def\mmzPgfAtEndMemoization{% \edef\mmz@temp{% \the\numexpr\pgf@picture@serial@count-\mmz@pgfpictureid\relax }% \ifx\mmz@temp=0 \else \xtoksapp\mmzCCMemo{% \unexpanded{% \global\expandafter\advance\csname pgf@picture@serial@count\endcsname }% \mmz@temp }% \fi }% % \begin{listingregion}{_support-pgf.tex} \mmzset{% at begin memoization=\mmzPgfAtBeginMemoization, at end memoization=\mmzPgfAtEndMemoization, }% % \end{listingregion} } % % % \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@if@package@loaded{tikz}{\tikz}{% \input advice-tikz.code.tex % We define and activate the automemoization handlers for the \TikZ; command % and environment. % \begin{listingregion}{_support-tikz.tex} \mmzset{% auto={tikzpicture}{memoize}, auto=\tikz{memoize, collector=\AdviceCollectTikZArguments}, }% % \end{listingregion} } % % % \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. Command |\Forest| is % defined using |xparse|, so |args| is unnecessary. %<*latex> \mmz@if@package@loaded{forest}{\Forest}{% % \begin{listingregion}{_support-forest.tex} \mmzset{ auto={forest}{memoize}, auto=\Forest{memoize}, }% % \end{listingregion} } % % % \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} }{}} % % % \subsection{Biblatex} % \label{sec:biblatex} % % %<*latex> \mmzset{ biblatex/.code={% \mmz@if@package@loaded{biblatex}{}{% \input memoize-biblatex.code.tex \mmzset{#1}% }% }, } % % %<*biblatex> \edef\memoizeresetatcatcode{\catcode`\noexpand\@\the\catcode`\@\relax}% \catcode`\@=11 \mmzset{% % Advise macro |\entry| occuring in |.bbl| files to collect the entry, % verbatim. |args|: |m| = citation key, |&&{...}u| = the entry, verbatim, % braced --- so |\blx@bbl@entry| will receive two mandatory arguments. auto=\blx@bbl@entry{ inner handler=\mmz@biblatex@entry, args={% m% &&{\collargsVerb \collargsAppendExpandablePostprocessor{{\the\collargsArg}}% }u{\endentry}% }, % No braces around the collected arguments, as each is already braced on % its own. raw collector options=\collargsReturnPlain, }, % \begin{mmzautokey}{cite,volcite,cites,volcites} % \indentmacrocode Define handlers for citation commands. auto/cite/.style={ run conditions=\mmz@biblatex@cite@rc, outer handler=\mmz@biblatex@cite@outer, args=l*m, raw collector options=\mmz@biblatex@def@star\collargsReturnNo, inner handler=\mmz@biblatex@cite@inner, }, % \noindentmacrocode We need a dedicated |volcite| even though |\volcite| % executes |\cite| because otherwise, we would end up with % |\cite{volume}{key}| in the cc-memo when |biblatex ccmemo cite=replicate|. auto/volcite/.style={ run if memoizing, outer handler=\mmz@biblatex@cite@outer, args=lml*m, raw collector options=\mmz@biblatex@def@star\collargsReturnNo, inner handler=\mmz@biblatex@cite@inner, }, auto/cites/.style={ run conditions=\mmz@biblatex@cites@rc, outer handler=\mmz@biblatex@cites@outer, args=l*m, raw collector options= \mmz@biblatex@def@star\collargsClearArgsfalse\collargsReturnNo, inner handler=\mmz@biblatex@cites@inner, }, auto/volcites/.style={ run if memoizing, outer handler=\mmz@biblatex@cites@outer, args=lml*m, raw collector options= \mmz@biblatex@def@star\collargsClearArgsfalse\collargsReturnNo, inner handler=\mmz@biblatex@cites@inner, }, % \end{mmzautokey} % \begin{mmzautokey}{biblatex ccmemo cite} % What to put into the cc-memo, |\nocite| or the handled citation command? biblatex ccmemo cite/.is choice, biblatex ccmemo cite/nocite/.code={% \let\mmz@biblatex@do@ccmemo\mmz@biblatex@do@nocite }, biblatex ccmemo cite/replicate/.code={% \let\mmz@biblatex@do@ccmemo\mmz@biblatex@do@replicate }, % \end{mmzautokey} }% % \begin{macro}{\mmz@biblatex@entry} % This macro stores the MD5 sum of the |\entry| when reading the |.bbl| file. \def\mmz@biblatex@entry#1#2{% \edef\mmz@temp{\pdf@mdfivesum{#2}}% \global\cslet{mmz@bbl@#1}\mmz@temp \mmz@scantokens{\AdviceOriginal{#1}#2}% } % \end{macro} % \begin{macro}{\mmz@biblatex@cite@rc,\mmz@biblatex@cites@rc} % \indentmacrocode Run if memoizing but not within a |\volcite| % command. Applied to |\cite(s)|. \def\mmz@biblatex@cite@rc{% \ifmemoizing % \noindentmacrocode We cannot use the official |\ifvolcite|, or even the % |blx@volcite| toggle it depends on, because these are defined\slash set % within the next-citation hook, which is yet to be executed. So we % depend on the internal detail that |\volcite| and friends redefine % |\blx@citeargs| to |\blx@volciteargs|. \ifx\blx@citeargs\blx@volciteargs \else \AdviceRuntrue \fi \fi } \def\mmz@biblatex@cites@rc{% \ifmemoizing % The internal detail with |\volcites|: it defines a hook. \ifdef\blx@hook@mcite@before{}{\AdviceRuntrue}% \fi } % \end{macro} % \begin{macro}{\mmz@biblatex@cite@outer} % Initialize the macro receiving the citation key(s), and execute the % collector. \def\mmz@biblatex@cite@outer{% \gdef\mmz@biblatex@keys{}% \AdviceCollector } % \end{macro} % \begin{macro}{\mmz@biblatex@mark@citation@key} % We \emph{append} to |\mmz@biblatex@keys| to automatically collect all % citation keys of a |\cites| command; note that we use this system for % |\cite| as well. \def\mmz@biblatex@def@star{% \collargsAlias{*}{&&{\mmz@biblatex@mark@citation@key}}% } \def\mmz@biblatex@mark@citation@key{% \collargsAppendPreprocessor{\xappto\mmz@biblatex@keys{,\the\collargsArg}}% } % \end{macro} % \begin{macro}{\mmz@biblatex@cite@inner} % This macro puts the cites reference keys into the context, and adds % |\nocite|, or the handled citation command, to the cc-memo. \def\mmz@biblatex@cite@inner{% \mmz@biblatex@do@context \mmz@biblatex@do@ccmemo \expandafter\AdviceOriginal\the\collargsArgs } \def\mmz@biblatex@do@context{% \expandafter\forcsvlist \expandafter\mmz@biblatex@do@context@one \expandafter{\mmz@biblatex@keys}% } \def\mmz@biblatex@do@context@one#1{% \mmz@mtoc@csname{mmz@bbl@#1}% \ifcsdef{mmz@bbl@#1}{}{\mmzAbort}% } \def\mmz@biblatex@do@nocite{% \xtoksapp\mmzCCMemo{% \noexpand\nocite{\mmz@biblatex@keys}% }% } \def\mmz@biblatex@do@replicate{% \xtoksapp\mmzCCMemo{% {% \nullfont % It is ok to use |\AdviceName| here, as the cc-memo is never input % during memoization. \expandonce\AdviceName\the\collargsArgs }% }% } \let\mmz@biblatex@do@ccmemo\mmz@biblatex@do@nocite % \end{macro} % \begin{macro}{\mmz@biblatex@cites@outer} % Same as for |cite|, but we iterate the collector as long as the arguments % continue. \def\mmz@biblatex@cites@outer{% \global\collargsArgs{}% \gdef\mmz@biblatex@keys{}% \AdviceCollector } \def\mmz@biblatex@cites@inner{% \futurelet\mmz@temp\mmz@biblatex@cites@inner@again } % If the following token is an opening brace or bracket, the % multicite arguments continue. \def\mmz@biblatex@cites@inner@again{% \mmz@tempfalse \ifx\mmz@temp\bgroup \mmz@temptrue \else \ifx\mmz@temp[%] \mmz@temptrue \fi \fi \ifmmz@temp \expandafter\AdviceCollector \else \expandafter\mmz@biblatex@cites@inner@finish \fi } \def\mmz@biblatex@cites@inner@finish{% \mmz@biblatex@do@context \mmz@biblatex@do@ccmemo \expandafter\AdviceOriginal\the\collargsArgs } % \end{macro} % % \paragraph{Advise} the citation commands. \mmzset{ auto=\cite{cite}, auto=\Cite{cite}, auto=\parencite{cite}, auto=\Parencite{cite}, auto=\footcite{cite}, auto=\footcitetext{cite}, auto=\textcite{cite}, auto=\Textcite{cite}, auto=\smartcite{cite}, auto=\Smartcite{cite}, auto=\supercite{cite}, auto=\cites{cites}, auto=\Cites{cites}, auto=\parencites{cites}, auto=\Parencites{cites}, auto=\footcites{cites}, auto=\footcitetexts{cites}, auto=\smartcites{cites}, auto=\Smartcites{cites}, auto=\textcites{cites}, auto=\Textcites{cites}, auto=\supercites{cites}, auto=\autocite{cite}, auto=\Autocite{cite}, auto=\autocites{cites}, auto=\Autocites{cites}, auto=\citeauthor{cite}, auto=\Citeauthor{cite}, auto=\citetitle{cite}, auto=\citeyear{cite}, auto=\citedate{cite}, auto=\citeurl{cite}, auto=\nocite{cite}, auto=\fullcite{cite}, auto=\footfullcite{cite}, auto=\volcite{volcite}, auto=\Volcite{volcite}, auto=\volcites{volcites}, auto=\Volcites{volcites}, auto=\pvolcite{volcite}, auto=\Pvolcite{volcite}, auto=\pvolcites{volcites}, auto=\Pvolcites{volcites}, auto=\fvolcite{volcite}, auto=\Fvolcite{volcite}, auto=\fvolcites{volcites}, auto=\Fvolcites{volcites}, auto=\ftvolcite{volcite}, auto=\ftvolcites{volcites}, auto=\Ftvolcite{volcite}, auto=\Ftvolcites{volcites}, auto=\svolcite{volcite}, auto=\Svolcite{volcite}, auto=\svolcites{volcites}, auto=\Svolcites{volcites}, auto=\tvolcite{volcite}, auto=\Tvolcite{volcite}, auto=\tvolcites{volcites}, auto=\Tvolcites{volcites}, auto=\avolcite{volcite}, auto=\Avolcite{volcite}, auto=\avolcites{volcites}, auto=\Avolcites{volcites}, auto=\notecite{cite}, auto=\Notecite{cite}, auto=\pnotecite{cite}, auto=\Pnotecite{cite}, auto=\fnotecite{cite}, % Similar to |volcite|, these commands must be handled specifically in order % to function correctly with |biblatex ccmemo cite=replicate|. auto=\citename{cite, args=l*mlm}, auto=\citelist{cite, args=l*mlm}, auto=\citefield{cite, args=l*mlm}, } \memoizeresetatcatcode % %<*mmz> % % % \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}} % % 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}, } % % We clear the hooks after executing the last of them. \mmzset{ begindocument/end/.append style={ begindocument/before/.code={}, begindocument/.code={}, begindocument/end/.code={}, } } % \end{key} % % Formats other than \hologo{plainTeX} need a way to prevent extraction during % package-loading. %\mmzset{extract/no/.code={}} % % \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}). % %\InputIfFileExists{memoize.cfg}{}{} %<*mmz> % % For formats other than \hologo{plainTeX}, we also save the current (initial % or |memoize.cfg|-set) value of |extract|, so that we can restore it when % package options include |extract=no|. Then, |extract| can be called without % an argument in the preamble, triggering extraction using this method; this is % useful e.g.\ if Memoize is compiled into a format. %\let\mmz@initial@extraction@method\mmz@extraction@method % % \paragraph{Process} the package options (except in \hologo{plainTeX}). %<*latex> \DeclareUnknownKeyHandler[mmz]{% \expanded{\noexpand\pgfqkeys{/mmz}{#1\IfBlankF{#2}{={#2}}}}} \ProcessKeyOptions[mmz] % %\expandafter\mmzset\expandafter{\currentmoduleparameters} % % In \hologo{LaTeX}, |nomemoize| has to process package options as well, % otherwise \hologo{LaTeX} will complain. % %<*latex&nommz> \DeclareUnknownKeyHandler[mmz]{} \ProcessKeyOptions[mmz] % % \paragraph{Extern extraction} % We redefine |extract| to immediately trigger extraction. This is crucial in % \hologo{plainTeX}, where extraction must be invoked after loading the % package, but also potentially useful in other formats when package options % include |extract=no|. %<*mmz> \mmzset{ extract/.is choice, extract/.default=\mmz@extraction@method, % But only once: extract/.append style={ extract/.code={\PackageError{memoize}{Key "extract" was invoked twice.}{In principle, externs should be extracted only once. If you really want to extract again, execute "extract/".}}, }, % In formats other than \hologo{plainTeX}, we remember the current |extract| % code and then trigger the extraction. %/utils/exec={\pgfkeysgetvalue{/mmz/extract/.@cmd}\mmz@temp@extract}, %extract=\mmz@extraction@method, } % Option |extract=no| (which only exists in formats other than % \hologo{plainTeX}) should allow for an explicit invocation of |extract| in % the preamble. %<*!plain> \def\mmz@temp{no} \ifx\mmz@extraction@method\mmz@temp \pgfkeyslet{/mmz/extract/.@cmd}\mmz@temp@extract \let\mmz@extraction@method\mmz@initial@extraction@method \fi \let\mmz@temp@extract\relax % % % 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 and % pure memoization are still perfectly valid in this mode, so let's just warn % the user. \ifnum\pdf@draftmode=1 \PackageWarning{memoize}{No externalization will be performed in the draft mode}% \fi % % % Several further things which need to be defined as dummies in % |nomemoize|\slash|memoizable|. %<*nommz,mmzable&generic> \pgfkeys{% /handlers/.meaning to context/.code={}, /handlers/.value to context/.code={}, } \let\mmzAbort\relax \let\mmzUnmemoizable\relax \newcommand\IfMemoizing[2][]{\@secondoftwo} \let\mmzNoRef\@gobble \let\mmzForceNoRef\@gobble \newtoks\mmzContext \newtoks\mmzContextExtra \newtoks\mmzCMemo \newtoks\mmzCCMemo \newcount\mmzExternPages \newcount\mmzExtraPages \let\mmzTracingOn\relax \let\mmzTracingOff\relax % % % \paragraph{The end} of |memoize|, |nomemoize| and |memoizable|. %<*mmz,nommz,mmzable> %\resetatcatcode %\stopmodule %\protect % % That's all, folks! % % \end{macrocode} % % \endinput % % Local Variables: % TeX-engine: luatex % TeX-master: "doc/memoize-code.tex" % TeX-auto-save: nil % End: