diff options
Diffstat (limited to 'macros/generic/expkv/expkv.dtx')
-rw-r--r-- | macros/generic/expkv/expkv.dtx | 237 |
1 files changed, 175 insertions, 62 deletions
diff --git a/macros/generic/expkv/expkv.dtx b/macros/generic/expkv/expkv.dtx index 3fae5459b0..12cf70fd87 100644 --- a/macros/generic/expkv/expkv.dtx +++ b/macros/generic/expkv/expkv.dtx @@ -149,6 +149,7 @@ and the derived files expkv.pdf } \newcommand*\expkvd{\expkvpkg{def}} \newcommand*\expkvc{\expkvpkg{cs}} +\newcommand*\expkvo{\expkvpkg{opt}} \newcommand\kv{\meta{key}=\meta{value}} \newcommand\key{\meta{key}} \newcommand\val{\meta{value}} @@ -157,7 +158,7 @@ and the derived files expkv.pdf \DoNotIndex{\def,\edef,\,,\=,\begingroup,\catcode,\chardef,\csname,\endcsname} \DoNotIndex{\endgroup,\endinput,\errmessage,\expandafter,\input,\let,\long} \DoNotIndex{\protected,\ProvidesFile,\ProvidesPackage,\relax,\space} -\DoNotIndex{\@,\unexpanded,\string,\expanded} +\DoNotIndex{\@,\unexpanded,\string,\expanded,\detokenize} \DoNotIndex{\ifcsname} \DoNotIndex{\ifx} \DoNotIndex{\else} @@ -233,6 +234,8 @@ and the derived files expkv.pdf % a key-defining frontend for \expkv\ using a \kv\ syntax % \item[cs] % define expandable \kv\ macros using \expkv +% \item[opt] +% parse package and class options with \expkv % \end{description} % 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}). @@ -254,9 +257,9 @@ and the derived files expkv.pdf % ``|def|'' in their name can be prefixed by anything allowed to prefix |\def| % (but \emph{don't} use |\outer|, keys defined with it won't ever be usable), % prefixes allowed for |\let| can prefix those with ``|let|'' in their name, -% accordingly. Neither \set\ nor \key\ are allowed to be empty for new keys and -% must not contain a |\par| or tokens that expand to it -- they must be legal -% inside of |\csname ...\endcsname|. +% accordingly. Neither \set\ nor \key\ are allowed to be empty for new keys. +% \set\ will be used as is inside of |\csname ...\endcsname| and \key\ will get +% \cs[no-index]{detokenize}d. % % \begin{function}{\ekvdef} % \begin{syntax} @@ -305,6 +308,28 @@ and the derived files expkv.pdf % whether that second key exists. % \end{function} % +% \begin{function}{\ekvdefunknown} +% \begin{syntax} +% \cs{ekvdefunknown}\marg{set}\marg{code} +% \end{syntax} +% By default an error will be thrown if an unknown \key\ is encountered. With +% 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 +% \end{function} +% \footnotetext{That order is correct, this way the code is faster.} +% +% \begin{function}{\ekvdefunknownNoVal} +% \begin{syntax} +% \cs{ekvdefunknownNoVal}\marg{set}\marg{code} +% \end{syntax} +% As already explained for \cs[no-index]{ekvdefunknown}, \expkv\ would throw +% 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}. +% \end{function} +% % \subsection{Parsing Keys} % % \begin{function}{\ekvset} @@ -324,6 +349,18 @@ and the derived files expkv.pdf % |\ekvset| should be nestable. % \end{function} % +% \begin{function}{\ekvsetdef} +% \begin{syntax} +% \cs{ekvsetdef}\meta{cs}\marg{set} +% \end{syntax} +% With this function you can define a shorthand macro \meta{cs} to parse keys +% of a specified \set. It is always defined |\long|, but if you need to you +% can also prefix it with |\global|. The resulting macro is a bit faster than +% the idiomatic definition:\par +% \texttt +% {\string\long\string\def\meta{cs}\#1\{\string\ekvset\marg{set}\{\#1\}\}} +% \end{function} +% % \begin{function}{\ekvparse} % \begin{syntax} % \cs{ekvparse}\meta{cs1}\meta{cs2}\{\kv,\ldots\} @@ -440,7 +477,11 @@ and the derived files expkv.pdf % ``\texttt{\csname ekv@name@set\endcsname{\set}}'' and the default of % |\ekv@name@key| is ``\texttt{\csname ekv@name@key\endcsname{\key}}''. % The complete name is build using |\ekv@name| which is equivalent to -% \texttt{\cs[no-index]{ekv@name@set}\marg{set}\cs{ekv@name@key}\marg{key}}. +% \texttt +% {^^A +% \cs[no-index]{ekv@name@set}\marg{set}\cs{ekv@name@key}^^A +% \{\cs[no-index]{detokenize}\marg{key}\}^^A +% }. % For |NoVal| keys an additional |N| gets appended irrespective of these % macros' definition, so their name is % \cs[no-index]{\csname ekv@name\endcsname{\set}{\key}N}. You might redefine @@ -542,17 +583,16 @@ and the derived files expkv.pdf % 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) and from what I can see, there is no -% direct way to define the equivalent test key. Nevertheless, I tested the -% closest possible equivalent of my test key while siding for \pkg{simplekv}'s -% design not forcing something into it it doesn't seem to be designed for. It is -% almost seven times slower and has hard to predict behaviour regarding brace -% and space stripping, similar to \pkg{keyval}. The tested definition was: -% \begin{lstlisting}[belowskip=0pt] -% \usepackage{simplekv} -% \setKVdefault[simplekv]{height={ abc}} % key setup -% \setKV[simplekv]{ height = 6 } % benchmarked -% \end{lstlisting} +% 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 tokenized. 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. It is around $10\,\%$ faster than \expkv. % % \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 @@ -592,16 +632,16 @@ and the derived files expkv.pdf % \toprule % \rmfamily Package & {$p_1$} & {$p_0$} & {$T_0$}& BB & CF & Date \\ % \midrule -% keyval & 13.423 & 2.594 & 6.928 & \yes & \yes & 2014-10-28 \\ -% \expkv & 19.718 & 7.256 & 9.611 & \no & \no & 2020-04-07 \\ -% options & 23.436 & 15.618 & 19.920 & \yes & \yes & 2015-03-01 \\ -% pgfkeys & 24.577 & 46.244 & 52.770 & \yes & \yes & 2020-01-08 \\ -% kvsetkeys & {\fnsym} & {\fnsym} & 39.760 & \no & \no & 2019-12-15 \\ -% l3keys & 108.001 & 56.299 & 50.710 & \no & \no & 2020-02-25 \\ -% simplekv & 149.181 & 25.712 & 8.070 & \yes & \yes & 2017-08-08 \\ -% xkeyval & 248.777 & 234.420 & 161.900 & \yes & \yes & 2014-12-03 \\ -% yax & 443.584 & 170.471 & 115.700 & \yes & \yes & 2010-01-22 \\ -% ltxkeys & 3516.032 & 5254.811 & 5487.000 & \no & \no & 2012-11-17 \\ +% keyval & 13.694 & 1.504 & 7.008 & \yes & \yes & 2014-10-28 \\ +% simplekv & 18.739 & 5.325 & 17.68 & \yes & \yes & 2020-04-27 \\ +% \expkv & 22.007 & 3.100 & 10.095 & \no & \no & 2020-06-21 \\ +% options & 24.351 & 12.010 & 20.38 & \yes & \yes & 2015-03-01 \\ +% pgfkeys & 24.621 & 45.474 & 53.25 & \yes & \yes & 2020-06-17 \\ +% kvsetkeys & {\fnsym} & {\fnsym} & 40.0 & \no & \no & 2019-12-15 \\ +% l3keys & 92.103 & 32.724 & 38.05 & \no & \no & 2020-06-18 \\ +% xkeyval & 257.125 & 173.704 & 164.5 & \yes & \yes & 2014-12-03 \\ +% yax & 440.234 & 76.276 & 113.9 & \yes & \yes & 2010-01-22 \\ +% ltxkeys & 3448.339 & 4470.031 & 5241.0 & \no & \no & 2012-11-17 \\ % \bottomrule % \end{tabular} % \par @@ -610,13 +650,12 @@ and the derived files expkv.pdf % \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{7.473}$, $p_1=\num{51.276}$, and $p_0=\num{49.568}$. Of course the +% $p_2=\num{8.811}$, $p_1=\num{36.015}$, and $p_0=\num{81.792}$. 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 (the maximum ratio $p_2/p_1$ -% for the other packages is \num{3.478e-3}). 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{11.396}$, $p_1=\num{3276.713}$, and $p_0=\num{6132.315}$. +% 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{29.496}$, $p_1=\num{2828.933}$, and $p_0=\num{6741.189}$. % \end{table} % % \subsection{Examples} @@ -797,7 +836,7 @@ and the derived files expkv.pdf % \paragraph{The same macro using \protect\expkvc} % Using \expkvc\ we can set up something equivalent with a bit less code. The % implementation chosen in \expkvc\ is more efficient than the example above and -% way easier to code. +% way easier to code for the user. % \begin{lstlisting} % \makeatletter % \ekvcSplitAndForward\sine\sine@ @@ -963,8 +1002,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.2} -\def\ekvDate{2020-04-10} +\def\ekvVersion{1.3} +\def\ekvDate{2020-07-02} % \end{macrocode} % \end{macro} % @@ -1042,12 +1081,18 @@ and the derived files expkv.pdf % \end{macrocode} % \end{macro} % -% \begin{macro}[internal]{\ekv@ifblank@} +% \begin{macro}[internal]{\ekv@ifblank,\ekv@ifblank@} % The obvious test that can be based on an if-empty is if-blank, meaning a test % checking whether the argument is empty or consists only of spaces. Our version % here will be tweaked a bit, as we want to check this, but with one leading -% |\ekv@mark| token that is to be ignored. +% |\ekv@mark| token that is to be ignored. The wrapper |\ekv@ifblank| will not +% be used by \expkv\ for speed reasons but \expkvo\ uses it. % \begin{macrocode} +\long\def\ekv@ifblank#1% + {% + \ekv@ifblank@#1\ekv@nil\ekv@ifempty@B\ekv@ifempty@true + \ekv@ifempty@A\ekv@ifempty@B\@secondoftwo + } \long\def\ekv@ifblank@\ekv@mark#1{\ekv@ifempty@\ekv@ifempty@A} % \end{macrocode} % \end{macro} @@ -1073,7 +1118,7 @@ and the derived files expkv.pdf % \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} -\def\ekv@name#1#2{\ekv@name@set{#1}\ekv@name@key{#2}} +\def\ekv@name#1#2{\ekv@name@set{#1}\ekv@name@key{\detokenize{#2}}} \def\ekv@name@set#1{ekv#1(} \def\ekv@name@key#1{#1)} % \end{macrocode} @@ -1129,7 +1174,10 @@ and the derived files expkv.pdf % \end{macro} % % \begin{macro} -% {\ekvdef,\ekvdefNoVal,\ekvlet,\ekvletNoVal,\ekvletkv,\ekvletkvNoVal} +% { +% \ekvdef,\ekvdefNoVal,\ekvlet,\ekvletNoVal,\ekvletkv,\ekvletkvNoVal, +% \ekvdefunknown,\ekvdefunknownNoVal +% } % Set up the key defining macros |\ekvdef| etc. % \begin{macrocode} \protected\long\def\ekvdef#1#2#3% @@ -1137,7 +1185,7 @@ and the derived files expkv.pdf \ekv@checkvalid{#1}{#2}% {% \expandafter\def\csname\ekv@name{#1}{#2}\endcsname##1{#3}% - \expandafter\ekv@defset\csname\ekv@undefined@set{#1}\endcsname{#1}% + \expandafter\ekv@defsetmacro\csname\ekv@undefined@set{#1}\endcsname{#1}% }% } \protected\long\def\ekvdefNoVal#1#2#3% @@ -1145,7 +1193,7 @@ and the derived files expkv.pdf \ekv@checkvalid{#1}{#2}% {% \expandafter\def\csname\ekv@name{#1}{#2}N\endcsname{#3}% - \expandafter\ekv@defset\csname\ekv@undefined@set{#1}\endcsname{#1}% + \expandafter\ekv@defsetmacro\csname\ekv@undefined@set{#1}\endcsname{#1}% }% } \protected\def\ekvlet#1#2#3% @@ -1153,7 +1201,7 @@ and the derived files expkv.pdf \ekv@checkvalid{#1}{#2}% {% \expandafter\let\csname\ekv@name{#1}{#2}\endcsname#3% - \expandafter\ekv@defset\csname\ekv@undefined@set{#1}\endcsname{#1}% + \expandafter\ekv@defsetmacro\csname\ekv@undefined@set{#1}\endcsname{#1}% }% } \protected\def\ekvletNoVal#1#2#3% @@ -1161,7 +1209,7 @@ and the derived files expkv.pdf \ekv@checkvalid{#1}{#2}% {% \expandafter\let\csname\ekv@name{#1}{#2}N\endcsname#3% - \expandafter\ekv@defset\csname\ekv@undefined@set{#1}\endcsname{#1}% + \expandafter\ekv@defsetmacro\csname\ekv@undefined@set{#1}\endcsname{#1}% }% } \protected\def\ekvletkv#1#2#3#4% @@ -1170,7 +1218,7 @@ and the derived files expkv.pdf {% \expandafter\let\csname\ekv@name{#1}{#2}\expandafter\endcsname \csname\ekv@name{#3}{#4}\endcsname - \expandafter\ekv@defset\csname\ekv@undefined@set{#1}\endcsname{#1}% + \expandafter\ekv@defsetmacro\csname\ekv@undefined@set{#1}\endcsname{#1}% }% } \protected\def\ekvletkvNoVal#1#2#3#4% @@ -1179,21 +1227,37 @@ and the derived files expkv.pdf {% \expandafter\let\csname\ekv@name{#1}{#2}N\expandafter\endcsname \csname\ekv@name{#3}{#4}N\endcsname - \expandafter\ekv@defset\csname\ekv@undefined@set{#1}\endcsname{#1}% + \expandafter\ekv@defsetmacro\csname\ekv@undefined@set{#1}\endcsname{#1}% + }% + } +\protected\def\ekvdefunknown#1#2% + {% + \ekv@checkvalid{#1}{.}% + {% + \expandafter\def\csname\ekv@name{#1}{}u\endcsname##1##2{#2}% + \expandafter\ekv@defsetmacro\csname\ekv@undefined@set{#1}\endcsname{#1}% + }% + } +\protected\def\ekvdefunknownNoVal#1#2% + {% + \ekv@checkvalid{#1}{.}% + {% + \expandafter\def\csname\ekv@name{#1}{}uN\endcsname##1{#2}% + \expandafter\ekv@defsetmacro\csname\ekv@undefined@set{#1}\endcsname{#1}% }% } % \end{macrocode} % \end{macro} % -% \begin{macro}[internal]{\ekv@defset} +% \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 % sequence. % \begin{macrocode} -\protected\def\ekv@defset#1#2% +\protected\def\ekv@defsetmacro#1#2% {% \ifx#1\relax - \edef#1##1{\ekv@name@set{#2}\ekv@name@key{##1}}% + \edef#1##1{\ekv@name@set{#2}\ekv@name@key{\noexpand\detokenize{##1}}}% \fi } % \end{macrocode} @@ -1202,10 +1266,11 @@ and the derived files expkv.pdf % \begin{macro}{\ekvset} % Set up |\ekvset|, which should not be affected by active commas and equal % signs. The equal signs are a bit harder to cope with and we'll do that later, -% but replacing the active commas with commas of category other can be done -% beforehand. That's why we define |\ekvset| here with a temporary meaning just -% to set up the things with two different category codes. |#1| will be a -% \texttt{,\textsubscript{13}} and |#2| will be a \texttt{=\textsubscript{13}}. +% but the active commas can be handled by just doing two comma-splitting loops +% one at actives one at others. That's why we define |\ekvset| here with a +% temporary meaning just to set up the things with two different category codes. +% |#1| will be a \texttt{,\textsubscript{13}} and |#2| will be a +% \texttt{=\textsubscript{13}}. % \begin{macrocode} \def\ekvset#1#2{% \endgroup @@ -1597,8 +1662,9 @@ and the derived files expkv.pdf % the third argument. It builds the key-macro name and provides everything to % be able to throw meaningful error messages if it isn't defined. % |\ekv@set@pair@| will space and brace strip the value if the macro is -% defined and call the key-macro. Else it'll branch into the error messages -% provided by |\ekv@set@pair|. +% defined and call the key-macro. Else it'll branch into the check whether an +% unknown key handler is defined for this set and that one will branch into +% the error messages provided by |\ekv@set@pair| if it isn't. % \begin{macrocode} \long\def\ekv@set@pair#1#2\ekv@nil#3% {% @@ -1612,10 +1678,22 @@ and the derived files expkv.pdf \endcsname {#2}% {% - \ekv@ifdefined{#3{#1}N}% - \ekv@err@noarg - \ekv@err@unknown - #3{#1}% + \expandafter\ekv@set@pair@ + \csname + \ifcsname #3{}u\endcsname + #3{}u% + \else + relax% + \fi + \endcsname + {#2}% + {% + \ekv@ifdefined{#3{#1}N}% + \ekv@err@noarg + \ekv@err@unknown + #3% + }% + {#1}% }% } \long\def\ekv@set@pair@#1#2% @@ -1645,10 +1723,21 @@ and the derived files expkv.pdf \fi \endcsname {% - \ekv@ifdefined{#2{#1}}% - \ekv@err@reqval - \ekv@err@unknown - #2{#1}% + \expandafter\ekv@set@key@ + \csname + \ifcsname #2{}uN\endcsname + #2{}uN% + \else + relax% + \fi + \endcsname + {% + \ekv@ifdefined{#2{#1}}% + \ekv@err@reqval + \ekv@err@unknown + #2% + }% + {#1}% }% } \def\ekv@set@key@#1% @@ -1661,6 +1750,30 @@ and the derived files expkv.pdf % \end{macrocode} % \end{macro} % +% \begin{macro}{\ekvsetdef} +% Provide a macro to define a shorthand to use |\ekvset| on a specified \set. +% The first macro expands |\ekvset| twice, such that everything which can be +% done up to this point is done. +% \begin{macrocode} +\protected\def\ekvsetdef#1#2% + {% + \expandafter\expandafter\expandafter + \ekv@setdef\expandafter\expandafter\expandafter{\ekvset{#2}{##1}}#1% + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@setdef} +% This auxiliary macro defines the shorthand macro after |\ekvset| got +% expanded as far as possible. +% \begin{macrocode} +\protected\def\ekv@setdef#1#2% + {% + \long\def#2##1{#1}% + } +% \end{macrocode} +% \end{macro} +% % \begin{macro}[internal]{\ekv@err,\ekv@err@} % Since |\ekvset| is fully expandable as long as the code of the keys is (which % is unlikely) we want to somehow throw expandable errors, in our case via |