-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathmemoize.edtx
4096 lines (4096 loc) · 152 KB
/
memoize.edtx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
% Memoize.edtx (this is not a .dtx file; to produce a .dtx, process it with edtx2dtx)
%
%% This file is a part of Memoize, a TeX package for externalization of
%% graphics and memoization of compilation results in general, available at
%% https://ctan.org/pkg/memoize and https://github.com/sasozivanovic/memoize.
%%
%% Copyright (c) 2020- Saso Zivanovic <saso.zivanovic@guest.arnes.si>
%% (Sa\v{s}o \v{Z}ivanovi\'{c})
%%
%% This work may be distributed and/or modified under the conditions of the
%% LaTeX Project Public License, either version 1.3c of this license or (at
%% your option) any later version. The latest version of this license is in
%% https://www.latex-project.org/lppl.txt and version 1.3c or later is part of
%% all distributions of LaTeX version 2008 or later.
%%
%% This work has the LPPL maintenance status `maintained'.
%% The Current Maintainer of this work is Saso Zivanovic.
%%
%% The files belonging to this work and covered by LPPL are listed in
%% (<texmf>/doc/generic/memoize/)FILES.
%
% \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>
%<latex>\ProvidesPackage{memoize}[2024/12/02 v1.4.1 Fast and flexible externalization]
%<context>%D \module[
%<context>%D file=t-memoize.tex,
%<context>%D version=1.4.1,
%<context>%D title=Memoize,
%<context>%D subtitle=Fast and flexible externalization,
%<context>%D author=Saso Zivanovic,
%<context>%D date=2024-12-02,
%<context>%D copyright=Saso Zivanovic,
%<context>%D license=LPPL,
%<context>%D ]
%<context>\writestatus{loading}{ConTeXt User Module / memoize}
%<context>\unprotect
%<context>\startmodule[memoize]
%<plain>% Package memoize 2024/12/02 v1.4.1
%</mmz>
%<*mmzable>
%<latex>\ProvidesPackage{memoizable}[2024/12/02 v1.4.1 A programmer's stub for Memoize]
%<context>%D \module[
%<context>%D file=t-memoizable.tex,
%<context>%D version=1.4.1,
%<context>%D title=Memoizable,
%<context>%D subtitle=A programmer's stub for Memoize,
%<context>%D author=Saso Zivanovic,
%<context>%D date=2024-12-02,
%<context>%D copyright=Saso Zivanovic,
%<context>%D license=LPPL,
%<context>%D ]
%<context>\writestatus{loading}{ConTeXt User Module / memoizable}
%<context>\unprotect
%<context>\startmodule[memoizable]
%<plain>% Package memoizable 2024/12/02 v1.4.1
%</mmzable>
%<*nommz>
%<latex>\ProvidesPackage{nomemoize}[2024/12/02 v1.4.1 A no-op stub for Memoize]
%<context>%D \module[
%<context>%D file=t-nomemoize.tex,
%<context>%D version=1.4.1,
%<context>%D title=Memoize,
%<context>%D subtitle=A no-op stub for Memoize,
%<context>%D author=Saso Zivanovic,
%<context>%D date=2024-12-02,
%<context>%D copyright=Saso Zivanovic,
%<context>%D license=LPPL,
%<context>%D ]
%<context>\writestatus{loading}{ConTeXt User Module / nomemoize}
%<context>\unprotect
%<context>\startmodule[nomemoize]
%<mmz>% Package nomemoize 2024/12/02 v1.4.1
%</nommz>
%
% \paragraph{Required packages} and \hologo{LaTeX}ization of \hologo{plainTeX}
% and \hologo{ConTeXt}.
%<*(mmz,mmzable,nommz)&(plain,context)>
\input miniltx
%</(mmz,mmzable,nommz)&(plain,context)>
% Some stuff which is ``missing'' in |miniltx|, copied here from |latex.ltx|.
%<*mmz&(plain,context)>
\def\PackageWarning#1#2{{%
\newlinechar`\^^J\def\MessageBreak{^^J\space\space#1: }%
\message{#1: #2}}}
%</mmz&(plain,context)>
% Same as the official definition, but without |\outer|. Needed for record
% file declarations.
%<*mmz&plain>
\def\newtoks{\alloc@5\toks\toksdef\@cclvi}
\def\newwrite{\alloc@7\write\chardef\sixt@@n}
%</mmz&plain>
% I can't really write any code without |etoolbox| \dots
%<*mmz>
%<latex>\RequirePackage{etoolbox}
%<plain,context>\input etoolbox-generic
% Setup the |memoize| namespace in \hologo{LuaTeX}.
\ifdefined\luatexversion
\directlua{memoize = {}}
\fi
% |pdftexcmds.sty| eases access to some PDF primitives, but I cannot manage to
% load it in \hologo{ConTeXt}, even if it's supposed to be a generic
% package. So let's load |pdftexcmds.lua| and copy--paste what we need from
% |pdftexcmds.sty|.
%<latex>\RequirePackage{pdftexcmds}
%<plain>\input pdftexcmds.sty
%<*context>
\directlua{%
require("pdftexcmds")
tex.enableprimitives('pdf@', {'draftmode'})
}
\long\def\pdf@mdfivesum#1{%
\directlua{%
oberdiek.pdftexcmds.mdfivesum("\luaescapestring{#1}", "byte")%
}%
}%
\def\pdf@system#1{%
\directlua{%
oberdiek.pdftexcmds.system("\luaescapestring{#1}")%
}%
}
\let\pdf@primitive\primitive
% Lua function |oberdiek.pdftexcmds.filesize| requires the |kpse| library,
% which is not loaded in \hologo{ConTeXt},
% see \url(https://){github.com/latex3/lua-uni-algos/issues/3}, so we define our
% own filesize function.
\directlua{%
function memoize.filesize(filename)
local filehandle = io.open(filename, "r")
% We can't easily use |~=|, as |~| is an active character, so the |else|
% workaround.
if filehandle == nil then
else
tex.write(filehandle:seek("end"))
io.close(filehandle)
end
end
}%
\def\pdf@filesize#1{%
\directlua{memoize.filesize("\luaescapestring{#1}")}%
}
%</context>
% Take care of some further differences between the engines.
\ifdef\pdftexversion{%
}{%
\def\pdfhorigin{1true in}%
\def\pdfvorigin{1true in}%
\ifdef\XeTeXversion{%
\let\quitvmode\leavevmode
}{%
\ifdef\luatexversion{%
\let\pdfpagewidth\pagewidth
\let\pdfpageheight\pageheight
\def\pdfmajorversion{\pdfvariable majorversion}%
\def\pdfminorversion{\pdfvariable minorversion}%
}{%
\PackageError{memoize}{Support for this TeX engine is not implemented}{}%
}%
}%
}
%</mmz>
%
% 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>
%<latex>\RequirePackage{pgfkeys}
%<plain>\input pgfkeys
%<context>\input t-pgfkey
%</nommz,mmzable>
%
% 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.
%<mmzable&!generic>\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
%</plain>
% Our auxiliary package (\MS\ref{sec:ref:collargs}, \S\ref{sec:code:collargs}).
% We also need it in |nomemoize|, to collect manual environments.
%<latex>\RequirePackage{advice}
%<plain>\input advice
%<context>\input t-advice
%</mmz>
%
% \paragraph.{Loading order} |memoize| and |nomemoize| are mutually exclusive,
% and |memoizable| must be loaded before either of them. |\mmz@loadstatus|: 1
% = memoize, 2 = memoizable, 3 = nomemoize.
%<*mmz,nommz>
\def\ifmmz@loadstatus#1{%
\ifnum#1=0\csname mmz@loadstatus\endcsname\relax
\expandafter\@firstoftwo
\else
\expandafter\@secondoftwo
\fi
}
%</mmz,nommz>
%<*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.}%
%<latex>\pgfkeys{/memoize/package options/.unknown/.code={}}
%<latex>\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
%<latex>"\string\usepackage{memoize}"
%<plain>"\string\input memoize"
%<context>"\string\usemodule[memoize]"
before the
%<latex>"\string\usepackage"
%<plain>"\string\input"
%<context>"\string\usemodule"
of that package.}%
%<latex>\pgfkeys{/memoize/package options/.unknown/.code={}}
%<latex>\ProcessPgfPackageOptions{/memoize/package options}
\endinput
}{}%
\ifmmz@loadstatus{1}{\endinput}{}%
\def\mmz@loadstatus{1}%
%</mmz>
%<*mmzable&generic>
\ifcsname mmz@loadstatus\endcsname\endinput\fi
\def\mmz@loadstatus{2}%
%</mmzable&generic>
%<*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
%<latex>"\string\usepackage{nomemoize}"
%<plain>"\string\input memoize"
%<context>"\string\usemodule[memoize]"
before the
%<latex>"\string\usepackage"
%<plain>"\string\input"
%<context>"\string\usemodule"
of that package.}%
\endinput
}{}%
\ifmmz@loadstatus{3}{\endinput}{}%
\def\mmz@loadstatus{3}%
%</nommz>
%
%<*mmz>
%
% \begin{macro}{\filetotoks}
% Read \hologo{TeX} file |#2| into token register |#1| (under the current
% category code regime); |\toksapp| is defined in CollArgs.
\def\filetotoks#1#2{%
\immediate\openin0{#2}%
#1={}%
\loop
\unless\ifeof0
\read0 to \totoks@temp
% We need the |\expandafter|s for our |\toksapp| macro.
\expandafter\toksapp\expandafter#1\expandafter{\totoks@temp}%
\repeat
\immediate\closein0
}
% \end{macro}
%
% \paragraph{Other} little things.
\newif\ifmmz@temp
\newtoks\mmz@temptoks
\newbox\mmz@box
\newwrite\mmz@out
%
% \section{The basic configuration}
% \label{sec:code:configuration}
%
% \begin{macro}{\mmzset}
% The user primarily interacts with Memoize through the |pgfkeys|-based
% configuration macro |\mmzset|, which executes keys in path |/mmz|. In
% |nomemoize| and |memoizable|, is exists as a no-op.
\def\mmzset#1{\pgfqkeys{/mmz}{#1}\ignorespaces}
%</mmz>
%<*nommz,mmzable&generic>
\def\mmzset#1{\ignorespaces}
%</nommz,mmzable&generic>
% \end{macro}
%
% \begin{macro}{\nommzkeys}
% Any |/mmz| keys used outside of |\mmzset| must be declared by this macro
% for |nomemoize| package to work.
%<mmz>\def\nommzkeys#1{}
%<*nommz,mmzable&generic>
\def\nommzkeys{\pgfqkeys{/mmz}}
\pgfqkeys{/mmz}{.unknown/.code={\pgfkeysdef{\pgfkeyscurrentkey}{}}}
%</nommz,mmzable&generic>
% \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,nommz,mmzable&generic>
%<*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}
%</latex>
%<plain>\def\memoize{%
%<context>\def\startmemoize{%
%<*plain,context>
\begingroup
% In \hologo{plainTeX} and \hologo{ConTeXt}, we don't have to worry about any
% spaces in front of the optional argument, as the environments are opened by
% a control sequence.
\futurelet\mmz@temp\mmz@env@i
}
\def\mmz@env@i{%
\ifx\mmz@temp[%]
\def\mmz@verbatim@fix{}%
\expandafter\mmz@env@ii
\else
\def\mmz@verbatim@fix{\noexpand\collargsFixFromNoVerbatim}%
\expandafter\mmz@env@iii
\fi
}
\def\mmz@env@ii[#1]{%
\mmzset{#1}%
\mmz@env@iii
}
%</plain,context>
\def\mmz@env@iii{%
\long\edef\mmz@do##1{%
% |\unskip| will ``trim'' spaces at the end of the environment body.
\noexpand\Memoize{##1}{##1\unskip}%
}%
\expanded{%
\noexpand\CollectArgumentsRaw{%
% |\CollectArgumentsRaw| will adapt the caller to the format automatically.
\noexpand\collargsCaller{memoize}%
% |verb(atim)| is in here if it was requested.
\expandonce\mmzRawCollectorOptions
% The category code fix, if needed.
\ifmmz@verbatim\mmz@verbatim@fix\fi
}%
% Spaces at the beginning of the environment body are trimmed by setting
% the first argument to |!t<space>| and disappearing it with
% |\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}%
}%
%</mmz>
% \end{environment}
%
% \begin{macro}{\nommz}
% We throw away the optional argument if present, and replace the opening
% brace with begin-group plus |\memoizefalse|. This way, the ``argument'' of
% |\nommz| will be processed in a group (with Memoize disabled) and even the
% verbatim code will work because the ``argument'' will not have been
% tokenized.
%
% As a user command, |\nommz| has to make it into package |nomemoize| as
% well, and we'll |\let| |\mmz| equal it there; it is not needed in
% |mmzable|.
%<*mmz,nommz>
\protected\def\nommz#1#{%
\afterassignment\nommz@i
\let\mmz@temp
}
\def\nommz@i{%
\bgroup
\memoizefalse
}
%<nommz>\let\mmz\nommz
% \end{macro}
%
% \begin{environment}{nomemoize}
% We throw away the optional argument and take care of the spaces at the
% beginning and at the end of the body.
%<*latex>
\newenvironment{nomemoize}[1][]{%
\memoizefalse
\ignorespaces
}{%
\unskip
}
%</latex>
%<*plain,context>
%<plain>\def\nomemoize{%
%<context>\def\startnomemoize{%
% Start a group to delimit |\memoizefalse|.
\begingroup
\memoizefalse
\futurelet\mmz@temp\nommz@env@i
}
\def\nommz@env@i{%
\ifx\mmz@temp[%]
\expandafter\nommz@env@ii
% No optional argument, no problems with spaces.
\fi
}
\def\nommz@env@ii[#1]{%
\ignorespaces
}
%<plain>\def\endnomemoize{%
%<context>\def\stopnomemoize{%
\endgroup
\unskip
}
%</plain,context>
%<*nommz>
%<plain,latex>\let\memoize\nomemoize
%<plain,latex>\let\endmemoize\endnomemoize
%<context>\let\startmemoize\startnomemoize
%<context>\let\stopmemoize\stopnomemoize
%</nommz>
%</mmz,nommz>
% \end{environment}
%
% \subsection{The memoization process}
% \label{sec:code:memoization-process}
%
% \begin{macro}{\ifmemoizing}
% This conditional is set to true when we start memoization (but not when
% we start regular compilation or utilization); it should never be set
% anywhere else. It is checked by |\Memoize| to prevent nested
% memoizations, deployed in advice run conditions set by |run only if
% memoizing|, etc.
%<*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,nommz,mmzable&generic>
%<*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 salted source code, 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{%
\expanded{\the\mmzSalt}%
\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,