commit 4dbb21aa0ed477a91422b43595d2d1bbc380a265
parent 3aa814ab800721f56bf67c01fa698184cc1ab57d
Author: Robert Russell <robertrussell.72001@gmail.com>
Date: Sun, 16 Apr 2023 16:27:21 -0700
Add macros for defining "parsers"
See in-source documentation.
Diffstat:
| M | util.tex | | | 98 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- |
1 file changed, 94 insertions(+), 4 deletions(-)
diff --git a/util.tex b/util.tex
@@ -5,6 +5,12 @@
\newskip\centering \centering=0pt plus1000pt minus1000pt
\newbox\voidbox
+% These macros help cope with TeX's bad behaviour regarding \afterassignment
+% and \setbox with a literal box (e.g., \hbox{...}).
+\newcount\@grouplevel
+\def\@checkgrouplevel{\ifnum\currentgrouplevel>\@grouplevel \ea\aftergroup \fi}
+\def\@savegrouplevel{\@grouplevel\currentgrouplevel}
+
% \defevent defines an "event". Hooks can be attached to events with \addhook,
% and all hooks are run in order when the event is triggered with \event.
% Events can have parameters like macros, and hooks have access to these
@@ -20,7 +26,7 @@
\protected\def\defevent#1[#2]{%
\toksA={#1}% Double hashes
\xcsdef{event:#2}{\def\nx\@hooks\the\toksA}%
- \ea\newtoks\begcs hooks:#2\endcs \cs{hooks:#2}={}%
+ \ea\gnewtoks\begcs hooks:#2\endcs \cs{hooks:#2}={}%
}
\protected\def\event[#1]{%
\relax % Events can appear in weird places (e.g., the standard \par event)
@@ -29,6 +35,93 @@
}
\protected\def\addhook[#1]#2{\cs{hooks:#1}\ea{\the\cs{hooks:#1}#2}}
+% \begparser...\endparser defines a "parser". Usage:
+% \begparser\x
+% \stage<param 0>{<body 0>}<assign 0>...
+% \stage<param 1>{<body 1>}<assign 1>...
+% etc...
+% \stage<param n-1>{<body n-1>}<assign n-1>...
+% \stage<param n>{<body n>}...
+% \endparser
+%
+% This defines a series of macros of form
+% \csdef{x:0}<param 0>{<body 0> \afterassignment\x:1 <assign 0>}
+% \csdef{x:1}<param 1>{<body 1> \afterassignment\x:2 <assign 1>}
+% etc...
+% \csdef{x:n-1}<param n-1>{<body n-1> \afterassignment\x:n <assign n-1>}
+% \csdef{x:n}<param n>{<body n>}
+%
+% Any of the <assign i>'s may be empty, in which case the corresponding
+% \afterassignment is omitted. Also, any of the \stage's may be replaced by a
+% \boxstage, in which case an intermediate stage is inserted to deal with the
+% behaviour of \afterassignment on box assignments with literal boxes
+% (e.g., \hbox{...}).
+%
+% Note that the text between \begparser\x and \endparser is essentially
+% arbitrary, so one can employ tricks such as
+% \toksA={ }
+% \ea\stage\the\toksA{<body>}<assign>...
+%
+% Parsers are always defined locally.
+% TODO: Add version of \begparser that defines each stage globally.
+\newcount\@parsern
+\newif\if@boxstage
+\newtoks\@stageparam
+\def\@parsercs{\@parsername:\number\numexpr\@parsern}
+\protected\def\begparser#1{%
+ \@parsern=0
+ \edef\@parsername{\csstring#1}%
+ \def\@prevstage##1{}%
+}
+\protected\def\boxstage{\@boxstagetrue \stage}
+\protected\def\stage#1#{\@stageparam={#1}\@stage}
+\def\@stage#1#2...{%
+ \@prevstage\iftrue % Define the prev stage with linkage to the curr stage.
+ \def\@stageasn{#2}%
+ \if@boxstage
+ \ifx\@stageasn\empty \else
+ \ecsdef{\@parsercs!}{%
+ \nx\@checkgrouplevel \ea\nx\begcs\@parsercs+1\endcs
+ }%
+ \fi
+ \fi
+ \edef\@stagedef{\csdef{\@parsercs}\the\@stageparam}%
+ \edef\@stagelinkage{%
+ \ifx\@stageasn\empty % Omit \afterassignment?
+ \ea\nx\begcs\@parsercs+1\endcs
+ \else
+ \if@boxstage
+ \nx\@savegrouplevel
+ \afterassignment\ea\nx\begcs\@parsercs!\endcs
+ \else
+ \afterassignment\ea\nx\begcs\@parsercs+1\endcs
+ \fi
+ \unexpanded{#2}%
+ \fi
+ }%
+ \def\@prevstage##1{%
+ \edef\@stagebody{{%
+ \unexpanded{#1}%
+ ##1% Include linkage?
+ \unexpanded\ea{\@stagelinkage}%
+ \fi
+ }}%
+ \ea\@stagedef\@stagebody
+ }%
+ \incr\@parsern
+ \@boxstagefalse
+}
+\protected\def\endparser{%
+ \ifnum\@parsern=0
+ \errmessage{parser misuse: no stages}%
+ \fi
+ \ifx\@stageasn\empty \else
+ \errmessage{parser misuse: last stage must have no assignment}%
+ \fi
+ \@prevstage\iffalse % Define last stage with no linkage.
+ \csletcs\@parsername{\@parsername:0}%
+}
+
% White space control
\def\^^M{\ } % control <return> = control <space>
\def\^^I{\ } % control <tab> = control <space>
@@ -110,9 +203,6 @@
% assignment (like \afterassignment, but with multiple tokens). For box
% register assignments, the tokens are placed after the entire assignment,
% unlike with \afterassignment. \deferasn does not use any temporary registers.
-\newcount\@grouplevel
-\def\@checkgrouplevel{\ifnum\currentgrouplevel>\@grouplevel \ea\aftergroup \fi}
-\def\@savegrouplevel{\@grouplevel\currentgrouplevel}
\def\@deferasnA{\@checkgrouplevel\@deferasnB}
\protected\def\deferasn#1{%
\@savegrouplevel