diff options
Diffstat (limited to 'macros/generic/expkv-bundle/pkg-pop.tex')
-rw-r--r-- | macros/generic/expkv-bundle/pkg-pop.tex | 439 |
1 files changed, 439 insertions, 0 deletions
diff --git a/macros/generic/expkv-bundle/pkg-pop.tex b/macros/generic/expkv-bundle/pkg-pop.tex new file mode 100644 index 0000000000..e34d0c5d1f --- /dev/null +++ b/macros/generic/expkv-bundle/pkg-pop.tex @@ -0,0 +1,439 @@ +\genericekv\expkvp{-pop} + +The \expkvp\ is mainly written to lay the basis for \expkvc's and \expkvd's +key-defining front ends. Historically the two packages shared pretty similar +code. To unify this and reduce the overall code amount some auxiliary package +was originally planned, but then I realised that with little to no overhead +(apart from documentation) this can also be provided to users. Well, and then I +thought, why not make the whole thing expandable as well. And here we are. + +So what's the idea? This package provides a \textbf{p}refix \textbf{o}riented +\textbf{p}arser\footnote{Naming packages is hard, especially when the name +should fit a particular naming scheme. Big thanks to samcarter for helping +me: \url{https://topanswers.xyz/tex?q=1985}. The author apologises that there is +no \texttt{expkv-pnk}, \texttt{expkv-rok}, \texttt{expkv-jaz} or any other music +themed name in \expkvbundle.} with two kinds of prefixes. The first is called a +\prefix, of which an item can have arbitrary many, the second a \type, of which +every item has only one. To distinguish the concept of an optional \prefix\ from +the generic term \enquote{prefix} I'll use this formatting whenever the special +kind of prefix is meant. + +Another peculiarity of \expkvp\ compared to the other packages in +\expkvbundle\ is that it doesn't separate \Nkey{}s from \Vkey{}s as +strictly. Instead a |NoVal|-marker is used as the value. If this is not what you +want you can use \cs{ekvpValueAlwaysRequired} (see there). + + +\subsection{Parsing Rules} + +A parser is processing only the \key\ of a \kv\ pair. The \key\ is split at +spaces (braces might be lost by this process!). Each split off piece is checked +whether it's a defined prefix. If it's a \type\ parsing of the \key\ stops and +the remainder is considered a \meta{name}. If it's a \prefix\ it'll be recorded +and parsing goes on. If it's neither parsing is also stopped (and the last +parsed space delimited part is put back -- braces might've been lost at this +step). If a no-type rule has been defined (\cs{ekvpDefNoType}) that one is +executed else an error is thrown. + +The \prefix\ or \type\ has to match after being |\detokenize|d, whereas the +\meta{name} will be unchanged (except for stripping off the prefixes). If only a +\key\ is given (so no =\val\ used) the same is done, and instead of \val\ a +no-value marker is used (if that accidentally ends up in the input stream this +looks like this: \csname c\string_novalue\string_tl\endcsname; this is the same +as the marker used by \pkg{expl3} for those familiar with it). + +A \prefix\ has two parts to it, a \meta{pre} and a \meta{post} code, whereas a +\type\ only results in a \type-action (or the no-type action if that's defined +and no \type\ found). The parsing result can also be seen in +\autoref{fig:p:result}. + +\begin{figure}[b] + \centering + \def\plevel#1{\raisebox{1ex}{#1}}% + \def\ts#1{\textsubscript{$#1$}}% + \begingroup + \ttfamily + pre\ts1 + \plevel + {% + pre\ts2 + \plevel + {% + \ldots\space + \plevel + {% + pre\ts n + \plevel{\type-action} + post\ts n + }% + \ldots\space + }% + post\ts2 + }% + post\ts{1}% + \endgroup + \caption + {% + Structure of a single \kv\ pair's parsing result with $n$~\prefix{}es% + \label{fig:p:result}% + } +\end{figure} + +Please note that \expkvp's parsers are fully expandable as long as your +\prefixes\ and \types\ are. Additionally \expkvp\ doesn't provide means to +define parsers, \prefixes, or \types\ |\protected|. As a result, make sure +you'll always call |\ekvpParse| inside of a |\protected| macro if you need +anything that's unexpandable or else your code might not do what you intended +since some states may not be updated when expandable code tries to access them. +The macro |\ekvpProtect| can help to overcome this issue, but it's more of a +last resort than a clean solution. + +\subsection{Defining Parsers} + +\begin{function}{\ekvpNewParser} + \begin{syntax} + \cs{ekvpNewParser}\marg{parser} + \end{syntax} + Defines a new parser called \meta{parser}. Every parser has to be defined this + way. Throws an error if the parser is already defined. +\end{function} + +\begin{function}{\ekvpDefType} + \begin{syntax} + \cs{ekvpDefType}\marg{parser}\marg{type}\marg{code} + \end{syntax} + Defines a \type\ that is called \meta{type} for the parser \meta{parser}. If + the \type\ is parsed the \meta{code} will be used as a \type-action. Inside of + \meta{code} you can use |#1| to refer to the \meta{name} (the remainder of the + \key\ after stripping off all the prefixes), |#2| to use the unaltered \key, + and |#3| to access the \val\ which was given to your \key. +\end{function} + +\begin{function}{\ekvpDefPrefix} + \begin{syntax} + \cs{ekvpDefPrefix}\marg{parser}\marg{prefix}\marg{pre}\marg{post} + \end{syntax} + Defines a \prefix\ that is called \meta{prefix} for the parser \meta{parser}. + If the \prefix\ is encountered the code in \meta{pre} will be put before the + \type-action and the code in \meta{post} will be put behind it. + If multiple \prefix{}es are used the \meta{pre} of the first will be put first + and the \meta{post} of the first will be put last. Inside of \meta{pre} and + \meta{post} |#1| is replaced with the found \type, |#2| the \meta{name}, and + |#3| the unaltered \key. If no valid type was found and the no-type rule + defined with \cs{ekvpDefNoType} is executed the argument |#1| will be empty. +\end{function} + +\begin{function}{\ekvpDefAutoPrefix} + \begin{syntax} + \cs{ekvpDefAutoPrefix}\marg{parser}\marg{pre}\marg{post} + \end{syntax} + You can also define a \prefix-like rule that is executed on each element + automatically. So the \meta{pre} and \meta{post} code of this will be inserted + for every valid element of the \kv\ list. Just like for \cs{ekvpDefPrefix} you + can access the \type\ with |#1|, the \meta{name} with |#2|, and the unaltered + \key\ with |#3|. +\end{function} + +\begin{function}{\ekvpDefPrefixStore} + \begin{syntax} + \cs{ekvpDefPrefixStore}\marg{parser}\marg{prefix}\meta{cs}\marg{pre}\marg{post} + \end{syntax} + This is a shortcut to define a \prefix\ named \meta{prefix} for \meta{parser} + that'll store \meta{pre} inside of \meta{cs} (which should be a single control + sequence) before the \type-action and afterwards store \meta{post} in it. Both + definitions (in \meta{pre} and in \meta{post}) are put inside |\ekvpProtect|. +\end{function} + +\begin{function}{\ekvpDefPrefixLet} + \begin{syntax} + \cs{ekvpDefPrefixLet}\marg{parser}\marg{prefix}\meta{cs}\meta{pre}\meta{post} + \end{syntax} + This is similar to \cs{ekvpDefPrefixStore}, but instead of storing in the + \meta{cs} it'll be let to the single tokens specified by \meta{pre} and + \meta{post}. If either \meta{pre} or \meta{post} contains more than a single + token the remainder is put after the |\let| statement. Both assignments (in + \meta{pre} and in \meta{post}) are put inside |\ekvpProtect|. +\end{function} + +\begin{function}{\ekvpLet} + \begin{syntax} + \cs{ekvpLet}\marg{parser_1}\marg{type}\marg{name_1}\oarg{parser_2}\marg{name_2} + \end{syntax} + Copies the definition of a \prefix\ or \type. The \meta{type} should be one of + |prefix|, or |type|. The \prefix\ or \type\ \meta{name_1} for \meta{parser_1} + will be let equal to the \prefix\ or \type\ \meta{name_2} of \meta{parser_2}. + If you omit the optional \meta{parser_2} it will default to \meta{parser_1}. +\end{function} + + +\subsection{Changing Default Behaviours} + +\begin{function}{\ekvpValueAlwaysRequired} + \begin{syntax} + \cs{ekvpValueAlwaysRequired}\marg{parser} + \end{syntax} + By default a special no-value marker will be provided for \val\ if no value + was given to a key. If this is used instead an error will be thrown that a + value is required. +\end{function} + +\begin{function}{\ekvpDefNoValue} + \begin{syntax} + \cs{ekvpDefNoValue}\marg{parser}\marg{code} + \end{syntax} + This is a third alternative to the default behaviour and + \cs{ekvpValueAlwaysRequired}. With this macro you can stop normal parsing if + no value was specified and instead run \meta{code}. Inside of \meta{code} the + unprocessed \Nkey\ is available as |#1|. No further processing of this \kv\ + list element takes place. +\end{function} + +\begin{function}{\ekvpUseNoValueMarker} + \begin{syntax} + \cs{ekvpUseNoValueMarker}\marg{parser}\marg{marker} + \end{syntax} + This macro changes the no-value marker from the package default to + \meta{marker}. Note that macros like \cs{ekvpAssertValue} don't work with + markers different from the default one. +\end{function} + +\begin{function}{\ekvpDefNoValuePrefix} + \begin{syntax} + \cs{ekvpDefNoValuePrefix}\marg{parser}\marg{pre}\marg{post} + \end{syntax} + It is also possible to handle \Nkey{}s as if this was some special \prefix. + So if a \Nkey\ is encountered you'll have \meta{pre} and \meta{post} put + before and behind the \type-action (as the outermost \prefix). The no-value + marker will be forwarded as \val. If you want to change a parser's marker and + use this you have to use \cs{ekvpUseNoValueMarker} before calling + \cs{ekvpDefNoValuePrefix}, and you must not use \cs{ekvpDefNoValue} or + \cs{ekvpValueAlwaysRequired} before using \cs{ekvpDefNoValuePrefix} (both + result in undefined behaviour). +\end{function} + +\begin{function}{\ekvpDefNoType} + \begin{syntax} + \cs{ekvpDefNoType}\marg{parser}\marg{code} + \end{syntax} + This defines an action if no valid \type\ was found, otherwise this behaves + like \cs{ekvpDefType} with the same arguments |#1| (\meta{name}), |#2| + (unaltered \key), and |#3| (\val) in \meta{code}. If this isn't used for the + \meta{parser} instead an error will be thrown whenever no \type\ is found. +\end{function} + + +\subsection{Markers} + +\expkvp\ will place three markers for each list element that was parsed and +defines an auxiliary to gobble up to that marker. After each marker an +additional group is placed containing the current list element (excluding the +\val). The gobblers gobble that group as well. Those markers are: + +\begin{function}{\ekvpEOP,\ekvpGobbleP} + Is placed after all the \prefix{}es' \meta{pre} code, directly before the + \type-action. +\end{function} + +\begin{function}{\ekvpEOT,\ekvpGobbleT} + Is placed after the \type-action, directly before the last \prefix's + \meta{post} code. +\end{function} + +\begin{function}{\ekvpEOA,\ekvpGobbleA} + Is placed at the end of the complete result of the current element, so after + all the \prefix{}es' \meta{post} code. +\end{function} + + +\subsection{Helpers in Actions} + +\begin{function}{\ekvpIfNoVal} + \begin{syntax} + \cs{ekvpIfNoVal}\marg{arg}\marg{true}\marg{false} + \end{syntax} + This will expand to \meta{true} if the \meta{arg} is the special no-value + marker, otherwise \meta{false} is left in the input stream. +\end{function} + +\begin{function}{\ekvpAssertIf,\ekvpAssertIfNot} + \begin{syntax} + \cs{ekvpAssertIf}\oarg{marker}\marg{if}\marg{message} + \end{syntax} + This macro will run the \TeX-if test specified by \meta{if} (should expand to + any \TeX-style if, e.g., \cs[no-index]{iftrue} or + \cs[no-index]{ifx}\meta{A}\meta{B}). If the test is true everything's fine, + else an error message is thrown using \meta{message} followed by the current + element and everything up to \meta{marker} is gobbled (\meta{marker} can be + any of |EOT|, which is the default, |EOP|, or |EOA|). The |Not| variant will + invert the logic, so if the \TeX-style if is true this will throw the error. +\end{function} + +\begin{function}{\ekvpAssertTF,\ekvpAssertTFNot} + \begin{syntax} + \cs{ekvpAssertTF}\oarg{marker}\marg{if}\marg{message} + \end{syntax} + This is pretty similar to \cs{ekvpAssertIf}, but \meta{if} should be a test + that uses its first argument if it's true and its second otherwise (so an + error is thrown if the second argument is used, nothing happens otherwise). + The |Not| variant is again inversed. +\end{function} + +\begin{function}{\ekvpAssertValue,\ekvpAssertNoValue} + \begin{syntax} + \cs{ekvpAssertValue}\oarg{marker}\marg{arg} + \end{syntax} + Asserts that \meta{arg} is not the no-value marker (\cs{ekvpAssertValue}) or + is the no-value marker (\cs{ekvpAssertNoValue}), otherwise throws an error and + gobbles everything up to \meta{marker} (like \cs{ekvpAssertIf}). +\end{function} + +\begin{function}{\ekvpAssertOneValue,\ekvpAssertTwoValues} + \begin{syntax} + \cs{ekvpAssertOneValue}\oarg{marker}\marg{arg} + \end{syntax} + Asserts that \meta{arg} contains exactly one or two values (which could both + be either single tokens or braced groups -- spaces between the two values in + the \cs{ekvpAssertTwoValues} case are ignored), if the number of values + doesn't match an error is thrown and everything up to \meta{marker} gobbled. +\end{function} + +\begin{function}{\ekvpProtect} + \begin{syntax} + \cs{ekvpProtect}\marg{code} + \end{syntax} + This macro protects \meta{code} from further expanding in every context a + |\protected| macro wouldn't expand. It needs at least two steps of expansion. + When a |\protected| macro would expand this will remove the braces around + \meta{code} and \TeX\ will process \meta{code} the same way it normally would. + After the first step of expansion it'll leave two macros, and after each + further full expansion these two macros stay there. Since \expkvp\ offers no + method to define \prefixes\ or \types\ |\protected| you can instead use this + macro. But if your parser needs any assignments you should nest the + |\ekvpParse| call in a |\protected| macro anyway. +\end{function} + +\subsection{Using Parsers} + +\begin{function}{\ekvpParse} + \begin{syntax} + \cs{ekvpParse}\marg{parser}\kvarg + \end{syntax} + Parses the \kv\ list as defined for \meta{parser}. This expands in exactly two + steps, and returns inside of |\unexpanded|, so doesn't expand any further in + an |\edef| or |\expanded|. After the two steps it'll directly leave the code + as though every \prefix's \meta{pre} and \meta{post} code and the \type-action + were input directly along with the different markers. +\end{function} + +\subsection{The Boring Macros} +Just for the sake of completeness. +\begin{function}{\ekvpDate,\ekvpVersion} + Store the date and version, respectively. +\end{function} + +\subsection{Examples} +\begin{example}{Defining a parser similar to \expkvd} + \ekvset{enverb}{store,no-tcb}% + Let's define a parser that is similar to \expkvd's |\ekvdefinekeys|. First we + define a new parser named |exdef|: + \begin{enverb} + \ekvpNewParser{exdef} + \end{enverb} + We'll need to provide our prefixes, |long| and |protected|. The following + initialises them and defines their action. + \begin{enverb} + \newcommand*\exLong{} + \newcommand*\exProtected{} + \ekvpDefPrefixLet{exdef}{long}\exLong\long\empty + \ekvpDefPrefixLet{exdef}{protected}\exProtected\protected\empty + \end{enverb} + Now we define a few types, I'll go with |noval|, |store|, and |code| for + starters. We exploit the fact that |\ekvdef| and |\ekvdefNoVal| will expand + the first argument, so we can simply store the set name in a macro. + \begin{enverb} + \ekvpDefType{exdef}{code} + {% + \ekvpAssertValue{#3}% + \exProtected\exLong\ekvdef\exSetName{#1}{#3}% + } + \ekvpDefType{exdef}{noval} + {% + \ekvpAssertValue{#3}% + \ekvpAssertIfNot{\ifx\exLong\long}{`long' not accepted}% + \exProtected\ekvdefNoVal\exSetName{#1}{#3}% + } + \ekvpDefType{exdef}{store} + {% + \ekvpAssertOneValue{#3}% + \ifdefined#3\else + \let#3\empty + \fi + \protected\exLong\ekvdef\exSetName{#1}{\edef#3{\unexpanded{##1}}}% + } + \end{enverb} + Now we need a user facing macro that puts the pieces together (this uses + |\NewDocumentCommand| instead of |\newcommand| because the former defines + macros |\protected|). + \begin{enverb} + \NewDocumentCommand\exdefinekeys{m +m} + {\def\exSetName{#1}\ekvpParse{exdef}{#2}} + \end{enverb} + Now we got that sorted so we can use our little parser: + \ekvset{enverb}{no-store,undo-no-tcb}% + \begin{enverb}[restore] + \newif\ifbar + \exdefinekeys{exampleset} + { + long store foo = \myfoo + ,protected noval bar = \bartrue + ,code baz = baz was called with \detokenize{#1} + } + \ekvset{exampleset}{foo=Foobar,bar,baz=\empty} + \ifbar bar was true so: \myfoo\fi + \end{enverb} +\end{example} + +\begin{example}{Visualising the expandability of \cs[no-index]{ekvpParse}} + \ekvset{enverb}{store,no-tcb}% + With this example we want to take a closer look at the expansion of + |\ekvpParse|. So please bear with me if this doesn't make much sense + otherwise. One of the issues is that \prefixes\ are a somewhat unordered + concept, and only \types\ must come last. We could juggle with flags (an + expandable data-storage, see \autoref{sec:c:flags}) to overcome this somehow + just to define some pseudo-syntax here, but I guess that's not worth it. + Anyhow, here goes nothing.\par + First we need a parser + \begin{enverb} + \ekvpNewParser{exexp} + \end{enverb} + and a \prefix. We could define one that ensures the name starts of with + a letter. We also want each element to start a new line, which we do using an + auto prefix. + \begin{enverb} + \newcommand\ifletter{} + \long\def\ifletter#1#2\stop{\ifcat a\noexpand#1} + \ekvpDefPrefix{exexp}{letter} + {\ekvpAssertIf{\ifletter#2\stop}{not a letter}} + { (really a letter)} + \ekvpDefAutoPrefix{exexp}{\noindent}{\par} + \end{enverb} + Finally we define a \type\ and a |NoType| rule: + \begin{enverb} + \ekvpDefType{exexp}{*}{$#1\cdot#3 = \the\numexpr#1*#3\relax$} + \ekvpDefNoType{exexp}{the #3th letter is #1} + \end{enverb} + Now we want to see whether the thing is expandable: + \ekvset{enverb}{no-store,undo-no-tcb}% + \begin{enverb}[restore,below] + \raggedright + \edef\foo{\ekvpParse{exexp}{letter e = 5, * 4 = \empty3}} + 1st full expansion + \texttt{\meaning\foo}\par + \medskip + \edef\foo{\foo} + 2nd full expansion + \texttt{\meaning\foo}\par + \medskip + \foo + \end{enverb} +\end{example} |