From f1991724088484845cc165c43948922dc85da9bf Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Sat, 10 Apr 2021 21:10:10 +0000 Subject: expkv (10apr21) git-svn-id: svn://tug.org/texlive/trunk@58815 c570f23f-e606-0410-a88d-b1316a301751 --- Master/texmf-dist/source/latex/expkv/expkv.dtx | 851 ++++++++++++++++++------- 1 file changed, 630 insertions(+), 221 deletions(-) (limited to 'Master/texmf-dist/source/latex/expkv') diff --git a/Master/texmf-dist/source/latex/expkv/expkv.dtx b/Master/texmf-dist/source/latex/expkv/expkv.dtx index d3fc414f393..49af99c01d4 100644 --- a/Master/texmf-dist/source/latex/expkv/expkv.dtx +++ b/Master/texmf-dist/source/latex/expkv/expkv.dtx @@ -1,6 +1,6 @@ % \iffalse meta-comment % -% File: expkv.dtx Copyright (C) 2020 Jonathan P. Spratte +% File: expkv.dtx Copyright (C) 2020-2021 Jonathan P. Spratte % % This work may be distributed and/or modified under the conditions of the % LaTeX Project Public License (LPPL), either version 1.3c of this license or @@ -27,7 +27,7 @@ Released under the LaTeX Project Public License v1.3c or later See http://www.latex-project.org/lppl.txt -------------------------------------------------------------- -Copyright (C) 2020 Jonathan P. Spratte +Copyright (C) 2020-2021 Jonathan P. Spratte This work may be distributed and/or modified under the conditions of the LaTeX Project Public License (LPPL), either version 1.3c of this license or @@ -84,10 +84,18 @@ and the derived files expkv.pdf ,basicstyle=\fontfamily{jkp}\itshape ,morekeywords=^^A {^^A - \ekvdef,\ekvdefNoVal,\ekvlet,\ekvletNoVal,\ekvletkv,\ekvletkvNoVal,^^A - \ekvset,\ekvparse,\ekvVersion,\ekvDate,\ekvifdefined,^^A - \ekvifdefinedNoVal,\ekvbreak,\ekvbreakPreSneak,\ekvbreakPostSneak,^^A - \ekvsneak,\ekvsneakPre,\ekvchangeset + \ekvdef,\ekvdefNoVal,^^A + \ekvlet,\ekvletNoVal,\ekvletkv,\ekvletkvNoVal,^^A + \ekvdefunknown,\ekvdefunknownNoVal,^^A + \ekvredirectunknown,\ekvredirectunknownNoVal,^^A + \ekvset,\ekvsetSneaked,^^A + \ekvsetdef,\ekvsetSneakeddef,\ekvsetdefSneaked,^^A + \ekvparse,^^A + \ekvVersion,\ekvDate,^^A + \ekvifdefined,\ekvifdefinedNoVal,\ekvifdefinedset,^^A + \ekvbreak,\ekvbreakPreSneak,\ekvbreakPostSneak,^^A + \ekvsneak,\ekvsneakPre,^^A + \ekvchangeset } ,morecomment=[l]\% ,commentstyle=\color[gray]{0.4} @@ -103,6 +111,7 @@ and the derived files expkv.pdf \definecolor{expkvred}{HTML}{9F393D} \colorlet{expkvgrey}{black!75} \makeatletter +\newcommand*\example{\par\smallskip\noindent\textit{Example:} \ignorespaces} \newcommand*\expFormat {^^A {^^A @@ -164,6 +173,31 @@ and the derived files expkv.pdf \newcommand*\expkvd{\expkvpkg{def}} \newcommand*\expkvc{\expkvpkg{cs}} \newcommand*\expkvo{\expkvpkg{opt}} +\ExplSyntaxOn +\newcommand*\pkglogo[1] + { + \texorpdfstring + { + \mbox + { + \BeginAccSupp{ActualText=#1} + \rmfamily + \str_case:nn {#1} + { + {yax} + { + Y\kern-.1em + \raise.15em\hbox{\scshape a} + \kern-.1em \lower.15em\hbox{X}% + } + } + \EndAccSupp{} + } + } + {#1} + } +\newcommand*\yax{\pkglogo{yax}} +\ExplSyntaxOff \newcommand\kv{\meta{key}=\meta{value}} \newcommand\key{\meta{key}} \newcommand\val{\meta{value}} @@ -173,6 +207,7 @@ and the derived files expkv.pdf \DoNotIndex{\endgroup,\endinput,\errmessage,\expandafter,\input,\let,\long} \DoNotIndex{\protected,\ProvidesFile,\ProvidesPackage,\relax,\space} \DoNotIndex{\@,\unexpanded,\string,\expanded,\detokenize,\meaning,\lastnamedcs} +\DoNotIndex{\romannumeral,\z@} \DoNotIndex{\ifcsname} \DoNotIndex{\ifx} \DoNotIndex{\else} @@ -257,6 +292,9 @@ and the derived files expkv.pdf % Note that while the package names are stylised with a vertical rule, their % names are all lower case with a hyphen (\emph{e.g.}, \file{expkv-def}). % +% A list of concise comparisons to other \kv\ packages is contained in +% \autoref{sec:cmp}. +% % \subsection{Setting up Keys}\label{sec:define} % \expkv\ provides a rather simple approach to setting up keys, similar to % \pkg{keyval}. However there is an auxiliary package named \expkvd\ which @@ -285,6 +323,10 @@ and the derived files expkv.pdf % Defines a \key\ taking a value in a \set\ to expand to \meta{code}. In % \meta{code} you can use |#1| to refer to the given value. % \end{function} +% \example Define |text| in |foo| to store the value inside |\foo@text|: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \protected\long\ekvdef{foo}{text}{\def\foo@width{#1}} +% \end{lstlisting} % % \begin{function}{\ekvdefNoVal} % \begin{syntax} @@ -292,6 +334,10 @@ and the derived files expkv.pdf % \end{syntax} % Defines a no value taking \key\ in a \set\ to expand to \meta{code}. % \end{function} +% \example Define |bool| in |foo| to set |\iffoo@bool| to |true|: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \protected\ekvdefNoVal{foo}{bool}{\foo@booltrue} +% \end{lstlisting} % % \begin{function}{\ekvlet} % \begin{syntax} @@ -300,6 +346,10 @@ and the derived files expkv.pdf % Let the value taking \key\ in \set\ to \meta{cs}, there are no checks on % \meta{cs} enforced. % \end{function} +% \example Let |cmd| in |foo| do the same as |\foo@cmd|: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \ekvlet{foo}{cmd}\foo@cmd +% \end{lstlisting} % % \begin{function}{\ekvletNoVal} % \begin{syntax} @@ -308,6 +358,7 @@ and the derived files expkv.pdf % Let the no value taking \key\ in \set\ to \meta{cs}, it is not checked % whether \meta{cs} exists or that it takes no parameter. % \end{function} +% \example See above. % % \begin{function}{\ekvletkv} % \begin{syntax} @@ -317,6 +368,10 @@ and the derived files expkv.pdf % whether that second key exists (but take a look at % \cs[no-index]{ekvifdefined}). % \end{function} +% \example Let |B| in |bar| be an alias for |A| in |foo|: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \ekvletkv{bar}{B}{foo}{A} +% \end{lstlisting} % % \begin{function}{\ekvletkvNoVal} % \begin{syntax} @@ -326,6 +381,7 @@ and the derived files expkv.pdf % whether that second key exists (but take a look at % \cs[no-index]{ekvifdefinedNoVal}). % \end{function} +% \example See above. % % \begin{function}{\ekvdefunknown} % \begin{syntax} @@ -335,9 +391,19 @@ and the derived files expkv.pdf % this macro you can define \meta{code} that will be executed for a given % \set\ when an unknown \key\ with a \val\ was encountered instead of throwing % an error. You can refer to the given \val\ with |#1| and to the unknown -% \key's name with |#2| in \meta{code}.\footnotemark +% \key's name with |#2| in \meta{code}.\footnotemark{} +% |\ekvdefunknown| and |\ekvredirectunknown| are mutually exclusive, +% you can't use both. % \end{function} % \footnotetext{That order is correct, this way the code is faster.} +% \example Also search |bar| for undefined keys of set |foo|: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \long\ekvdefunknown{foo}{\ekvset{bar}{#2={#1}}} +% \end{lstlisting} +% This example differs from using |\ekvredirectunknown{foo}{bar}| (see below) in +% that also the unknown-key handler of the |bar| set will be triggered, error +% messages for undefined keys will look different, and this is slower than using +% |\ekvredirectunknown|. % % \begin{function}{\ekvdefunknownNoVal} % \begin{syntax} @@ -347,7 +413,49 @@ and the derived files expkv.pdf % an error when encountering an unknown \key. With this you can instead let it % execute \meta{code} if an unknown |NoVal| \key\ was encountered. You can % refer to the given \key\ with |#1| in \meta{code}. +% |\ekvdefunknownNoVal| and |\ekvredirectunknownNoVal| are mutually exclusive, +% you can't use both. % \end{function} +% \example Also search |bar| for undefined keys of set |foo|: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \ekvdefunknownNoVal{foo}{\ekvset{bar}{#1}} +% \end{lstlisting} +% +% \begin{function}{\ekvredirectunknown} +% \begin{syntax} +% \cs{ekvredirectunknown}\marg{set}\marg{set-list} +% \end{syntax} +% This is a short cut to set up a special |\ekvdefunknown| for \set\ that will +% check each set in the comma separated \meta{set-list} for the unknown \key. +% You can't use prefixes (so no |\long| or |\protected|) with this macro, the +% resulting unknown-key handler will always be |\long|. The first set in the +% \meta{set-list} has highest priority. Once the \key\ is found the remaining +% sets are discarded, if the \key\ isn't found in any set an error will be +% thrown eventually. Note that the error messages are affected by the use of +% this macro, in particular, it isn't checked whether a |NoVal| key of the +% same name is defined in order to throw a |value forbidden| error. +% |\ekvdefunknown| and |\ekvredirectunknown| are mutually exclusive, +% you can't use both. +% \end{function} +% \example For every key not defined in the set |foo| also search the sets |bar| +% and |baz|: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \ekvredirectunknown{foo}{bar, baz} +% \end{lstlisting} +% +% \begin{function}{\ekvredirectunknownNoVal} +% \begin{syntax} +% \cs{ekvredirectunknownNoVal}\marg{set}\marg{set-list} +% \end{syntax} +% This behaves just like |\ekvredirectunknown| and does the same but for the +% |NoVal| keys. Again no prefixes are supported. Note that the error messages +% are affected by the use of this macro, in particular, it isn't checked +% whether a normal key of the same name is defined in order to throw a +% |value forbidden| error. +% |\ekvdefunknownNoVal| and |\ekvredirectunknownNoVal| are mutually exclusive, +% you can't use both. +% \end{function} +% \example See above. % % \subsection{Parsing Keys} % @@ -367,6 +475,10 @@ and the derived files expkv.pdf % be split at the first one and the others are considered part of the value. % |\ekvset| should be nestable. % \end{function} +% \example Parse |key=arg, key| in the set |foo|: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \ekvset{foo}{key=arg, key} +% \end{lstlisting} % % \begin{function}{\ekvsetSneaked} % \begin{syntax} @@ -377,6 +489,10 @@ and the derived files expkv.pdf % \cs[no-index]{ekvsneak} has been called with \meta{sneak} as its argument as % the first action. % \end{function} +% \example Parse |key=arg, key| in the set |foo| with |\afterwards| sneaked out: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \ekvsetSneaked{foo}{\afterwards}{key=arg, key} +% \end{lstlisting} % % \begin{function}{\ekvsetdef} % \begin{syntax} @@ -389,6 +505,12 @@ and the derived files expkv.pdf % \texttt % {\string\long\string\def\meta{cs}\#1\{\string\ekvset\marg{set}\{\#1\}\}} % \end{function} +% \example Define the macro |\foosetup| to parse keys in the set |foo| and use +% it to parse |key=arg, key|: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \ekvsetdef\foosetup{foo} +% \foosetup{key=arg, key} +% \end{lstlisting} % % \begin{function}{\ekvsetSneakeddef} % \begin{syntax} @@ -406,10 +528,17 @@ and the derived files expkv.pdf % \string\ekvsetSneaked\marg{set}\{\#1\}\{\#2\}\}^^A % } % \end{function} +% \example Define the macro |\foothings| to parse keys in the set |foo| and +% accept a sneaked argument, then use it to parse |key=arg, key| and sneak +% |\afterwards|: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \ekvsetSneakeddef\foothings{foo} +% \foothings{\afterwards}{key=arg, key} +% \end{lstlisting} % % \begin{function}{\ekvsetdefSneaked} % \begin{syntax} -% \cs{ekvsetSneakeddef}\meta{cs}\marg{set}\marg{sneaked} +% \cs{ekvsetdefSneaked}\meta{cs}\marg{set}\marg{sneaked} % \end{syntax} % And this one behaves like \cs[no-index]{ekvsetSneakeddef} but with a fixed % \meta{sneaked} argument. So the resulting macro is faster than but else @@ -420,6 +549,12 @@ and the derived files expkv.pdf % \string\ekvsetSneaked\marg{set}\marg{sneaked}\{\#1\}\}^^A % } % \end{function} +% \example Define the macro |\barthing| to parse keys in the set |bar| and +% always execute |\afterwards| afterwards, then use it to parse |key=arg, key|: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \ekvsetdefSneaked\barthing{bar}{\afterwards} +% \barthing{key=arg, key} +% \end{lstlisting} % % \begin{function}{\ekvparse} % \begin{syntax} @@ -449,8 +584,7 @@ and the derived files expkv.pdf % you can put an \cs[no-index]{expanded} around it and will still be faster % since you need only a single \cs[no-index]{expandafter} this way.^^A % } -% -% As a small example: +% \example % \begin{lstlisting} % \ekvparse{\handlekey{S}}{\handlekeyval{S}}{foo = bar, key, baz={zzz}} % \end{lstlisting} @@ -472,15 +606,14 @@ and the derived files expkv.pdf % \handle\kv{foo}{bar}\k{key}\kv{baz}{zzz} % \end{lstlisting} % -% \subsection{Miscellaneous} -% -% \subsubsection{Other Macros} +% \subsection{Other Macros} % % \expkv\ provides some other macros which might be of interest. % % \begin{function}{\ekvVersion,\ekvDate} % These two macros store the version and date of the package. % \end{function} +% % \begin{function}{\ekvifdefined,\ekvifdefinedNoVal} % \begin{syntax} % \cs{ekvifdefined}\marg{set}\marg{key}\marg{true}\marg{false} @@ -490,6 +623,11 @@ and the derived files expkv.pdf % either a hash table entry doesn't exist for that key or its meaning is % |\relax|. % \end{function} +% \example Check whether the key |special| is already defined in set |foo|, if +% it isn't input a file that contains more key definitions: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \ekvifdefined{foo}{special}{}{\input{foo.morekeys.tex}} +% \end{lstlisting} % % \begin{function}{\ekvifdefinedset} % \begin{syntax} @@ -498,6 +636,12 @@ and the derived files expkv.pdf % This macro tests whether \set\ is defined (which it is if at least one key % was defined for it). If it is \meta{true} will be run, else \meta{false}. % \end{function} +% \example Check whether the set |VeRyUnLiKeLy| is already defined, if so throw +% an error, else do nothing: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \ekvifdefinedset{VeRyUnLiKeLy} +% {\errmessage{VeRyUnLiKeLy already defined}}{} +% \end{lstlisting} % % \begin{function}{\ekvbreak,\ekvbreakPreSneak,\ekvbreakPostSneak} % \begin{syntax} @@ -510,6 +654,12 @@ and the derived files expkv.pdf % \meta{after} before anything that has been smuggled and |\ekvbreakPostSneak| % will put \meta{after} after the stuff that has been sneaked out. % \end{function} +% \example Define a key |abort| that will stop key parsing inside the set |foo| +% and execute |\foo@aborted|, or if it got a value |\foo@aborted@with|: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \ekvdefNoVal{foo}{abort}{\ekvbreak{\foo@aborted}} +% \ekvdef{foo}{abort}{\ekvbreak{\foo@aborted@with{#1}}} +% \end{lstlisting} % % \begin{function}{\ekvsneak,\ekvsneakPre} % \begin{syntax} @@ -523,6 +673,11 @@ and the derived files expkv.pdf % list everything that has been |\ekvsneak|ed will be left in the input % stream. A small usage example is shown in \autoref{sec:sneakex}. % \end{function} +% \example Define a key |secret| in the set |foo| that will sneak out a macro +% |\foo@secretly@sneaked|: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \ekvdefNoVal{foo}{secret}{\ekvsneak{\foo@secretly@sneaked}} +% \end{lstlisting} % % \begin{function}{\ekvchangeset} % \begin{syntax} @@ -530,17 +685,23 @@ and the derived files expkv.pdf % \end{syntax} % Replaces the current set with \meta{new-set}, so for the rest of the current % |\ekvset| call, that call behaves as if it was called with -% \texttt{\cs[no-index]{ekvset}\marg{new-set}}. Just like |\ekvsneak| this -% reads and reinserts the remainder of the current |\ekvset| macro to do its -% job. It is comparable to using \texttt{\meta{key}/.cd} in \pkg{pgfkeys}. +% \texttt{\cs[no-index]{ekvset}\marg{new-set}}. It is comparable to using +% \texttt{\meta{key}/.cd} in \pkg{pgfkeys}. % \end{function} +% \example Define a key |cd| in set |foo| that will change to another set as +% specified in the value, if the set is undefined it'll stop the parsing and +% throw an error as defined in the macro |\foo@cd@error|: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \ekvdef{foo}{cd} +% {\ekvifdefinedset{#1}{\ekvchangeset{#1}}{\ekvbreak{\foo@cd@error}}} +% \end{lstlisting} % % \bigskip % % \begin{function}{\ekv@name,\ekv@name@set,\ekv@name@key} % \begin{syntax} -% \cs{ekv@name}\marg{set}\marg{key}\\ -% \cs{ekv@name@set}\marg{set}\\ +% \cs{ekv@name}\marg{set}\marg{key} +% \cs{ekv@name@set}\marg{set} % \cs{ekv@name@key}\marg{key} % \end{syntax} % The names of the macros that correspond to a key in a set are build with @@ -556,190 +717,11 @@ and the derived files expkv.pdf % macros outside of \expkv, but \emph{don't} change them! \expkv\ relies on % their exact definitions internally. % \end{function} -% -% \subsubsection{Bugs} -% -% Just like \pkg{keyval}, \expkv\ is bug free. But if you find \pmso{bugs}hidden -% features\footnote{Thanks, David!} you can tell me about them either via mail -% (see the first page) or directly on GitHub if you have an account there: -% \url{https://github.com/Skillmon/tex_expkv} -% -% \subsubsection{Comparisons} -% -% Comparisons of speed are done with a very simple test key and the help of the -% \pkg{l3benchmark} package. The key and its usage should be equivalent to -% \begin{lstlisting} -% \protected\ekvdef{test}{height}{\def\myheight{#1}} -% \ekvsetdef\expkvtest{test} -% \expkvtest{ height = 6 } -% \end{lstlisting} -% and only the usage of the key, not its definition, is benchmarked. For the -% impatient, the essence of these comparisons regarding speed and buggy -% behaviour is contained in \autoref{tab:comp}. -% -% As far as I know \expkv\ is the only fully expandable \kv\ parser. I tried to -% compare \expkv\ to every \kv\ package listed on -% \href{https://ctan.org/topic/keyval}{CTAN}, however, one might notice that -% some of those are missing from this list. That's because I didn't get the -% others to work due to bugs, or because they just provide wrappers around other -% packages in this list. -% -% In this subsubsection is no benchmark of |\ekvparse| and |\keyval_parse:NNn| -% contained, as most other packages don't provide equivalent features to my -% knowledge. |\ekvparse| is slightly faster than |\ekvset|, but keep in mind -% that it does less. The same is true for |\keyval_parse:NNn| compared to -% |\keys_set:nn| of \pkg{expl3} (where the difference is much bigger). Comparing -% just the two, |\ekvparse| is a tad faster than |\keyval_parse:NNn| because of -% the two tests (for empty key names and only a single equal sign) which are -% omitted. -% -% \paragraph{\pkg{keyval}} is about \SIrange{30}{40}{\percent} faster and has a -% comparable feature set just a slightly different way how it handles keys -% without values. That might be considered a drawback, as it limits the -% versatility, but also as an advantage, as it might reduce doubled code. Keep -% in mind that as soon as someone loads \pkg{xkeyval} the performance of -% \pkg{keyval} gets replaced by \pkg{xkeyval}'s. -% -% Also \pkg{keyval} has a bug, which unfortunately can't really be resolved -% without breaking backwards compatibility for \emph{many} documents, namely it -% strips braces from the argument before stripping spaces if the argument isn't -% surrounded by spaces, also it might strip more than one set of braces. Hence -% all of the following are equivalent in their outcome, though the last two -% lines should result in something different than the first two: -% \begin{lstlisting}[belowskip=0pt] -% \setkeys{foo}{bar=baz} -% \setkeys{foo}{bar= {baz}} -% \setkeys{foo}{bar={ baz}} % should be ` baz' -% \setkeys{foo}{bar={{baz}}} % should be `{baz}' -% \end{lstlisting} -% -% \paragraph{\pkg{xkeyval}} is roughly twenty times slower, but it provides more -% functionality, e.g., it has choice keys, boolean keys, and so on. It contains -% the same bug as \pkg{keyval} as it has to be compatible with it by design (it -% replaces \pkg{keyval}'s frontend), but also adds even more cases in which -% braces are stripped that shouldn't be stripped, worsening the situation. -% -% \paragraph{\pkg{ltxkeys}} is no longer compatible with the \LaTeX\ kernel -% starting with the release 2020-10-01. It is over 380 times slower -- which is -% funny, because it aims to be ``[\ldots] faster [\ldots] than these earlier -% packages [referring to \pkg{keyval} and \pkg{xkeyval}].'' It needs more time -% to parse zero~keys than five of the packages in this comparison need to parse -% 100~keys. Since it aims to have a bigger feature set than \pkg{xkeyval}, it -% most definitely also has a bigger feature set than \expkv. Also, it can't -% parse |\long| input, so as soon as your values contain a |\par|, it'll throw -% errors. Furthermore, \pkg{ltxkeys} doesn't strip outer braces at all by -% design, which, imho, is a weird design choice. In addition \pkg{ltxkeys} loads -% \pkg{catoptions} which is known to introduce bugs (e.g., see -% \url{https://tex.stackexchange.com/questions/461783}). Because it is no longer -% compatible with the kernel, I stop benchmarking it (so the numbers listed here -% and in \autoref{tab:comp} regarding \pkg{ltxkeys} were last updated on -% 2020-10-05). -% -% \paragraph{\pkg{l3keys}} is around four and a half times slower, but has an, -% imho, great interface to define keys. It strips \emph{all} outer spaces, even -% if somehow multiple spaces ended up on either end. It offers more features, -% but is pretty much bound to \pkg{expl3} code. Whether that's a drawback is up -% to you. -% -% \paragraph{\pkg{pgfkeys}} is around \num{2.7} times slower for one key if one -% uses the |/|\meta{path}|/.cd| syntax and almost \SI{20}{\percent} slower if -% one uses |\pgfqkeys|, but has an \emph{enormous} feature set. -% To get the best performance |\pgfqkeys| was used in the benchmark. This -% reduces the overhead for setting the base directory of the benchmark keys by -% about \SI{43}{\ops} (so both $p_0$ and $T_0$ would be about \SI{43}{\ops} -% bigger if |\pgfkeys{|\meta{path}|/.cd,|\meta{keys}|}| was used instead). -% It has the same or a very similar bug \pkg{keyval} has. The brace bug (and -% also the category fragility) can be fixed by \pkg{pgfkeyx}, but this package -% was last updated in 2012 and it slows down |\pgfkeys| by factor~\num{8}. Also -% \pkg{pgfkeyx} is no longer compatible with versions of \pkg{pgfkeys} newer -% than 2020-05-25. -% -% \paragraph{\pkg{kvsetkeys} with \pkg{kvdefinekeys}} is about \num{4.4} times -% slower, but it works even if commas and equals have category codes different -% from 12 (just as some other packages in this list). Else the features of the -% keys are equal to those of \pkg{keyval}, the parser has more features, though. -% -% \paragraph{\pkg{options}} is \num{1.7} times slower for only a single value. -% It has a much bigger feature set. Unfortunately it also suffers from the -% premature unbracing bug \pkg{keyval} has. -% -% \paragraph{\pkg{simplekv}} is hard to compare because I don't speak French (so -% I don't understand the documentation). There was an update released on -% 2020-04-27 which greatly improved the package's performance and adds -% functionality so that it can be used more like most of the other \kv\ -% packages. It has problems with stripping braces and spaces in a hard to -% predict manner just like \pkg{keyval}. Also, while it tries to be robust -% against category code changes of commas and equal signs, the used mechanism -% fails if the \kv\ list already got tokenised. Regarding unknown keys it got a -% very interesting behaviour. It doesn't throw an error, but stores the \val\ in -% a new entry accessible with \cs[no-index]{useKV}. Also if you omit \val\ it -% stores |true| for that \key. For up to three keys, \expkv\ is a bit faster, -% for more keys \pkg{simplekv} takes the lead. -% -% \paragraph{\pkg{yax}} is over twenty times slower. It has a pretty strange -% syntax, imho, and again a direct equivalent is hard to define. It has the -% premature unbracing bug, too. Also somehow loading \pkg{yax} broke -% \pkg{options} for me. The tested definition was: -% \begin{lstlisting}[belowskip=0pt] -% \usepackage{yax} -% \defactiveparameter yax {\storevalue\myheight yax:height } % key setup -% \setparameterlist{yax}{ height = 6 } % benchmarked +% \example Execute the callback of the |NoVal| key |key| in set |foo|: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \csname\ekv@name{foo}{key}N\endcsname % \end{lstlisting} % -% \begin{table} -% \def\fnsym{\textcolor{red!80!black}{*}}% -% \sisetup{round-precision=1, round-mode=places}% -% \begingroup -% \centering -% \newcommand*\yes{\textcolor{red!80!black} {yes}}^^A -% \newcommand*\no {\textcolor{green!80!black}{no}}^^A -% \caption[Comparison of \kv\ packages] -% {^^A -% Comparison of \kv\ packages. The packages are ordered from -% fastest to slowest for one \kv\ pair. Benchmarking was done using -% \pkg{l3benchmark} and the scripts in the \file{Benchmarks} folder -% of the \href{https://github.com/Skillmon/tex_expkv}{git repository}. -% The columns $p_i$ are the polynomial coefficients of a linear fit to the -% run-time, $p_0$ can be interpreted as the overhead for initialisation -% and $p_1$ the cost per key. The $T_0$ column is the actual mean ops -% needed for an empty list argument, as the linear fit doesn't match that -% point well in general. The column ``BB'' lists whether the -% parsing is affected by some sort of brace bug, ``CF'' stands for -% category code fragile and lists whether the parsing breaks with active -% commas or equal signs.^^A -% \label{tab:comp}^^A -% } -% \begin{tabular} -% {>{\collectcell\pkg}l<{\endcollectcell}*3{S[table-format=4.1]}ccc} -% \toprule -% \rmfamily Package & {$p_1$} & {$p_0$} & {$T_0$}& BB & CF & Date \\ -% \midrule -% keyval & 13.742 & 1.486 & 7.254 & \yes & \yes & 2014-10-28 \\ -% \expkv & 19.701 & 2.169 & 6.592 & \no & \no & 2020-10-10 \\ -% simplekv & 18.334 & 6.971 & 17.710 & \yes & \yes & 2020-04-27 \\ -% pgfkeys & 24.274 & 1.725 & 10.650 & \yes & \yes & 2020-09-05 \\ -% options & 23.600 & 15.638 & 20.830 & \yes & \yes & 2015-03-01 \\ -% kvsetkeys & {\fnsym} & {\fnsym} & 40.290 & \no & \no & 2019-12-15 \\ -% l3keys & 71.309 & 33.131 & 31.590 & \no & \no & 2020-09-24 \\ -% xkeyval & 253.563 & 202.246 & 168.300 & \yes & \yes & 2014-12-03 \\ -% yax & 421.853 & 157.003 & 114.700 & \yes & \yes & 2010-01-22 \\ -% ltxkeys & 3400.142 & 4737.958 & 5368.000 & \no & \no & 2012-11-17 \\ -% \bottomrule -% \end{tabular} -% \par -% \endgroup -% \medskip -% \fnsym For \pkg{kvsetkeys} the linear model used for the other -% packages is a poor fit, \pkg{kvsetkeys} seems to have approximately -% quadratic run-time, the coefficients of the second degree polynomial fit are -% $p_2=\num{8.240}$, $p_1=\num{44.862}$, and $p_0=\num{60.793}$. Of course the -% other packages might not really have linear run-time, but at least from 1~to -% 20~keys the fits don't seem too bad. If one extrapolates the fits for 100 -% \kv\ pairs one finds that most of them match pretty well, the exception -% being \pkg{ltxkeys}, which behaves quadratic as well with -% $p_2=\num{23.500}$, $p_1=\num{2906.634}$, and $p_0=\num{6547.489}$. -% \end{table} -% % \subsection{Examples} % % \subsubsection{Standard Use-Case} @@ -783,7 +765,7 @@ and the derived files expkv.pdf % \ourmacro{width=5pt}\par % \end{minipage} % -% \paragraph{The same key using \protect\expkvd} +% \paragraph{The same keys using \protect\expkvd} % Using \expkvd\ we can set up the equivalent key using a \kv\ interface, after % the following we could use |\ourmacro| in the same way as above. \expkvd\ will % allocate and initialise |\ourdim| and define the |width| key |\protected| for @@ -799,6 +781,59 @@ and the derived files expkv.pdf % } % \end{lstlisting} % +% \subsubsection{A Macro to Draw Rules} +% +% Another small example could be a \kv\ driven |\rule| alternative, because I +% keep forgetting the correct order of its arguments. First we define the keys +% (and initialize the used macros to store the keys): +% \begin{lstlisting} +% \makeatletter +% \newcommand*\myrule@ht{1ex} +% \newcommand*\myrule@wd{0.1em} +% \newcommand*\myrule@raise{\z@} +% \protected\ekvdef{myrule}{ht}{\def\myrule@ht{#1}} +% \protected\ekvdef{myrule}{wd}{\def\myrule@wd{#1}} +% \protected\ekvdef{myrule}{raise}{\def\myrule@raise{#1}} +% \end{lstlisting} +% Then we define a macro to change the defaults outside of |\myrule| and +% |\myrule| itself: +% \begin{lstlisting} +% \ekvsetdef\myruleset{myrule} +% \newcommand*\myrule[1][]{\begingroup\myruleset{#1}\myrule@out\endgroup} +% \end{lstlisting} +% And finally the output: +% \begin{lstlisting} +% \newcommand*\myrule@out{\rule[\myrule@raise]\myrule@wd\myrule@ht} +% \makeatother +% \end{lstlisting} +% And we can use it:\\ +% \begin{minipage}{7cm} +% \begin{lstlisting} +% a\myrule\par +% a\myrule[ht=2ex,raise=-.5ex]\par +% \myruleset{wd=5pt} +% a\myrule +% \end{lstlisting} +% \end{minipage}^^A +% \begin{minipage}{\dimexpr\linewidth-7cm\relax} +% \makeatletter +% \newcommand*\myrule@ht{1ex} +% \newcommand*\myrule@wd{0.1em} +% \newcommand*\myrule@raise{\z@} +% \protected\ekvdef{myrule}{ht}{\def\myrule@ht{#1}} +% \protected\ekvdef{myrule}{wd}{\def\myrule@wd{#1}} +% \protected\ekvdef{myrule}{raise}{\def\myrule@raise{#1}} +% \ekvsetdef\myruleset{myrule} +% \newcommand*\myrule[1][]{\begingroup\myruleset{#1}\myrule@out\endgroup} +% \newcommand*\myrule@out{\rule[\myrule@raise]\myrule@wd\myrule@ht} +% \makeatother +% a\myrule\par +% a\myrule[ht=2ex,raise=-.5ex]\par +% \myruleset{wd=5pt} +% a\myrule +% \end{minipage} +% +% % \subsubsection{An Expandable \kv\ Macro Using \cs[no-index]{ekvsneak}} % \label{sec:sneakex} % @@ -1004,6 +1039,23 @@ and the derived files expkv.pdf % value forbidden (`', set `'). % \end{lstlisting} % +% If you're using an undefined key in a set for which |\ekvredirectunknown| was +% used, and the key isn't found in any of the other sets as well, you'll get: +% \begin{lstlisting} +% ! Undefined control sequence. +% \! expkv Error: +% no key `' in sets {}{}... +% \end{lstlisting} +% +% If you're using an undefined |NoVal| key in a set for which +% |\ekvredirectunknownNoVal| was used, and the key isn't found in any of the +% other sets as well, you'll get: +% \begin{lstlisting} +% ! Undefined control sequence. +% \! expkv Error: +% no NoVal key `' in sets {}... +% \end{lstlisting} +% % If you're using a set for which you never executed one of the defining macros % from \autoref{sec:define} you'll get a low level \TeX\ error, as that isn't % actively tested by the parser (and hence will lead to undefined behaviour and @@ -1014,6 +1066,195 @@ and the derived files expkv.pdf % \! expkv Error: Set `' undefined. % \end{lstlisting} % +% \subsection{Bugs} +% +% Just like \pkg{keyval}, \expkv\ is bug free. But if you find \pmso{bugs}hidden +% features\footnote{Thanks, David!} you can tell me about them either via mail +% (see the first page) or directly on GitHub if you have an account there: +% \url{https://github.com/Skillmon/tex_expkv} +% +% \subsection{Comparisons}\label{sec:cmp} +% +% This subsection makes some basic comparison between \expkv\ and other \kv\ +% packages. The comparisons are really concise, regarding speed, feature range +% (without listing the features of each package), and bugs and misfeatures. +% +% Comparisons of speed are done with a very simple test key and the help of the +% \pkg{l3benchmark} package. The key and its usage should be equivalent to +% \begin{lstlisting} +% \protected\ekvdef{test}{height}{\def\myheight{#1}} +% \ekvsetdef\expkvtest{test} +% \expkvtest{ height = 6 } +% \end{lstlisting} +% and only the usage of the key, not its definition, is benchmarked. For the +% impatient, the essence of these comparisons regarding speed and buggy +% behaviour is contained in \autoref{tab:comp}. +% +% As far as I know \expkv\ is the only fully expandable \kv\ parser. I tried to +% compare \expkv\ to every \kv\ package listed on +% \href{https://ctan.org/topic/keyval}{CTAN}, however, one might notice that +% some of those are missing from this list. That's because I didn't get the +% others to work due to bugs, or because they just provide wrappers around other +% packages in this list. +% +% In this subsection is no benchmark of |\ekvparse| and |\keyval_parse:NNn| +% contained, as most other packages don't provide equivalent features to my +% knowledge. |\ekvparse| is slightly faster than |\ekvset|, but keep in mind +% that it does less. The same is true for |\keyval_parse:NNn| compared to +% |\keys_set:nn| of \pkg{expl3} (where the difference is much bigger). Comparing +% just the two, |\ekvparse| is a tad faster than |\keyval_parse:NNn| because of +% the two tests (for empty key names and only a single equal sign) which are +% omitted. +% +% \paragraph{\pkg{keyval}} is about \SIrange{30}{40}{\percent} faster and has a +% comparable feature set (actually a bit smaller since \expkv\ supports +% unknown-key handlers and redirection to other sets) just a slightly different +% way how it handles keys without values. That might be considered a drawback, +% as it limits the versatility, but also as an advantage, as it might reduce +% doubled code. Keep in mind that as soon as someone loads \pkg{xkeyval} the +% performance of \pkg{keyval} gets replaced by \pkg{xkeyval}'s. +% +% Also \pkg{keyval} has a bug, which unfortunately can't really be resolved +% without breaking backwards compatibility for \emph{many} documents, namely it +% strips braces from the argument before stripping spaces if the argument isn't +% surrounded by spaces, also it might strip more than one set of braces. Hence +% all of the following are equivalent in their outcome, though the last two +% lines should result in something different than the first two: +% \begin{lstlisting}[belowskip=0pt] +% \setkeys{foo}{bar=baz} +% \setkeys{foo}{bar= {baz}} +% \setkeys{foo}{bar={ baz}} % should be ` baz' +% \setkeys{foo}{bar={{baz}}} % should be `{baz}' +% \end{lstlisting} +% +% \paragraph{\pkg{xkeyval}} is roughly twenty times slower, but it provides more +% functionality, e.g., it has choice keys, boolean keys, and so on. It contains +% the same bug as \pkg{keyval} as it has to be compatible with it by design (it +% replaces \pkg{keyval}'s frontend), but also adds even more cases in which +% braces are stripped that shouldn't be stripped, worsening the situation. +% +% \paragraph{\pkg{ltxkeys}} is no longer compatible with the \LaTeX\ kernel +% starting with the release 2020-10-01. It is over 380 times slower -- which is +% funny, because it aims to be ``[\ldots] faster [\ldots] than these earlier +% packages [referring to \pkg{keyval} and \pkg{xkeyval}].'' It needs more time +% to parse zero~keys than five of the packages in this comparison need to parse +% 100~keys. Since it aims to have a bigger feature set than \pkg{xkeyval}, it +% most definitely also has a bigger feature set than \expkv. Also, it can't +% parse |\long| input, so as soon as your values contain a |\par|, it'll throw +% errors. Furthermore, \pkg{ltxkeys} doesn't strip outer braces at all by +% design, which, imho, is a weird design choice. In addition \pkg{ltxkeys} loads +% \pkg{catoptions} which is known to introduce bugs (e.g., see +% \url{https://tex.stackexchange.com/questions/461783}). Because it is no longer +% compatible with the kernel, I stop benchmarking it (so the numbers listed here +% and in \autoref{tab:comp} regarding \pkg{ltxkeys} were last updated on +% 2020-10-05). +% +% \paragraph{\pkg{l3keys}} is around four and a half times slower, but has an, +% imho, great interface to define keys. It strips \emph{all} outer spaces, even +% if somehow multiple spaces ended up on either end. It offers more features, +% but is pretty much bound to \pkg{expl3} code. Whether that's a drawback is up +% to you. +% +% \paragraph{\pkg{pgfkeys}} is around \num{2.7} times slower for one key if one +% uses the |/|\meta{path}|/.cd| syntax and almost \SI{20}{\percent} slower if +% one uses |\pgfqkeys|, but has an \emph{enormous} feature set. +% To get the best performance |\pgfqkeys| was used in the benchmark. This +% reduces the overhead for setting the base directory of the benchmark keys by +% about \SI{43}{\ops} (so both $p_0$ and $T_0$ would be about \SI{43}{\ops} +% bigger if |\pgfkeys{|\meta{path}|/.cd,|\meta{keys}|}| was used instead). +% It has the same or a very similar bug \pkg{keyval} has. The brace bug (and +% also the category fragility) can be fixed by \pkg{pgfkeyx}, but this package +% was last updated in 2012 and it slows down |\pgfkeys| by factor~\num{8}. Also +% \pkg{pgfkeyx} is no longer compatible with versions of \pkg{pgfkeys} newer +% than 2020-05-25. +% +% \paragraph{\pkg{kvsetkeys} with \pkg{kvdefinekeys}} is about \num{4.4} times +% slower, but it works even if commas and equals have category codes different +% from 12 (just as some other packages in this list). Else the features of the +% keys are equal to those of \pkg{keyval}, the parser has more features, though. +% +% \paragraph{\pkg{options}} is \num{1.7} times slower for only a single value. +% It has a much bigger feature set. Unfortunately it also suffers from the +% premature unbracing bug \pkg{keyval} has. +% +% \paragraph{\pkg{simplekv}} is hard to compare because I don't speak French (so +% I don't understand the documentation). There was an update released on +% 2020-04-27 which greatly improved the package's performance and adds +% functionality so that it can be used more like most of the other \kv\ +% packages. It has problems with stripping braces and spaces in a hard to +% predict manner just like \pkg{keyval}. Also, while it tries to be robust +% against category code changes of commas and equal signs, the used mechanism +% fails if the \kv\ list already got tokenised. Regarding unknown keys it got a +% very interesting behaviour. It doesn't throw an error, but stores the \val\ in +% a new entry accessible with \cs[no-index]{useKV}. Also if you omit \val\ it +% stores |true| for that \key. For up to three keys, \expkv\ is a bit faster, +% for more keys \pkg{simplekv} takes the lead. +% +% \paragraph{\protect\yax} is over twenty times slower. It has a pretty +% strange syntax for the \TeX-world, imho, and again a direct equivalent is hard +% to define (don't understand me wrong, I don't say I don't like the syntax, +% it's just atypical). It has the premature unbracing bug, too. Also somehow +% loading \yax\ broke \pkg{options} for me. The tested definition was: +% \begin{lstlisting}[belowskip=0pt] +% \usepackage{yax} +% \defactiveparameter yax {\storevalue\myheight yax:height } % key setup +% \setparameterlist{yax}{ height = 6 } % benchmarked +% \end{lstlisting} +% +% \begin{table} +% \def\fnsym{\textcolor{red!80!black}{*}}% +% \sisetup{round-precision=1, round-mode=places}% +% \begingroup +% \centering +% \newcommand*\yes{\textcolor{red!80!black} {yes}}^^A +% \newcommand*\no {\textcolor{green!80!black}{no}}^^A +% \caption[Comparison of \kv\ packages] +% {^^A +% Comparison of \kv\ packages. The packages are ordered from +% fastest to slowest for one \kv\ pair. Benchmarking was done using +% \pkg{l3benchmark} and the scripts in the \file{Benchmarks} folder +% of the \href{https://github.com/Skillmon/tex_expkv}{git repository}. +% The columns $p_i$ are the polynomial coefficients of a linear fit to the +% run-time, $p_0$ can be interpreted as the overhead for initialisation +% and $p_1$ the cost per key. The $T_0$ column is the actual mean ops +% needed for an empty list argument, as the linear fit doesn't match that +% point well in general. The column ``BB'' lists whether the +% parsing is affected by some sort of brace bug, ``CF'' stands for +% category code fragile and lists whether the parsing breaks with active +% commas or equal signs.^^A +% \label{tab:comp}^^A +% } +% \begin{tabular} +% {>{\collectcell\pkg}l<{\endcollectcell}*3{S[table-format=4.1]}ccc} +% \toprule +% \rmfamily Package & {$p_1$} & {$p_0$} & {$T_0$}& BB & CF & Date \\ +% \midrule +% keyval & 13.742 & 1.486 & 7.254 & \yes & \yes & 2014-10-28 \\ +% \expkv & 19.701 & 2.169 & 6.592 & \no & \no & 2020-10-10 \\ +% simplekv & 18.334 & 6.971 & 17.710 & \yes & \yes & 2020-04-27 \\ +% pgfkeys & 24.274 & 1.725 & 10.650 & \yes & \yes & 2020-09-05 \\ +% options & 23.600 & 15.638 & 20.830 & \yes & \yes & 2015-03-01 \\ +% kvsetkeys & {\fnsym} & {\fnsym} & 40.290 & \no & \no & 2019-12-15 \\ +% l3keys & 71.309 & 33.131 & 31.590 & \no & \no & 2020-09-24 \\ +% xkeyval & 253.563 & 202.246 & 168.300 & \yes & \yes & 2014-12-03 \\ +% \yax & 421.853 & 157.003 & 114.700 & \yes & \yes & 2010-01-22 \\ +% ltxkeys & 3400.142 & 4737.958 & 5368.000 & \no & \no & 2012-11-17 \\ +% \bottomrule +% \end{tabular} +% \par +% \endgroup +% \medskip +% \fnsym For \pkg{kvsetkeys} the linear model used for the other +% packages is a poor fit, \pkg{kvsetkeys} seems to have approximately +% quadratic run-time, the coefficients of the second degree polynomial fit are +% $p_2=\num{8.240}$, $p_1=\num{44.862}$, and $p_0=\num{60.793}$. Of course the +% other packages might not really have linear run-time, but at least from 1~to +% 20~keys the fits don't seem too bad. If one extrapolates the fits for 100 +% \kv\ pairs one finds that most of them match pretty well, the exception +% being \pkg{ltxkeys}, which behaves quadratic as well with +% $p_2=\num{23.500}$, $p_1=\num{2906.634}$, and $p_0=\num{6547.489}$. +% \end{table} +% % \subsection{License} % % Copyright \textcopyright\ 2020\unless\ifnum\year=2020--\the\year\fi\ @@ -1084,8 +1325,8 @@ and the derived files expkv.pdf % \begin{macro}{\ekvVersion,\ekvDate} % We're on our first input, so lets store the version and date in a macro. % \begin{macrocode} -\def\ekvVersion{1.6} -\def\ekvDate{2020-12-28} +\def\ekvVersion{1.7} +\def\ekvDate{2021-04-09} % \end{macrocode} % \end{macro} % @@ -1291,6 +1532,38 @@ and the derived files expkv.pdf % \end{macrocode} % \end{macro} % +% \begin{macro}[internal]{\ekv@csv@loop,\ekv@csv@loop@do,\ekv@csv@loop@end} +% This is just a very simple loop over a list of comma separated values, +% leaving each element as the argument to a specified function inside of +% |\unravel|. It should be used as +% \texttt +% {^^A +% \cs[no-index]{ekv@csv@loop}\hskip0pt^^A +% \marg{function}\hskip0pt^^A +% \cs[no-index]{ekv@mark}\hskip0pt^^A +% \meta{csv-list}\hskip0pt^^A +% ,\cs[no-index]{ekv@stop},^^A +% }. +% We use some |\expandafter| chain to preexpand |\ekv@strip| here. +% \begin{macrocode} +\ekv@exparg{\long\def\ekv@csv@loop#1#2,}% + {% + \expandafter + \ekv@gobble@from@mark@to@stop + \expandafter#\expandafter2\expandafter\ekv@csv@loop@end\expandafter + \ekv@stop + \ekv@strip{#2}{\ekv@csv@loop@do{#1}}% + \ekv@csv@loop{#1}\ekv@mark + } +\long\def\ekv@csv@loop@do#1#2{\unexpanded{#1{#2}}} +\long\expandafter\def\expandafter\ekv@csv@loop@end + \expandafter\ekv@stop + \ekv@strip{#1}#2% + \ekv@csv@loop#3\ekv@mark + {} +% \end{macrocode} +% \end{macro} +% % \begin{macro}{\ekv@name,\ekv@name@set,\ekv@name@key} % The keys will all follow the same naming scheme, so we define it here. % \begin{macrocode} @@ -1429,11 +1702,148 @@ and the derived files expkv.pdf \unexpanded\expandafter{\ekv@undefined@set{#1}\endcsname{#1}}% }% {\unexpanded\expandafter{\ekv@name{#3}{#4}}}% - }% + } \expandafter\ekvdef\ekvdefNoVal % \end{macrocode} % \end{macro} % +% \begin{macro}{\ekvredirectunknown,\ekvredirectunknownNoVal} +% \begin{macro}[internal] +% { +% \ekv@defredirectunknown,\ekv@redirectunknown@aux, +% \ekv@redirectunknownNoVal@aux +% } +% The redirection macros prepare the unknown function by looping over the +% provided list of sets and leaving a |\ekv@redirect@kv| or |\ekv@redirect@k| +% for each set. Only the first of these internals will receive the \key\ and +% \val\ as arguments. +% \begin{macrocode} +\protected\def\ekvredirectunknown + {% + \ekv@defredirectunknown + \ekv@redirect@kv + \ekv@err@redirect@kv@notfound + {\long\ekvdefunknown}% + \ekv@redirectunknown@aux + } +\protected\def\ekvredirectunknownNoVal + {% + \ekv@defredirectunknown + \ekv@redirect@k + \ekv@err@redirect@k@notfound + \ekvdefunknownNoVal + \ekv@redirectunknownNoVal@aux + } +\protected\def\ekv@defredirectunknown#1#2#3#4#5#6% + {% + \begingroup + \edef\ekv@tmp + {% + \ekv@csv@loop#1\ekv@mark#6,\ekv@stop,% + \unexpanded{#2}% + {\ekv@csv@loop{}\ekv@mark#5,#6,\ekv@stop,}% + }% + \ekv@expargtwice + {\endgroup#3{#5}}% + {\expandafter#4\ekv@tmp\ekv@stop}% + } +\def\ekv@redirectunknown@aux#1{#1{##1}{##2}} +\def\ekv@redirectunknownNoVal@aux#1{#1{##1}} +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[internal] +% { +% \ekv@redirect@k,\ekv@redirect@k@a,\ekv@redirect@k@a@, +% \ekv@redirect@k@b,\ekv@redirect@k@c,\ekv@redirect@k@d, +% \ekv@redirect@kv,\ekv@redirect@kv@a,\ekv@redirect@kv@a@, +% \ekv@redirect@kv@b,\ekv@redirect@kv@c,\ekv@redirect@kv@d +% } +% The redirect code works by some simple loop over all the sets, which we +% already preprocessed in |\ekv@defredirectunknown|. For some optimisation we +% blow this up a bit code wise, essentially, all this does is |\ekvifdefined| +% or |\ekvifdefinedNoVal| in each set, if there is a match gobble the +% remainder of the specified sets and execute the key macro, else go on with +% the next set (to which the \key\ and \val\ are forwarded). +% +% First we set up some code which is different depending on |\lastnamedcs| +% being available or not. All this is stored in a temporary macro to have +% pre-expanded |\ekv@name| constellations ready. +% \begin{macrocode} +\def\ekv@redirect@k#1#2#3#4% + {% + \ekv@if@lastnamedcs + {% + \def\ekv@redirect@k##1##2##3% + {% + \ifcsname#1\endcsname\ekv@redirect@k@a\fi + ##3{##1}% + }% + \def\ekv@redirect@k@a\fi{\fi\expandafter\ekv@redirect@k@b\lastnamedcs}% + \long\def\ekv@redirect@kv##1##2##3##4% + {% + \ifcsname#2\endcsname\ekv@redirect@kv@a\fi\@gobble{##1}% + ##4{##1}{##2}% + } + \def\ekv@redirect@kv@a\fi\@gobble + {\fi\expandafter\ekv@redirect@kv@b\lastnamedcs}% + } + {% + \def\ekv@redirect@k##1##2##3% + {% + \ifcsname#1\endcsname\ekv@redirect@k@a\fi\ekv@redirect@k@a@ + #1\endcsname + ##3{##1}% + }% + \def\ekv@redirect@k@a@#3\endcsname{}% + \def\ekv@redirect@k@a\fi\ekv@redirect@k@a@ + {\fi\expandafter\ekv@redirect@k@b\csname}% + \long\def\ekv@redirect@kv##1##2##3##4% + {% + \ifcsname#2\endcsname\ekv@redirect@kv@a\fi\ekv@redirect@kv@a@ + #2\endcsname{##1}% + ##4{##1}{##2}% + } + \long\def\ekv@redirect@kv@a@#4\endcsname##3{}% + \def\ekv@redirect@kv@a\fi\ekv@redirect@kv@a@ + {\fi\expandafter\ekv@redirect@kv@b\csname}% + } + } +% \end{macrocode} +% The key name given to this loop will already be |\detokenize|d by |\ekvset|, +% so we can safely remove the |\detokenize| here for some performance gain. +% \begin{macrocode} +\def\ekv@redirect@kv#1\detokenize#2#3\ekv@stop{\unexpanded{#1#2#3}} +\edef\ekv@redirect@kv + {% + {\expandafter\ekv@redirect@kv\ekv@name{#2}{#1}N\ekv@stop}% + {\expandafter\ekv@redirect@kv\ekv@name{#3}{#2}\ekv@stop}% + {\expandafter\ekv@redirect@kv\ekv@name{#1}{#2}N\ekv@stop}% + {\expandafter\ekv@redirect@kv\ekv@name{#1}{#2}\ekv@stop}% + } +% \end{macrocode} +% Everything is ready to make the real definitions. +% \begin{macrocode} +\expandafter\ekv@redirect@k\ekv@redirect@kv +% \end{macrocode} +% The remaining macros here are independent on |\lastnamedcs|, starting from +% the |@b| we know that there is a hash table entry, and get the macro as a +% parameter. We still have to test whether the macro is |\relax|, depending on +% the result of that test we have to either remove the remainder of the +% current test, or the remainder of the set list and invoke the macro. +% \begin{macrocode} +\def\ekv@redirect@k@b#1% + {\ifx\relax#1\ekv@redirect@k@c\fi\ekv@redirect@k@d#1} +\def\ekv@redirect@k@c\fi\ekv@redirect@k@d#1{\fi} +\def\ekv@redirect@k@d#1#2\ekv@stop{#1} +\def\ekv@redirect@kv@b#1% + {\ifx\relax#1\ekv@redirect@kv@c\fi\ekv@redirect@kv@d#1} +\long\def\ekv@redirect@kv@c\fi\ekv@redirect@kv@d#1#2{\fi} +\long\def\ekv@redirect@kv@d#1#2#3\ekv@stop{#1{#2}} +% \end{macrocode} +% \end{macro} +% % \begin{macro}[internal]{\ekv@defsetmacro} % In order to enhance the speed the set name given to |\ekvset| will be turned % into a control sequence pretty early, so we have to define that control @@ -1841,12 +2251,12 @@ and the derived files expkv.pdf % % \begin{macro}{\ekvchangeset} % Provide a macro that is able to switch out the current \set\ in |\ekvset|. -% This operation is slow (by comparison, it should be slightly faster than -% |\ekvsneak|), but allows for something similar to \pkg{pgfkeys}'s -% \texttt{\meta{key}/.cd} mechanism. However this operation is more expensive -% than |/.cd| as we can't just redefine some token to reflect this, but have to -% switch out the set expandably, so this works similar to the |\ekvsneak| macros -% reading and reinserting the remainder of the \kv\ list. +% This operation allows something similar to \pkg{pgfkeys}'s +% \texttt{\meta{key}/.cd} mechanism. However this operation can be more +% expensive than |/.cd| as we can't just redefine some token to reflect this, +% but have to switch out the set expandably, so this works similar to the +% |\ekvsneak| macros reading and reinserting things, but it only has to read and +% reinsert the remainder of the current key's replacement code. % \begin{macrocode} \ekv@exparg{\def\ekvchangeset#1}% {% @@ -2034,18 +2444,12 @@ and the derived files expkv.pdf % is unlikely) we want to somehow throw expandable errors, in our case via % undefined control sequences. % \begin{macrocode} -\begingroup -\edef\ekv@err +\def\ekv@err#1% {% - \endgroup - \unexpanded{\long\def\ekv@err}##1% - {% - \unexpanded{\expandafter\ekv@err@\@firstofone}% - {\unexpanded\expandafter{\csname ! expkv Error:\endcsname}##1.}% - \unexpanded{\ekv@stop}% - }% + \long\def\ekv@err##1{\expandafter\ekv@err@\@firstofone{#1##1.}\ekv@stop}% } -\ekv@err +\begingroup\expandafter\endgroup +\expandafter\ekv@err\csname ! expkv Error:\endcsname \def\ekv@err@{\expandafter\ekv@gobbleto@stop} % \end{macrocode} % \end{macro} @@ -2059,10 +2463,15 @@ and the derived files expkv.pdf % use those instead of the full strings. % \begin{macrocode} \long\def\ekv@err@common #1#2{\expandafter\ekv@err@common@\string#2{#1}} -\long\def\ekv@err@common@#1`#2' #3.#4#5{\ekv@err{#4 (`#5', set `#2')}} +\ekv@exparg{\long\def\ekv@err@common@#1`#2' #3.#4#5}% + {\ekv@err{#4 (`#5', set `#2')}} \ekv@exparg{\long\def\ekv@err@unknown#1}{\ekv@err@common{unknown key}{#1}} \ekv@exparg{\long\def\ekv@err@noarg #1}{\ekv@err@common{value forbidden}{#1}} \ekv@exparg{\long\def\ekv@err@reqval #1}{\ekv@err@common{value required}{#1}} +\ekv@exparg{\long\def\ekv@err@redirect@kv@notfound#1#2#3\ekv@stop}% + {\ekv@err{no key `#2' in sets #3}} +\ekv@exparg{\def\ekv@err@redirect@k@notfound#1#2\ekv@stop}% + {\ekv@err{no NoVal key `#1' in sets #2}} % \end{macrocode} % \end{macro} % -- cgit v1.2.3