summaryrefslogtreecommitdiff
path: root/macros/generic/expkv-bundle/pkg-pop.tex
diff options
context:
space:
mode:
Diffstat (limited to 'macros/generic/expkv-bundle/pkg-pop.tex')
-rw-r--r--macros/generic/expkv-bundle/pkg-pop.tex439
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}