diff options
Diffstat (limited to 'obsolete/macros/generic/expkv/expkv.dtx')
-rw-r--r-- | obsolete/macros/generic/expkv/expkv.dtx | 2980 |
1 files changed, 2980 insertions, 0 deletions
diff --git a/obsolete/macros/generic/expkv/expkv.dtx b/obsolete/macros/generic/expkv/expkv.dtx new file mode 100644 index 0000000000..8651ad97a3 --- /dev/null +++ b/obsolete/macros/generic/expkv/expkv.dtx @@ -0,0 +1,2980 @@ +% \iffalse meta-comment +% +% 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 +% (at your option) any later version. The latest version of this license is in +% the file: +% +% http://www.latex-project.org/lppl.txt +% +% ------------------------------------------------------------------------------ +% +%<*driver>^^A>>= +\def\nameofplainTeX{plain} +\ifx\fmtname\nameofplainTeX\else + \expandafter\begingroup +\fi +\input l3docstrip.tex +\askforoverwritefalse +\preamble + +-------------------------------------------------------------- +expkv -- an expandable key=val implementation +E-mail: jspratte@yahoo.de +Released under the LaTeX Project Public License v1.3c or later +See http://www.latex-project.org/lppl.txt +-------------------------------------------------------------- + +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 +(at your option) any later version. The latest version of this license is in +the file: + + http://www.latex-project.org/lppl.txt + +This work is "maintained" (as per LPPL maintenance status) by + Jonathan P. Spratte. + +This work consists of the file expkv.dtx +and the derived files expkv.pdf + expkv.sty + expkv.tex + t-expkv.tex + +\endpreamble +% stop docstrip adding \endinput +\postamble +\endpostamble +\generate{\file{expkv.sty}{\from{expkv.dtx}{pkg}}} +\generate{\file{expkv.tex}{\from{expkv.dtx}{tex}}} +\generate{\file{t-expkv.tex}{\from{expkv.dtx}{ctx}}} +\ifx\fmtname\nameofplainTeX + \expandafter\endbatchfile +\else + \expandafter\endgroup +\fi +% +\IfFileExists{expkv.tex}{\input{expkv.tex}}{} +\ProvidesFile{expkv.dtx} + [\csname ekvDate\endcsname\ an expandable key=val implementation] +\PassOptionsToPackage{full}{textcomp} +\documentclass{l3doc} +\RequirePackage[oldstylenums,nott]{kpfonts} +\input{glyphtounicode} +\pdfgentounicode=1 +\RequirePackage{xfp} ^^A required for an example +\RequirePackage{listings} +\RequirePackage{booktabs} +\RequirePackage{array} +\RequirePackage{collcell} +\RequirePackage{siunitx} +\DeclareSIUnit\ops{ops} +\RequirePackage{xcolor} +\RequirePackage{caption} +\RequirePackage{microtype} +\RequirePackage{accsupp} +\RequirePackage{enumitem} +\RequirePackage{tcolorbox} +\newtcolorbox{exresult}[2][] + {colback=expkvgrey!10!white,colframe=expkvgrey,fontupper=\small,width={#2},#1} +\newtcbox\exres[1][] + { + colback=expkvgrey!10!white + ,colframe=expkvgrey + ,size=small + ,nobeforeafter + ,tcbox raise base + ,fontupper=\small + ,#1 + } +\def\mylstwd{.55em} +\lstdefinelanguage{expkv}[primitive]{TeX} + { + ,moretexcs=[4]^^A e-TeX + { + expanded, + numexpr, + protected, + } + ,moretexcs=[5]^^A plain/LaTeX/ConTeXt + { + approx, + begin, + empty, + item, + LaTeX, + makeatletter,makeatother, + newcommand,newdimen, + RequirePackage, + rule, + TeX, + textit,texttt, + usepackage, + usemodule, + } + ,moretexcs=[6]^^A used packages + { + ^^A xfp + fpeval, + ^^A keyval + setkeys, + ^^A yax + defactiveparameter,storevalue,setparameterlist + } + ,moretexcs=[2]^^A expkv macros + { + 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,^^A + ekvoptarg,ekvoptargTF,^^A + ekverr + } + ,moretexcs=[3]^^A expkv-pkg macros + { + ^^A expkv-cs + ekvcSecondaryKeys,ekvcSplitAndForward, + ^^A expkv-def + ekvdefinekeys + } + } +\colorlet{codeparam}{cyan!65!black} +\lstset + { + ,language=expkv + ,flexiblecolumns=false + ,basewidth=\mylstwd + ,gobble=2 + ,basicstyle=\fontfamily{jkp}\itshape + ,texcsstyle=*[1]{\bfseries\color{expkvgrey}} ^^A primitives + ,texcsstyle=*[2]{\bfseries\color{expkvred}} ^^A expkv + ,texcsstyle=*[3]{\color{expkvred}} ^^A expkv-pkg + ,texcsstyle=*[4]{\bfseries\color{expkvgrey}} ^^A e-TeX + ,texcsstyle=*[5]{\bfseries\color{expkvgrey}} ^^A plain/LaTeX macros + ,texcsstyle=*[6]{} ^^A macros of other packages + ,commentstyle=\color[gray]{0.4} + ,literate= + {\{} {{\CodeUpBf\{}}{1} + {\}} {{\CodeUpBf\}}}{1} + {$} {{\CodeUpBf\$}}{1} + {[} {{\CodeUp[}}{1} + {]} {{\CodeUp]}}{1} + {(} {{\CodeUp(}}{1} + {)} {{\CodeUp)}}{1} + {*} {{$*$}}{1} + {1} {{\CodeUp{1}}}{1} + {2} {{\CodeUp{2}}}{1} + {3} {{\CodeUp{3}}}{1} + {4} {{\CodeUp{4}}}{1} + {5} {{\CodeUp{5}}}{1} + {6} {{\CodeUp{6}}}{1} + {7} {{\CodeUp{7}}}{1} + {8} {{\CodeUp{8}}}{1} + {9} {{\CodeUp{9}}}{1} + {0} {{\CodeUp{0}}}{1} + {##} {{\CodeColored{codeparam}{1}{\#}}}{1} + {##1}{{\CodeColored{codeparam}{2}{\#1}}}{2} + {##2}{{\CodeColored{codeparam}{2}{\#2}}}{2} + {##3}{{\CodeColored{codeparam}{2}{\#3}}}{2} + {##4}{{\CodeColored{codeparam}{2}{\#4}}}{2} + {##5}{{\CodeColored{codeparam}{2}{\#5}}}{2} + {##6}{{\CodeColored{codeparam}{2}{\#6}}}{2} + {##7}{{\CodeColored{codeparam}{2}{\#7}}}{2} + {##8}{{\CodeColored{codeparam}{2}{\#8}}}{2} + {##9}{{\CodeColored{codeparam}{2}{\#9}}}{2} + {<key>}{{$\langle$}key{$\rangle$}}{5} + {<set>}{{$\langle$}set{$\rangle$}}{5} + {<set1>}{{$\langle$}set1{$\rangle$}}{6} + {<set2>}{{$\langle$}set2{$\rangle$}}{6} + ^^A,literate=*{<key>}{\key}{4}{<set>}{\set}{4} + } +\newcommand*\CodeColored[3]{\textcolor{#1}{\makebox[\dimexpr\mylstwd*#2]{#3}}} +\newcommand*\CodeUpBf[1]{\makebox[\mylstwd]{\textup{\textbf{#1}}}} +\newcommand*\CodeUp[1]{\makebox[\mylstwd]{\textup{#1}}} +\RequirePackage{randtext} +\let\metaORIG\meta +\protected\def\meta #1{\texttt{\metaORIG{#1}}} +\renewcommand*\thefootnote{\fnsymbol{footnote}} +\definecolor{expkvred}{HTML}{9F393D} +\colorlet{expkvgrey}{black!75} +\makeatletter +\newcommand*\example{\par\smallskip\noindent\textit{Example:} \ignorespaces} +\newcommand*\expFormat + {^^A + {^^A + \rmfamily + \bfseries + {\color{expkvgrey}e\kern-.05em x\kern-.05em}^^A + \lower.493ex\hbox{{\color{expkvgrey}P}}^^A + }^^A + } +\newcommand*\kvstyle + {^^A + \normalfont\rmfamily\bfseries\color{expkvred}^^A + } +\newcommand*\expkv + {^^A + \texorpdfstring + {^^A + \mbox + {^^A + \BeginAccSupp{ActualText=expkv}^^A + \href{https://github.com/Skillmon/tex_expkv} + {^^A + \rmfamily + \bfseries + {\color{expkvgrey}e\kern-.05em x\kern-.05em}^^A + \lower.493ex + \hbox{{\color{expkvgrey}P}\kern-.1em{\color{expkvred}k}}^^A + \kern-.18em{\color{expkvred}v}^^A + }^^A + \EndAccSupp{}^^A + }^^A + } + {expkv}^^A + } +\newcommand*\expkvpkg[1] + {^^A + \texorpdfstring + {^^A + \mbox + {^^A + \BeginAccSupp{ActualText=expkv-#1}^^A + \href{https://github.com/Skillmon/tex_expkv-#1} + {^^A + \rmfamily + \bfseries + {\color{expkvgrey}e\kern-.05em x\kern-.05em}^^A + \lower.493ex + \hbox{{\color{expkvgrey}P}\kern-.1em{\color{expkvred}k}}^^A + \kern-.18em{\color{expkvred}v}^^A + {\color{expkvgrey}^^A + \kern.05em\rule[-.1ex]{.08em}{1.2ex}\kern.05em\textsc{#1}^^A + }^^A + }^^A + \EndAccSupp{}^^A + }^^A + } + {expkv-#1}^^A + } +\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}} +\newcommand\set{\meta{set}} +\hypersetup{linkcolor=red!80!black,urlcolor=purple!80!black} +\DoNotIndex{\directlua} +\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,\detokenize,\meaning} +\DoNotIndex{\lastnamedcs,\romannumeral,\z@} +\DoNotIndex{\noexpand,\normalexpanded,\normalunexpanded} +\DoNotIndex{\par,\protect} +\DoNotIndex{\unprotect} +\DoNotIndex{\writestatus} +\DoNotIndex{\ifcsname} +\DoNotIndex{\ifx} +\DoNotIndex{\iffalse} +\DoNotIndex{\ifnum} +\DoNotIndex{\else} +\DoNotIndex{\fi} +\@gobble\fi ^^A ignoring \ifx etc., but only one \fi +\@gobble\fi ^^A ignoring \ifx etc., but only one \fi +\@gobble\fi ^^A ignoring \ifx etc., but only one \fi +\@ifdefinable\gobbledocstriptag{\def\gobbledocstriptag#1>{}} +\newcommand*\pmso[1] ^^A poor man's strike out + {^^A + \leavevmode + \begingroup + \sbox0{#1}^^A + \rlap{\vrule height .6ex depth -.5ex width \wd0\relax}^^A + \usebox0\relax + \endgroup + } +\makeatother +\begin{document} + \title + {^^A + \texorpdfstring + {^^A + \huge\expkv + \\[\medskipamount] + \Large an {\expFormat}andable + \meta{{\kvstyle k}\kern-.05em ey}=^^A + \meta{{\kvstyle v}\kern-.05em alue} + implementation^^A + }{expkv - an expandable <key>=<value> implementation}^^A + } + \date{\ekvDate\space v\ekvVersion} + \author{Jonathan P. Spratte\thanks{\protect\randomize{jspratte@yahoo.de}}} + \DocInput{expkv.dtx} +\end{document} +%</driver>^^A=<< +% \fi +% +% \maketitle +% \renewcommand*\thefootnote{\arabic{footnote}} +% +% \begin{abstract} +% \noindent\parfillskip=0pt +% \expkv\ provides a small interface for \kv\ parsing. The parsing macro is +% \emph{fully expandable}, the \meta{code} of your keys might be not. \expkv\ is +% \emph{swift}, close to the fastest \kv\ implementation. However it is the +% fastest which copes with active commas and equal signs and doesn't strip +% braces accidentally. +% \end{abstract} +% +% \tableofcontents +% +% \begin{documentation}^^A>>= +% +% \section{Documentation} +% +% \expkv\ provides an expandable \kv\ parser. The \kv\ pairs should be given as +% a comma separated list and the separator between a \key\ and the associated +% \val\ should be an equal sign. Both, the commas and the equal signs, might be +% of category 12 (other) or 13 (active). To support this is necessary as for +% example \pkg{babel} turns characters active for some languages, for instance +% the equal sign is turned active for Turkish. +% +% \expkv\ is usable as generic code, as a \LaTeX\ package or as a \ConTeXt\ +% module. To use it, just use one of: +% \begin{lstlisting} +% \input expkv % plainTeX +% \usepackage{expkv} % LaTeX +% \usemodule[expkv] % ConTeXt +% \end{lstlisting} +% Both the \LaTeX\ package and the \ConTeXt\ module don't do more than +% \file{expkv.tex}, except calling |\ProvidesPackage| and setting things up such +% that \file{expkv.tex} will use |\ProvidesFile|, or printing some status +% information. The \ConTeXt\ support is not thoroughly tested, though (since I +% don't use \ConTeXt\ myself I don't know if there are additional pitfalls I +% wasn't aware of). +% +% In the \expkv\ family are other packages contained which provide additional +% functionality. Those packages currently are: +% \begin{description}[format=\expkvpkg] +% \item[def] +% 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}). +% +% 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 +% provides a more sophisticated interface, similar to well established packages +% like \pkg{pgfkeys} or \pkg{l3keys}. +% +% Keys in \expkv\ (as in almost all other \kv\ implementations) belong to a +% \emph{set} such that different sets can contain keys of the same name. Unlike +% many other implementations \expkv\ doesn't provide means to set a default +% value, instead we have keys that take values and keys that don't (the latter +% are called |NoVal| keys by \expkv), but both can have the same name (on the +% user level). +% +% The following macros are available to define new keys. Those macros containing +% ``|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. +% \set\ will be used as is inside of |\csname ...\endcsname| and \key\ will get +% |\detokenize|d. Also \set\ should not contain an explicit |\par| token. +% +% \begin{function}{\ekvdef} +% \begin{syntax} +% \cs{ekvdef}\marg{set}\marg{key}\marg{code} +% \end{syntax} +% 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@text{#1}} +% \end{lstlisting} +% +% \begin{function}{\ekvdefNoVal} +% \begin{syntax} +% \cs{ekvdefNoVal}\marg{set}\marg{key}\marg{code} +% \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} +% \cs{ekvlet}\marg{set}\marg{key}\meta{cs} +% \end{syntax} +% Let the value taking \key\ in \set\ to \meta{cs}, there are no checks on +% \meta{cs} enforced, but the code should expect the value as a single braced +% argument directly following it. +% \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} +% \cs{ekvletNoVal}\marg{set}\marg{key}\meta{cs} +% \end{syntax} +% 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} +% \cs{ekvletkv}\marg{set}\marg{key}\marg{set2}\marg{key2} +% \end{syntax} +% Let the \key\ in \set\ to \meta{key2} in \meta{set2}, it is not checked +% 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} +% \cs{ekvletkvNoVal}\marg{set}\marg{key}\marg{set2}\marg{key2} +% \end{syntax} +% Let the \key\ in \set\ to \meta{key2} in \meta{set2}, it is not checked +% whether that second key exists (but take a look at +% \cs[no-index]{ekvifdefinedNoVal}). +% \end{function} +% \example See above. +% +% \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{} +% |\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} +% \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}. +% |\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 an |unwanted value| 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 +% |missing value| error. +% |\ekvdefunknownNoVal| and |\ekvredirectunknownNoVal| are mutually exclusive, +% you can't use both. +% \end{function} +% \example See above. +% +% \subsection{Parsing Keys} +% +% \begin{function}{\ekvset} +% \begin{syntax} +% \cs{ekvset}\marg{set}\{\kv,\ldots\} +% \end{syntax} +% Splits \kv\ pairs on commas. From both \key\ and \val\ up to one space is +% stripped from both ends, if then only a braced group remains the braces are +% stripped as well. So |\ekvset{foo}{bar=baz}| and +% |\ekvset{foo}{ {bar}= {baz} }| will both do +% \texttt{\cs[no-index]{\meta{foobarcode}}\{baz\}}, so you can hide commas, +% equal signs and spaces at the ends of either \key\ or \val\ by putting +% braces around them. If you omit the equal sign the code of the key created +% with the |NoVal| variants described in \autoref{sec:define} will be +% executed. If \kv\ contains more than a single unhidden equal sign, it will +% be split at the first one and the others are considered part of the value. +% |\ekvset| should be nestable. +% +% |\ekvset| is currently \emph{not} alignment safe.\footnotemark\ As a result, +% key names and values that contain an |&| must be wrapped in braces when +% |\ekvset| is used inside an alignment (like \LaTeXe's |tabular| environment) +% or you have to create a wrapper that ensures an alignment safe context. +% \end{function} +% \footnotetext{This might change in the future, I've not decided yet.} +% \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} +% \cs{ekvsetSneaked}\marg{set}\marg{sneak}\{\kv,\ldots\} +% \end{syntax} +% Just like \cs[no-index]{ekvset}, this macro parses the \kv\ pairs within the +% given \set. But \cs[no-index]{ekvsetSneaked} will behave as if +% \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} +% \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 faster than but +% else equivalent to the idiomatic definition:\par +% \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} +% \cs{ekvsetSneakeddef}\meta{cs}\marg{set} +% \end{syntax} +% Just like \cs[no-index]{ekvsetdef} this defines a shorthand macro \meta{cs}, +% but this macro will make it a shorthand for \cs[no-index]{ekvsetSneaked}, +% meaning that \meta{cs} will take two arguments, the first being stuff that +% should be given to \cs[no-index]{ekvsneak} and the second the \kv\ list. The +% resulting macro is faster than but else equivalent to the idiomatic +% definition:\par +% \texttt +% {% +% \string\long\string\def\meta{cs}\#1\#2\{^^A +% \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{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 +% equivalent to the idiomatic definition:\par +% \texttt +% {% +% \string\long\string\def\meta{cs}\#1\{^^A +% \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} +% \cs{ekvparse}\marg{code1}\marg{code2}\{\kv,\ldots\} +% \end{syntax} +% This macro parses the \kv\ pairs and provides those list elements which are +% only keys as an argument to \meta{code1}, and those which are a \kv\ pair +% to \meta{code2} as two arguments. It is fully expandable as well and returns +% each element of the parsed list in |\unexpanded|, which has no effect +% outside of an |\expanded| or |\edef| context. +% Also |\ekvparse| expands in exactly two steps of expansion. +% You can use multiple tokens in \meta{code1} and \meta{code2} or just a +% single control sequence name. In both cases the found \key\ and \val\ are +% provided as a brace group following them. +% +% |\ekvparse| is alignment safe, meaning that you don't have to take any +% precautions if it is used inside an alignment context (like \LaTeXe's +% |tabular| environment) and any key or value can contain an |&|. +% +% |\ekvbreak|, |\ekvsneak|, and |\ekvchangeset| and their relatives don't work +% in |\ekvparse|. It is analogue to \pkg{expl3}'s |\keyval_parse:NNn|, but +% not with the same parsing rules -- |\keyval_parse:NNn| throws an error on +% multiple equal signs per \kv\ pair and on empty \key\ names in a \kv\ pair, +% both of which |\ekvparse| doesn't deal with. +% \end{function} +% \example +% \begin{lstlisting} +% \ekvparse{\handlekey{S}}{\handlekeyval{S}}{foo = bar, key, baz={zzz}} +% \end{lstlisting} +% would be equivalent to +% \begin{lstlisting} +% \handlekeyval{S}{foo}{bar}\handlekey{S}{key}\handlekeyval{S}{baz}{zzz} +% \end{lstlisting} +% and afterwards |\handlekey| and |\handlekeyval| would have to further handle +% the \key. There are no macros like these two contained in \expkv, you have +% to set them up yourself if you want to use |\ekvparse| (of course the names +% might differ). If you need the results of |\ekvparse| as the argument for +% another macro, you should use |\expanded|, or expand |\ekvparse| twice, as +% only then the input stream will contain the output above: +% \begin{lstlisting} +% \expandafter\parse\expanded{\ekvparse\k\kv{foo = bar, key, baz={zzz}}} +% \end{lstlisting} +% or +% \begin{lstlisting} +% \expandafter\expandafter\expandafter +% \parse\ekvparse\k\kv{foo = bar, key, baz={zzz}} +% \end{lstlisting} +% would both expand to +% \begin{lstlisting} +% \parse\kv{foo}{bar}\k{key}\kv{baz}{zzz} +% \end{lstlisting} +% +% \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} +% \cs{ekvifdefinedNoVal}\marg{set}\marg{key}\marg{true}\marg{false} +% \end{syntax} +% These two macros test whether there is a \key\ in \set. It is false if +% 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} +% \cs{ekvifdefinedset}\marg{set}\marg{true}\marg{false} +% \end{syntax} +% 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} +% \cs{ekvbreak}\marg{after} +% \end{syntax} +% Gobbles the remainder of the current |\ekvset| macro and its argument list +% and reinserts \meta{after}. So this can be used to break out of |\ekvset|. +% The first variant will also gobble anything that has been sneaked out using +% |\ekvsneak| or |\ekvsneakPre|, while |\ekvbreakPreSneak| will put +% \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} +% \cs{ekvsneak}\marg{after} +% \end{syntax} +% Puts \meta{after} after the effects of |\ekvset|. The first variant will put +% \meta{after} after any other tokens which might have been sneaked before, +% while |\ekvsneakPre| will put \meta{after} before other smuggled stuff. +% This reads and reinserts the remainder of the current |\ekvset| macro and +% its argument list to do its job. After |\ekvset| has parsed the entire \kv\ +% 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 +% |\foo@secretly@sneaked|: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \ekvdefNoVal{foo}{secret}{\ekvsneak{\foo@secretly@sneaked}} +% \end{lstlisting} +% +% \begin{function}{\ekvchangeset} +% \begin{syntax} +% \cs{ekvchangeset}\marg{new-set} +% \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}}. 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} +% +% \begin{function}{\ekvoptarg} +% \begin{syntax} +% \cs{ekvoptarg}\marg{next}\marg{default} +% \end{syntax} +% This macro will check for a following optional argument in brackets (|[]|) +% expandably. After the optional argument there has to be a mandatory one. The +% code in \meta{next} should expect two arguments (the processed optional +% argument and the mandatory one). If there was an optional argument the +% result will be \meta{next}\marg{optional}\meta{mandatory} (so the optional +% argument will be wrapped in braces, the mandatory argument will be +% untouched). If there was no optional argument the result will be +% \meta{next}\marg{default}\marg{mandatory} (so the default will be used and +% the mandatory argument will be wrapped in braces after being read once -- if +% it was already wrapped it is effectively unchanged). +% \end{function} +% |\ekvoptarg| expands in exactly two steps, grabs all the arguments only at the +% second expansion step, and is alignment safe. It has its limitations however. +% It can't tell the difference between |[| and |{[}|, so it doesn't work if the +% mandatory argument is a single bracket. Also if the optional argument should +% contain a nested closing bracket, the optional argument has to use nested +% braces like so: |[{arg]ument}]|. +% \example Say we have a macro that should take an optional argument defaulting +% to 1: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \newcommand\foo{\ekvoptarg\@foo{1}} +% \newcommand\@foo[2]{Mandatory: #2\par Optional: #1} +% \end{lstlisting} +% +% \begin{function}{\ekvoptargTF} +% \begin{syntax} +% \cs{ekvoptargTF}\marg{true}\marg{false} +% \end{syntax} +% This macro is similar to |\ekvoptarg|, but will result in +% \meta{true}\marg{optional}\meta{mandatory} or \meta{false}\marg{mandatory} +% instead of placing a default value. +% \end{function} +% |\ekvoptargTF| expands in exactly two steps, grabs all the arguments only at +% the second expansion step, and is alignment safe. It has the same limitations +% as |\ekvoptarg|. +% \example Say we have a macro that should behave differently depending on +% whether there was an optional argument or not. This could be done with: +% \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] +% \newcommand\foo{\ekvoptargTF\foo@a\foo@b} +% \newcommand\foo@a[2]{Mandatory: #2\par Optional: #1} +% \newcommand\foo@b[1]{Mandatory: #1\par No optional.} +% \end{lstlisting} +% +% \begin{function}{\ekvcsvloop} +% \begin{syntax} +% \cs{ekvcsvloop}\marg{code}\marg{csv-list} +% \end{syntax} +% This loops over the comma separated items in \meta{csv-list} and, after +% stripping spaces from either end of \meta{item} and removing at most one set +% of outer braces, leaves +% \texttt +% {^^A +% \cs[no-index]{unexpanded}\hskip0pt +% \{\hskip0pt +% \meta{code}\hskip0pt +% \marg{item}\hskip0pt +% \}^^A +% } +% for each list item in the input stream. Blank elements are ignored (if you +% need a blank element it should be given as |{}|). It supports both active +% commas and commas of category other. You could consider it as a watered down +% version of |\ekvparse|. However it is not alignment safe, which you could +% achieve by nesting it in |\expanded| (since the braces around the argument +% of |\expanded| will hide |&|s from \TeX's alignment parsing). +% \end{function} +% \example The following splits a comma separated list and prints it in a +% typewriter font with parentheses around each element.\\ +% \begin{minipage}{.75\linewidth} +% \begin{lstlisting} +% \newcommand*\myprocessor[1]{\texttt{(#1)}} +% \ekvcsvloop\myprocessor{abc,def,ghi}\par +% \ekvcsvloop\myprocessor{1,,2,,3,,4}\par +% \end{lstlisting} +% \end{minipage}^^A +% \begin{exresult}[nobeforeafter,box align=center]{.25\linewidth} +% \newcommand*\myprocessor[1]{\texttt{(#1)}} +% \ekvcsvloop\myprocessor{abc,def,ghi}\par +% \ekvcsvloop\myprocessor{1,,2,,3,,4}\par +% \end{exresult} +% +% \begin{function}{\ekverr} +% \begin{syntax} +% \cs{ekverr}\marg{package}\marg{message} +% \end{syntax} +% This macro will throw an error fully expandably.\footnotemark\ The error +% length is limited to a total length of 69~characters, and since ten +% characters will be added for the formatting (\verb*|! | and +% \verb*| Error: |) that leaves us with a total length for \meta{package} plus +% \meta{message} of 59~characters. If the message gets longer \TeX\ will only +% display the first 69~characters and append |\ETC.| to the end. +% +% Neither \meta{package} nor \meta{message} expand any further. Also +% \meta{package} must not contain an explicit |\par| token or the token +% |\thanks@jfbu|. No such restriction applies to \meta{message}. +% +% If |^^J| is set up as the |\newlinechar| (which is the case in \LaTeXe\ but +% not in plain \TeX\ by default) you can use that to introduce line breaks in +% your error message. However that doesn't change the message length limit. +% \end{function} +% \footnotetext{The used mechanism was to the best of my knowledge first +% implemented by Jean-François Burnol.} +% After your own error message some further text will be placed. The +% formatting of that text will look good if |^^J| is the |\newlinechar|, else +% not so much. That text will read: +% \begin{verbatim} +% ! Paragraph ended before \<an-expandable-macro> +% completed due to above exception. If the error +% summary is not comprehensible see the package +% documentation. +% I will try to recover now. If you're in inter- +% active mode hit <return> at the ? prompt and I +% continue hoping recovery was complete. +% \end{verbatim} +% Any clean up has to be done by you, |\ekverr| will expand to nothing after +% throwing the error message. +% +% In \ConTeXt\ this macro works differently. While still being fully expandable, +% it doesn't have the character count limitation and doesn't impose restrictions +% on \meta{package}. It will not display the additional text and adding line +% breaks is not possible. +% \example Say we set up a small calculation which works with user input. In our +% calculation we need a division, so have to watch out for division by zero. If +% we detect such a case we throw an error and do the recovery by using the +% biggest integer allowed in \TeX\ as the result. +% \begin{lstlisting} +% \newcommand*\mydivision[2] +% {% +% \the\numexpr +% \ifnum\numexpr#2=0 % space here on purpose +% \ekverr{my}{division by 0. Setting result to 2147483647.}% +% 2147483647% +% \else +% (#1)/(#2)% +% \fi +% \relax +% } +% $(10+5)/(3-3)\approx\mydivision{10+5}{3-3}$ +% \end{lstlisting} +% If that code gets executed the following will be the terminal output +% \begin{verbatim} +% Runaway argument? +% ! my Error: division by 0. Setting result to 2147483647. +% ! Paragraph ended before \<an-expandable-macro> +% completed due to above exception. If the error +% summary is not comprehensible see the package +% documentation. +% I will try to recover now. If you're in inter- +% active mode hit <return> at the ? prompt and I +% continue hoping recovery was complete. +% <to be read again> +% \par +% l.15 $(10+5)/(3-3)\approx\mydivision{10+5}{3-3} +% $ +% ? +% \end{verbatim} +% and the output would contain +% \exres{$(10+5)/(3-3)\approx2147483647$} +% if we continued the \TeX\ run at the prompt. +% +% \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@key}\marg{key} +% \end{syntax} +% The names of the macros that correspond to a key in a set are build with +% these macros. The name is built from two blocks, one that is formatting the +% \set\ name (|\ekv@name@set|) and one for formatting the \key\ name +% (|\ekv@name@key|). To get the actual name the argument to |\ekv@name@key| +% must be |\detokenize|d. Both blocks are put together (with the necessary +% |\detokenize|) by |\ekv@name|. +% For |NoVal| keys an additional |N| gets appended irrespective of these +% macros' definition, so their name is +% \texttt{\cs[no-index]{ekv@name}\marg{set}\marg{key}N}.\par +% You can use these macros to implement additional functionality or access key +% macros outside of \expkv, but \emph{don't} change them! \expkv\ relies on +% their exact definitions internally. +% \end{function} +% \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} +% +% \subsection{Examples} +% +% \subsubsection{Standard Use-Case} +% +% Say we have a macro for which we want to create a \kv\ interface. The macro +% has a parameter, which is stored in the dimension |\ourdim| having a default +% value from its initialisation. Now we want to be able to change that dimension +% with the |width| key to some specified value. For that we'd do +% \begin{lstlisting} +% \newdimen\ourdim +% \ourdim=150pt +% \protected\ekvdef{our}{width}{\ourdim=#1\relax} +% \end{lstlisting} +% as you can see, we use the set |our| here. We want the key to behave different +% if no value is specified. In that case the key should not use its initial +% value, but be smart and determine the available space from +% \cs[no-index]{hsize}, so we also define +% \begin{lstlisting} +% \protected\ekvdefNoVal{our}{width}{\ourdim=.9\hsize} +% \end{lstlisting} +% Now we set up our macro to use this \kv\ interface +% \begin{lstlisting} +% \protected\def\ourmacro#1% +% {\begingroup\ekvset{our}{#1}\the\ourdim\endgroup} +% \end{lstlisting} +% Finally we can use our macro like in the following\\ +% \begin{minipage}{.5\linewidth} +% \begin{lstlisting} +% \ourmacro{}\par +% \ourmacro{width}\par +% \ourmacro{width=5pt}\par +% \end{lstlisting} +% \end{minipage}^^A +% \begin{exresult}[nobeforeafter,box align=center]{.5\linewidth} +% \newdimen\ourdim\ourdim=150pt +% \protected\ekvdef{our}{width}{\ourdim=#1\relax}^^A +% \protected\ekvdefNoVal{our}{width}{\ourdim=.9\hsize}^^A +% \protected\def\ourmacro#1^^A +% {\begingroup\ekvset{our}{#1}\the\ourdim\endgroup}^^A +% \ourmacro{}\par +% \ourmacro{width}\par +% \ourmacro{width=5pt}\par +% \end{exresult} +% +% \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 +% us, so the result will be exactly the same -- with the exception that the +% default will use |\ourdim=.9\hsize\relax| instead. +% \begin{lstlisting} +% \input expkv-def % or \usepackage{expkv-def} +% \ekvdefinekeys{our} +% { +% dimen width = \ourdim, +% qdefault width = .9\hsize, +% initial width = 150pt +% } +% \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 macros used 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}} +% \protected\ekvdef{myrule}{lower}{\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}{.5\linewidth} +% \begin{lstlisting} +% a\myrule\par +% a\myrule[ht=2ex,lower=.5ex]\par +% \myruleset{wd=5pt} +% a\myrule +% \end{lstlisting} +% \end{minipage}^^A +% \begin{exresult}[nobeforeafter,box align=center]{.5\linewidth} +% \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}} +% \protected\ekvdef{myrule}{lower}{\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,lower=.5ex]\par +% \myruleset{wd=5pt} +% a\myrule +% \end{exresult} +% +% +% \subsubsection{An Expandable \kv\ Macro Using \cs[no-index]{ekvsneak}} +% \label{sec:sneakex} +% +% Let's set up an expandable macro, that uses a \kv\ interface. The problems +% we'll face for this are: +% \begin{enumerate} +% \item ignoring duplicate keys +% \item default values for keys which weren't used +% \item providing the values as the correct argument to a macro (ordered) +% \end{enumerate} +% +% First we need to decide which \kv\ parsing macro we want to do this with, +% |\ekvset| or |\ekvparse|. For this example we also want to show the +% usage of |\ekvsneak|, hence we'll choose |\ekvset|. And we'll have to use +% |\ekvset| such that it builds a parsable list for our macro internals. To +% gain back control after |\ekvset| is done we have to put an internal of our +% macro at the start of that list, so we use an internal key that uses +% |\ekvsneakPre| after any user input. +% +% To ignore duplicates will be easy if the value of the key used last will be +% put first in the list, so the following will use |\ekvsneakPre| for the +% user-level keys. If we wanted some key for which the first usage should be the +% binding one we would use |\ekvsneak| instead for that key. +% +% Providing default values can be done in different ways, we'll use a simple +% approach in which we'll just put the outcome of our keys if they were used +% with default values before the parsing list terminator. +% +% Ordering the keys can be done simply by searching for a specific token for +% each argument which acts like a flag, so our sneaked out values will include +% specific tokens acting as markers. +% +% Now that we have answers for our technical problems, we have to decide what +% our example macro should do. How about we define a macro that calculates the +% sine of a number and rounds that to a specified precision? As a small extra +% this macro should understand input in radian and degree and the used +% trigonometric function should be selectable as well. For the hard part of this +% task (expandably evaluating trigonometric functions) we'll use the \pkg{xfp} +% package. +% +% First we set up our keys according to our earlier considerations and set up +% the user facing macro |\sine|. The end marker of the parsing list will be a +% |\sine@stop| token, which we don't need to define and we put our defaults +% right before it. The user macro |\sine| uses |\ekvoptargTF| to check for the +% optional argument short cutting to the final step if no optional argument was +% found. This way we safe some time in this case, though we have to specify the +% default values twice. +% +% \begin{lstlisting} +% \RequirePackage{xfp} +% \makeatletter +% \ekvdef{expex}{f}{\ekvsneakPre{\f{#1}}} +% \ekvdef{expex}{round}{\ekvsneakPre{\rnd{#1}}} +% \ekvdefNoVal{expex}{degree}{\ekvsneakPre{\deg{d}}} +% \ekvdefNoVal{expex}{radian}{\ekvsneakPre{\deg{}}} +% \ekvdefNoVal{expex}{internal}{\ekvsneakPre{\sine@rnd}} +% \newcommand*\sine{\ekvoptargTF\sine@args{\sine@final{sin}{d}{3}}} +% \newcommand*\sine@args[2] +% {\ekvset{expex}{#1,internal}\rnd{3}\deg{d}\f{sin}\sine@stop{#2}} +% \end{lstlisting} +% +% Now we need to define some internal macros to extract the value of each key's +% last usage (remember that this will be the group after the first special +% flag-token). For that we use one delimited macro per key. +% \begin{lstlisting} +% \def\sine@rnd#1\rnd#2#3\sine@stop{\sine@deg#1#3\sine@stop{#2}} +% \def\sine@deg#1\deg#2#3\sine@stop{\sine@f#1#3\sine@stop{#2}} +% \def\sine@f#1\f#2#3\sine@stop{\sine@final{#2}} +% \end{lstlisting} +% After the macros |\sine@rnd|, |\sine@deg|, and |\sine@f| the macro +% |\sine@final| will see +% \texttt +% {^^A +% \cs[no-index]{sine@final}^^A +% \marg{f}\marg{degree/radian}\marg{round}\marg{num}^^A +% }. +% Now |\sine@final| has to expandably deal with those arguments such that the +% |\fpeval| macro of \pkg{xfp} gets the correct input. Luckily this is pretty +% straight forward in this example. In |\fpeval| the trigonometric functions +% have names such as |sin| or |cos| and the degree taking variants |sind| or +% |cosd|. And since the |degree| key puts a |d| in |#2| and the |radian| key +% leaves |#2| empty all we have to do to get the correct function name is stick +% the two together. +% \begin{lstlisting} +% \newcommand*\sine@final[4]{\fpeval{round(#1#2(#4),#3)}} +% \makeatother +% \end{lstlisting} +% Let's test our macro:\\ +% \begin{minipage}{.75\linewidth} +% \begin{lstlisting}[gobble=4] +% \sine{60}\par +% \sine[round=10]{60}\par +% \sine[f=cos,radian]{pi}\par +% \edef\myval{\sine[f=tan]{1}}\texttt{\meaning\myval} +% \end{lstlisting} +% \end{minipage}^^A +% \begin{exresult}[nobeforeafter,box align=center]{.25\linewidth} +% \makeatletter +% \ekvdef{expex}{f}{\ekvsneakPre{\f{#1}}}^^A +% \ekvdef{expex}{round}{\ekvsneakPre{\rnd{#1}}}^^A +% \ekvdefNoVal{expex}{degree}{\ekvsneakPre{\deg{d}}}^^A +% \ekvdefNoVal{expex}{radian}{\ekvsneakPre{\deg{}}}^^A +% \ekvdefNoVal{expex}{internal}{\ekvsneakPre{\sine@rnd}}^^A +% \newcommand*\sine{\ekvoptargTF\sine@args{\sine@final{sin}{d}{3}}} +% \newcommand*\sine@args[2] +% {\ekvset{expex}{#1,internal}\rnd{3}\deg{d}\f{sin}\sine@stop{#2}} +% \def\sine@rnd#1\rnd#2#3\sine@stop{\sine@deg#1#3\sine@stop{#2}}^^A +% \def\sine@deg#1\deg#2#3\sine@stop{\sine@f#1#3\sine@stop{#2}}^^A +% \def\sine@f#1\f#2#3\sine@stop{\sine@final{#2}}^^A +% \newcommand*\sine@final[4]{\fpeval{round(#1#2(#4),#3)}}^^A +% \makeatother +% \sine{60}\par +% \sine[round=10]{60}\par +% \sine[f=cos,radian]{pi}\par +% \edef\myval{\sine[f=tan]{1}}\texttt{\meaning\myval} +% \end{exresult} +% +% \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 for the user. +% \begin{lstlisting} +% \makeatletter +% \newcommand*\sine{\ekvoptargTF\sine@a{\sine@b{sin}{d}{3}}} +% \ekvcSplitAndForward\sine@a\sine@b +% { +% f=sin, +% unit=d, +% round=3, +% } +% \ekvcSecondaryKeys\sine@a +% { +% nmeta degree={unit=d}, +% nmeta radian={unit={}}, +% } +% \newcommand*\sine@b[4]{\fpeval{round(#1#2(#4),#3)}} +% \makeatother +% \end{lstlisting} +% The resulting macro will behave just like the one previously defined, but will +% have an additional |unit| key, since in \expkvc\ every argument must have a +% value taking key which defines it. +% +% +% \subsection{Error Messages} +% +% \expkv\ should only send messages in case of errors, there are no warnings and +% no info messages. In this subsection those errors are listed. +% +% \subsubsection{Load Time} +% +% \file{expkv.tex} checks whether \eTeX\ and the |\expanded| primitive are +% available. If it isn't, an error will be thrown using |\errmessage|: +% \begin{lstlisting} +% ! expkv Error: e-TeX and \expanded required. +% \end{lstlisting} +% +% \subsubsection{Defining Keys} +% +% If you get any error from \expkv\ while you're trying to define a key, the +% definition will be aborted and gobbled. +% +% If you try to define a key with an empty set name you'll get: +% \begin{lstlisting} +% ! expkv Error: empty set name not allowed. +% \end{lstlisting} +% +% Similarly, if you try to define a key with an empty key name: +% \begin{lstlisting} +% ! expkv Error: empty key name not allowed. +% \end{lstlisting} +% +% Both of these messages are done in a way that doesn't throw additional errors +% due to |\global|, |\long|, etc., not being used correctly if you prefixed one +% of the defining macros. +% +% \subsubsection{Using Keys} +% +% This subsubsection contains the errors thrown during |\ekvset|. The errors are +% thrown in an expandable manner using |\ekverr|. In the following messages +% \lstinline|<key>| gets replaced with the problematic key's name, and +% \lstinline|<set>| with the corresponding set. If any errors during \kv\ +% handling are encountered, the entry in the comma separated list will be +% omitted after the error is thrown and the next \kv\ pair will be parsed. +% +% If you're using an undefined key you'll get: +% \begin{lstlisting} +% Runaway argument? +% ! expkv Error: unknown key `<key>' in set `<set>' +% \end{lstlisting} +% +% If you're using a key for which only a normal version and no |NoVal| version +% is defined, but don't provide a value, you'll get: +% \begin{lstlisting} +% Runaway argument? +% ! expkv Error: missing value for `<key>' in set `<set>' +% \end{lstlisting} +% +% If you're using a key for which only a |NoVal| version and no normal version +% is defined, but provide a value, you'll get: +% \begin{lstlisting} +% Runaway argument? +% ! expkv Error: unwanted value for `<key>' in set `<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} +% Runaway argument? +% ! expkv Error: no key `<key>' in sets {<set1>}{<set2>}... +% \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} +% Runaway argument? +% ! expkv Error: no NoVal key `<key>' in sets {<set1>}{<set2>}... +% \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 +% not be gracefully ignored). The error will look like +% \begin{lstlisting}[language={}] +% ! Missing \endcsname inserted. +% <to be read again> +% \! expkv Error: Set `<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 } % setup +% \setparameterlist{yax}{ height = 6 } % benchmark +% \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\ +% Jonathan P. Spratte +% +% \medskip\noindent +% 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 +% (at your option) any later version. The latest version of this license is in +% the file: +% +% \url{http://www.latex-project.org/lppl.txt} +% +% \noindent +% This work is ``maintained'' (as per LPPL maintenance status) by +% +% \mbox{Jonathan P. Spratte}. +% +% \end{documentation}^^A=<< +% +% \begin{implementation}^^A>>= +% +% \clearpage +% +% \section{Implementation}^^A>>= +%^^A the LaTeX package >>= +% \subsection{The \LaTeX\ Package} +% First we set up the \LaTeX\ package. That one doesn't really do much except +% |\input|ting the generic code and identifying itself as a package. +% \gobbledocstriptag +%<*pkg> +% \begin{macrocode} +\def\ekv@tmp + {% + \ProvidesFile{expkv.tex}% + [\ekvDate\space v\ekvVersion\space an expandable key=val implementation]% + } +\input{expkv.tex} +\ProvidesPackage{expkv}% + [\ekvDate\space v\ekvVersion\space an expandable key=val implementation] +% \end{macrocode} +% \gobbledocstriptag +%</pkg> +%^^A=<< +%^^A the ConTeXt module >>= +% \subsection{The \ConTeXt\ module} +% This is pretty straight forward, we just have to change the error throwing +% mechanism for \ConTeXt\ (the approach taken for plain and \LaTeX\ breaks in +% \ConTeXt, effectively breaking \ConTeXt, dropping you in an interactive \TeX\ +% session with almost no means of escape). +% \gobbledocstriptag +%<*ctx> +% \begin{macrocode} +\writestatus{loading}{ConTeXt User Module / expkv} +\unprotect +\input expkv.tex +\long\def\ekv@err@collect#1\par#2% + {\directlua{tex.error[[\detokenize{#2} Error: #1]]}} +\writestatus{loading} + {ConTeXt User Module / expkv / Version \ekvVersion\space loaded} +\protect\endinput +% \end{macrocode} +% \gobbledocstriptag +%</ctx> +%^^A=<< +%^^A main file >>= +% \subsection{The Generic Code} +% The rest of this implementation will be the generic code. +% \gobbledocstriptag +%<*tex> +% +% We make sure that it's only input once: +% \begin{macrocode} +\expandafter\ifx\csname ekvVersion\endcsname\relax +\else + \expandafter\endinput +\fi +% \end{macrocode} +% +% Check whether \eTeX\ and |\expanded| are available -- \expkv\ requires \eTeX. +% \begin{macrocode} +\begingroup + \edef\ekvtmpa{\string\expanded} + \edef\ekvtmpb{\meaning\expanded} + \expandafter +\endgroup +\ifx\ekvtmpa\ekvtmpb + \expandafter\let\csname ekv@expanded\endcsname\expanded + \expandafter\let\csname ekv@unexpanded\endcsname\unexpanded +\else + \begingroup + \edef\ekvtmpa{\string\expanded} + \edef\ekvtmpb{\meaning\normalexpanded} + \expandafter + \endgroup + \ifx\ekvtmpa\ekvtmpb + \expandafter\let\csname ekv@expanded\endcsname\normalexpanded + \expandafter\let\csname ekv@unexpanded\endcsname\normalunexpanded + \else + \errmessage + {expkv Error: e-TeX and the \noexpand\expanded primitive required}% + \expandafter\endinput + \fi +\fi +% \end{macrocode} +% +% \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.9a} +\def\ekvDate{2021-09-20} +% \end{macrocode} +% \end{macro} +% +% If the \LaTeX\ format is loaded we want to be a good file and report back who +% we are, for this the package will have defined |\ekv@tmp| to use +% |\ProvidesFile|, else this will expand to a |\relax| and do no harm. +% \begin{macrocode} +\csname ekv@tmp\endcsname +% \end{macrocode} +% +% Store the category code of |@| to later be able to reset it and change it to +% 11 for now. +% \begin{macrocode} +\expandafter\chardef\csname ekv@tmp\endcsname=\catcode`\@ +\catcode`\@=11 +% \end{macrocode} +% |\ekv@tmp| might later be reused to gobble any prefixes which might be +% provided to |\ekvdef| and similar in case the names are invalid, we just +% temporarily use it here as means to store the current category code of |@| to +% restore it at the end of the file, we never care for the actual definition of +% it. +% +% \begin{macro}[internal]{\ekv@if@lastnamedcs} +% If the primitive |\lastnamedcs| is available, we can be a bit faster than +% without it. So we test for this and save the test's result in this macro. +% \begin{macrocode} +\begingroup + \edef\ekv@tmpa{\string \lastnamedcs} + \edef\ekv@tmpb{\meaning\lastnamedcs} + \ifx\ekv@tmpa\ekv@tmpb + \def\ekv@if@lastnamedcs{\long\def\ekv@if@lastnamedcs##1##2{##1}} + \else + \def\ekv@if@lastnamedcs{\long\def\ekv@if@lastnamedcs##1##2{##2}} + \fi + \expandafter +\endgroup +\ekv@if@lastnamedcs +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@empty} +% Sometimes we have to introduce a token to prevent accidental brace stripping. +% This token would then need to be removed by |\@gobble| or similar. Instead we +% can use |\ekv@empty| which will just expand to nothing, that is faster than +% gobbling an argument. +% \begin{macrocode} +\def\ekv@empty{} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal] +% { +% \@gobble,\@firstofone,\@firstoftwo,\@secondoftwo, +% \ekv@fi@gobble,\ekv@fi@firstofone,\ekv@fi@firstoftwo,\ekv@fi@secondoftwo, +% \ekv@gobble@mark,\ekv@gobbleto@stop,\ekv@gobble@from@mark@to@stop +% } +% Since branching tests are often more versatile than |\if...\else...\fi| +% constructs, we define helpers that are branching pretty fast. Also here are +% some other utility functions that just grab some tokens. The ones that are +% also contained in \LaTeX\ don't use the |ekv| prefix. Not all of the ones +% defined here are really needed by \expkv\ but are provided because packages +% like \expkvd\ or \expkvo\ need them (and I don't want to define them in each +% package which might need them). +% \begin{macrocode} +\long\def\@gobble#1{} +\long\def\@firstofone#1{#1} +\long\def\@firstoftwo#1#2{#1} +\long\def\@secondoftwo#1#2{#2} +\long\def\ekv@fi@gobble\fi\@firstofone#1{\fi} +\long\def\ekv@fi@firstofone\fi\@gobble#1{\fi#1} +\long\def\ekv@fi@firstoftwo\fi\@secondoftwo#1#2{\fi#1} +\long\def\ekv@fi@secondoftwo\fi\@firstoftwo#1#2{\fi#2} +\def\ekv@gobble@mark\ekv@mark{} +\long\def\ekv@gobbleto@stop#1\ekv@stop{} +\long\def\ekv@gobble@from@mark@to@stop\ekv@mark#1\ekv@stop{} +% \end{macrocode} +% \end{macro} +% As you can see |\ekv@gobbleto@stop| uses a special marker |\ekv@stop|. The +% package will use three such markers, the one you've seen already, |\ekv@mark| +% and |\ekv@nil|. Contrarily to how for instance \pkg{expl3} does things, we +% don't define them, as we don't need them to have an actual meaning. This has +% the advantage that if they somehow get expanded -- which should never happen +% if things work out -- they'll throw an error directly. +% +% \begin{macro}[internal] +% { +% \ekv@ifempty,\ekv@ifempty@,\ekv@ifempty@true,\ekv@ifempty@false, +% \ekv@ifempty@true@F,\ekv@ifempty@true@F@gobble, +% \ekv@ifempty@true@F@gobbletwo +% } +% We can test for a lot of things building on an if-empty test, so lets define a +% really fast one. Since some tests might have reversed logic (true if something +% is not empty) we also set up macros for the reversed branches. +% \begin{macrocode} +\long\def\ekv@ifempty#1% + {% + \ekv@ifempty@\ekv@ifempty@A#1\ekv@ifempty@B\ekv@ifempty@true + \ekv@ifempty@A\ekv@ifempty@B\@secondoftwo + } +\long\def\ekv@ifempty@#1\ekv@ifempty@A\ekv@ifempty@B{} +\long\def\ekv@ifempty@true\ekv@ifempty@A\ekv@ifempty@B\@secondoftwo#1#2{#1} +\long\def\ekv@ifempty@false\ekv@ifempty@A\ekv@ifempty@B\@firstoftwo#1#2{#2} +\long\def\ekv@ifempty@true@F\ekv@ifempty@A\ekv@ifempty@B\@firstofone#1{} +\long\def\ekv@ifempty@true@F@gobble\ekv@ifempty@A\ekv@ifempty@B\@firstofone#1#2% + {} +\long\def\ekv@ifempty@true@F@gobbletwo + \ekv@ifempty@A\ekv@ifempty@B\@firstofone#1#2#3% + {} +% \end{macrocode} +% \end{macro} +% +% \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. 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} +% +% \begin{macro}[internal]{\ekv@ifdefined} +% We'll need to check whether something is defined quite frequently, so why not +% define a macro that does this. The following test is expandable and pretty +% fast. The version with |\lastnamedcs| is the fastest version to test for an +% undefined macro I know of (that considers both undefined macros and those with +% the meaning |\relax|). +% \begin{macrocode} +\ekv@if@lastnamedcs + {% + \long\def\ekv@ifdefined#1{\ifcsname#1\endcsname\ekv@ifdef@\fi\@secondoftwo} + \def\ekv@ifdef@\fi\@secondoftwo + {% + \fi + \expandafter\ifx\lastnamedcs\relax + \ekv@fi@secondoftwo + \fi + \@firstoftwo + } + } + {% + \long\def\ekv@ifdefined#1% + {% + \ifcsname#1\endcsname\ekv@ifdef@\fi\ekv@ifdef@false#1\endcsname\relax + \ekv@fi@secondoftwo + \fi + \@firstoftwo + } + \def\ekv@ifdef@\fi\ekv@ifdef@false{\fi\expandafter\ifx\csname} + \long\def\ekv@ifdef@false + #1\endcsname\relax\ekv@fi@secondoftwo\fi\@firstoftwo#2#3% + {#3} + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@strip,\ekv@strip@a,\ekv@strip@b,\ekv@strip@c} +% We borrow some ideas of \pkg{expl3}'s \pkg{l3tl} to strip spaces +% from keys and values. This |\ekv@strip| also strips one level of outer braces +% \emph{after} stripping spaces, so an input of | {abc} | becomes |abc| after +% stripping. It should be used with |#1| prefixed by |\ekv@mark|. Also this +% implementation at most strips \emph{one} space from both sides (which should +% be fine most of the time, since \TeX\ reads consecutive spaces as a single one +% during tokenisation). +% \begin{macrocode} +\def\ekv@strip#1% + {% + \long\def\ekv@strip##1% + {% + \ekv@strip@a + ##1\ekv@nil + \ekv@mark#1% + #1\ekv@nil + }% + \long\def\ekv@strip@a##1\ekv@mark#1{\ekv@strip@b##1\ekv@mark}% + } +\ekv@strip{ } +\long\def\ekv@strip@b#1 \ekv@nil{\ekv@strip@c#1\ekv@nil} +\long\def\ekv@strip@c\ekv@mark#1\ekv@nil\ekv@mark#2\ekv@nil#3{#3{#1}} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal] +% {\ekv@exparg,\ekv@exparg@,\ekv@expargtwice,\ekv@expargtwice@,\ekv@zero} +% To reduce some code doublets while gaining some speed (and also as convenience +% for other packages in the family), it is often useful to expand the first +% token in a definition once. Let's define a wrapper for this. +% +% Also, to end a |\romannumeral| expansion, we want to use |\z@|, which is +% contained in both plain \TeX\ and \LaTeX, but we use a private name for it to +% make it easier to spot and hence easier to manage. +% \begin{macrocode} +\let\ekv@zero\z@ +\long\def\ekv@exparg#1#2{\expandafter\ekv@exparg@\expandafter{#2}{#1}} +\long\def\ekv@exparg@#1#2{#2{#1}}% +\long\def\ekv@expargtwice#1#2{\expandafter\ekv@expargtwice@\expandafter{#2}{#1}} +\def\ekv@expargtwice@{\expandafter\ekv@exparg@\expandafter} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ekvcsvloop} +% \begin{macro}[internal]{\ekv@csv@loop@active,\ekv@csv@loop@active@end} +% An |\ekvcsvloop| will just loop over a csv list in a simple manner. First we +% split at active commas (gives better performance this way), next we have to +% check whether we're at the end of the list (checking for |\ekv@stop|). If +% not we go on splitting at commas of category other. +% \begin{macrocode} +\begingroup +\def\ekvcsvloop#1{% +\endgroup +\long\def\ekvcsvloop##1##2% + {\ekv@csv@loop@active{##1}\ekv@mark##2#1\ekv@stop#1} +% \end{macrocode} +% This does the same as |\ekv@csv@loop| but for active commas. +% \begin{macrocode} +\long\def\ekv@csv@loop@active##1##2#1% + {% + \ekv@gobble@from@mark@to@stop##2\ekv@csv@loop@active@end\ekv@stop + \ekv@csv@loop{##1}##2,\ekv@stop,% + }% +\long\def\ekv@csv@loop@active@end + \ekv@stop + \ekv@csv@loop##1\ekv@mark\ekv@stop,\ekv@stop,% + {}% +} +% \end{macrocode} +% Do the definitions with the weird catcode. +% \begin{macrocode} +\catcode`\,=13 +\ekvcsvloop, +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@csv@loop,\ekv@csv@loop@do,\ekv@csv@loop@end} +% We use temporary macros and an |\expandafter| chain to preexpand +% |\ekv@strip| here. After splitting at other commas we check again for end +% the end of the sublist, check for blank elements which should be ignored, +% and else strip spaces and execute the user code (protecting it from further +% expanding with |\unexpanded|). +% \begin{macrocode} +\def\ekv@csv@loop#1% + {% + \long\def\ekv@csv@loop##1##2,% + {% + \ekv@gobble@from@mark@to@stop##2\ekv@csv@loop@end\ekv@stop + \ekv@ifblank@##2\ekv@nil\ekv@ifempty@B\ekv@csv@loop@blank + \ekv@ifempty@A\ekv@ifempty@B + #1\ekv@csv@loop@do{##1}% + }% + } +\expandafter\ekv@csv@loop\expandafter{\ekv@strip{#2}} +\long\def\ekv@csv@loop@do#1#2{\ekv@unexpanded{#2{#1}}\ekv@csv@loop{#2}\ekv@mark} +\def\ekv@csv@loop@end#1% + {% + \long\def\ekv@csv@loop@end + \ekv@stop + \ekv@ifblank@\ekv@mark\ekv@stop\ekv@nil\ekv@ifempty@B\ekv@csv@loop@blank + \ekv@ifempty@A\ekv@ifempty@B + #1\ekv@csv@loop@do##1% + {\ekv@csv@loop@active{##1}\ekv@mark}% + } +\expandafter\ekv@csv@loop@end\expandafter{\ekv@strip{\ekv@mark\ekv@stop}} +\long\expandafter\def\expandafter\ekv@csv@loop@blank + \expandafter\ekv@ifempty@A\expandafter\ekv@ifempty@B + \ekv@strip{\ekv@mark#1}\ekv@csv@loop@do#2% + {\ekv@csv@loop{#2}\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} +\def\ekv@name@set#1{ekv#1(} +\long\def\ekv@name@key#1{#1)} +\edef\ekv@name + {% + \ekv@unexpanded\expandafter{\ekv@name@set{#1}}% + \ekv@unexpanded\expandafter{\ekv@name@key{\detokenize{#2}}}% + } +\long\ekv@exparg{\def\ekv@name#1#2}{\ekv@name} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@undefined@set} +% We can misuse the macro name we use to expandably store the set-name in a +% single token -- since this increases performance drastically, especially for +% long set-names -- to throw a more meaningful error message in case a set isn't +% defined. The name of |\ekv@undefined@set| is a little bit misleading, as it is +% called in either case inside of |\csname|, but the result will be a control +% sequence with meaning |\relax| if the set is undefined, hence will break the +% |\csname| building the key-macro which will throw the error message. +% \begin{macrocode} +\def\ekv@undefined@set#1{! expkv Error: Set `#1' undefined.} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@checkvalid} +% We place some restrictions on the allowed names, though, namely sets and +% keys are not allowed to be empty -- blanks are fine (meaning \mbox{set- +% or} key-names consisting of spaces). The |\def\ekv@tmp| gobbles any \TeX\ +% prefixes which would otherwise throw errors. This will, however, break the +% package if an |\outer| has been gobbled this way. I consider that good, +% because keys shouldn't be defined |\outer| anyways. +% \begin{macrocode} +\edef\ekv@checkvalid + {% + \ekv@unexpanded\expandafter{\ekv@ifempty{#1}}% + \ekv@unexpanded + {{% + \def\ekv@tmp{}% + \errmessage{expkv Error: empty set name not allowed}% + }}% + {% + \ekv@unexpanded\expandafter{\ekv@ifempty{#2}}% + \ekv@unexpanded + {% + {% + \def\ekv@tmp{}% + \errmessage{expkv Error: empty key name not allowed}% + }% + \@secondoftwo + }% + }% + \ekv@unexpanded{\@gobble}% + } +\ekv@exparg{\protected\def\ekv@checkvalid#1#2}{\ekv@checkvalid}% +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ekvifdefined,\ekvifdefinedNoVal} +% And provide user-level macros to test whether a key is defined. +% \begin{macrocode} +\ekv@expargtwice{\long\def\ekvifdefined#1#2}% + {\expandafter\ekv@ifdefined\expandafter{\ekv@name{#1}{#2}}} +\ekv@expargtwice{\long\def\ekvifdefinedNoVal#1#2}% + {\expandafter\ekv@ifdefined\expandafter{\ekv@name{#1}{#2}N}} +% \end{macrocode} +% \end{macro} +% +% \begin{macro} +% { +% \ekvdef,\ekvdefNoVal,\ekvlet,\ekvletNoVal,\ekvletkv,\ekvletkvNoVal, +% \ekvdefunknown,\ekvdefunknownNoVal +% } +% Set up the key defining macros |\ekvdef| etc. We use temporary macros to set +% these up with a few expansions already done. +% \begin{macrocode} +\def\ekvdef#1#2#3#4% + {% + \protected\long\def\ekvdef##1##2##3% + {#1{\expandafter\def\csname#2\endcsname####1{##3}#3}}% + \protected\long\def\ekvdefNoVal##1##2##3% + {#1{\expandafter\def\csname#2N\endcsname{##3}#3}}% + \protected\long\def\ekvlet##1##2##3% + {#1{\expandafter\let\csname#2\endcsname##3#3}}% + \protected\long\def\ekvletNoVal##1##2##3% + {#1{\expandafter\let\csname#2N\endcsname##3#3}}% + \ekv@expargtwice{\protected\long\def\ekvdefunknown##1##2}% + {% + \romannumeral + \expandafter\ekv@exparg@\expandafter + {% + \expandafter\expandafter\expandafter + \def\expandafter\csname\ekv@name{##1}{}u\endcsname####1####2{##2}% + #3% + }% + {\ekv@zero\ekv@checkvalid{##1}.}% + }% + \ekv@expargtwice{\protected\long\def\ekvdefunknownNoVal##1##2}% + {% + \romannumeral + \expandafter\ekv@exparg@\expandafter + {% + \expandafter\expandafter\expandafter + \def\expandafter\csname\ekv@name{##1}{}uN\endcsname####1{##2}% + #3% + }% + {\ekv@zero\ekv@checkvalid{##1}.}% + }% + \protected\long\def\ekvletkv##1##2##3##4% + {% + #1% + {% + \expandafter\let\csname#2\expandafter\endcsname + \csname#4\endcsname + #3% + }% + }% + \protected\long\def\ekvletkvNoVal##1##2##3##4% + {% + #1% + {% + \expandafter\let\csname#2N\expandafter\endcsname + \csname#4N\endcsname + #3% + }% + }% + } +\edef\ekvdefNoVal + {% + {\ekv@unexpanded\expandafter{\ekv@checkvalid{#1}{#2}}}% + {\ekv@unexpanded\expandafter{\ekv@name{#1}{#2}}}% + {% + \ekv@unexpanded{\expandafter\ekv@defsetmacro\csname}% + \ekv@unexpanded\expandafter{\ekv@undefined@set{#1}\endcsname{#1}}% + }% + {\ekv@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 + {% + \ekvcsvloop#1{#6}% + \ekv@unexpanded{#2}% + {\ekvcsvloop{}{#5,#6}}% + }% + \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{\ekv@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 +% sequence. +% \begin{macrocode} +\edef\ekv@defsetmacro + {% + \ekv@unexpanded{\ifx#1\relax\edef#1##1}% + {% + \ekv@unexpanded\expandafter{\ekv@name@set{#2}}% + \ekv@unexpanded\expandafter{\ekv@name@key{##1}}% + }% + \ekv@unexpanded{\fi}% + } +\ekv@exparg{\protected\def\ekv@defsetmacro#1#2}{\ekv@defsetmacro} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ekvifdefinedset} +% \begin{macrocode} +\ekv@expargtwice{\def\ekvifdefinedset#1}% + {\expandafter\ekv@ifdefined\expandafter{\ekv@undefined@set{#1}}} +% \end{macrocode} +% \end{macro} +% +% \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 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} +\begingroup +\def\ekvset#1#2{% +\endgroup +\ekv@exparg{\long\def\ekvset##1##2}% + {% + \expandafter\expandafter\expandafter + \ekv@set\expandafter\csname\ekv@undefined@set{##1}\endcsname + \ekv@mark##2#1\ekv@stop#1{}% + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@set} +% |\ekv@set| will split the \kv\ list at active commas. Then it has to check +% whether there were unprotected other commas and resplit there. +% \begin{macrocode} +\long\def\ekv@set##1##2#1% + {% +% \end{macrocode} +% Test whether we're at the end, if so invoke |\ekv@endset|, +% \begin{macrocode} + \ekv@gobble@from@mark@to@stop##2\ekv@endset\ekv@stop +% \end{macrocode} +% else go on with other commas. +% \begin{macrocode} + \ekv@set@other##1##2,\ekv@stop,% + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@endset} +% |\ekv@endset| is a hungry little macro. It will eat everything that remains +% of |\ekv@set| and unbrace the sneaked stuff. +% \begin{macrocode} +\long\def\ekv@endset + \ekv@stop\ekv@set@other##1\ekv@mark\ekv@stop,\ekv@stop,##2% + {##2} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@eq@other,\ekv@eq@active} +% Splitting at equal signs will be done in a way that checks whether there is +% an equal sign and splits at the same time. This gets quite messy and the +% code might look complicated, but this is pretty fast (faster than first +% checking for an equal sign and splitting if one is found). The splitting +% code will be adapted for |\ekvset| and |\ekvparse| to get the most speed, +% but some of these macros don't require such adaptions. |\ekv@eq@other| and +% |\ekv@eq@active| will split the argument at the first equal sign and insert +% the macro which comes after the first following |\ekv@mark|. This allows for +% fast branching based on \TeX's argument grabbing rules and we don't have to +% split after the branching if the equal sign was there. +% \begin{macrocode} +\long\def\ekv@eq@other##1=##2\ekv@mark##3{##3##1\ekv@stop\ekv@mark##2} +\long\def\ekv@eq@active##1#2##2\ekv@mark##3{##3##1\ekv@stop\ekv@mark##2} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@set@other} +% The macro |\ekv@set@other| is guaranteed to get only single \kv\ pairs. +% \begin{macrocode} +\long\def\ekv@set@other##1##2,% + {% +% \end{macrocode} +% First we test whether we're done. +% \begin{macrocode} + \ekv@gobble@from@mark@to@stop##2\ekv@endset@other\ekv@stop +% \end{macrocode} +% If not we split at the equal sign of category other. +% \begin{macrocode} + \ekv@eq@other##2\ekv@nil\ekv@mark\ekv@set@eq@other@a + =\ekv@mark\ekv@set@eq@active +% \end{macrocode} +% And insert the set name for the next recursion step of |\ekv@set@other|. +% \begin{macrocode} + ##1% + \ekv@mark + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@set@eq@other@a, \ekv@set@eq@other@b} +% The first of these two macros runs the split-test for equal signs of +% category active. It will only be inserted if the \kv\ pair contained at +% least one equal sign of category other and |##1| will contain everything up +% to that equal sign. +% \begin{macrocode} +\long\def\ekv@set@eq@other@a##1\ekv@stop + {% + \ekv@eq@active##1\ekv@nil\ekv@mark\ekv@set@eq@other@active + #2\ekv@mark\ekv@set@eq@other@b + } +% \end{macrocode} +% The second macro will have been called by |\ekv@eq@active| if no active +% equal sign was found. All it does is remove the excess tokens of that +% test and forward the \kv\ pair to |\ekv@set@pair|. Normally we would have to +% also gobble an additional |\ekv@mark| after |\ekv@stop|, but this mark is +% needed to delimit |\ekv@set@pair|'s argument anyway, so we just leave it +% there. +% \begin{macrocode} +\ekv@exparg + {% + \long\def\ekv@set@eq@other@b + ##1\ekv@nil\ekv@mark\ekv@set@eq@other@active\ekv@stop\ekv@mark + ##2\ekv@nil=\ekv@mark\ekv@set@eq@active + }% + {\ekv@strip{##1}{\expandafter\ekv@set@pair\detokenize}\ekv@mark##2\ekv@nil} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@set@eq@other@active} +% |\ekv@set@eq@other@active| will be called if the \kv\ pair was wrongly +% split on an equal sign of category other but has an earlier equal sign of +% category active. |##1| will be the contents up to the active equal sign and +% |##2| everything that remains until the first found other equal sign. It has +% to reinsert the equal sign and forward things to |\ekv@set@pair|. +% \begin{macrocode} +\ekv@exparg + {% + \long\def\ekv@set@eq@other@active + ##1\ekv@stop##2\ekv@nil#2\ekv@mark + \ekv@set@eq@other@b\ekv@mark##3=\ekv@mark\ekv@set@eq@active + }% + {\ekv@strip{##1}{\expandafter\ekv@set@pair\detokenize}\ekv@mark##2=##3} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@set@eq@active, \ekv@set@eq@active@} +% |\ekv@set@eq@active| will be called when there was no equal sign of category +% other in the \kv\ pair. It removes the excess tokens of the prior test and +% split-checks for an active equal sign. +% \begin{macrocode} +\long\def\ekv@set@eq@active + ##1\ekv@nil\ekv@mark\ekv@set@eq@other@a\ekv@stop\ekv@mark + {% + \ekv@eq@active##1\ekv@nil\ekv@mark\ekv@set@eq@active@ + #2\ekv@mark\ekv@set@noeq + } +% \end{macrocode} +% If an active equal sign was found in |\ekv@set@eq@active| we'll have to pass +% the now split \kv\ pair on to |\ekv@set@pair|. +% \begin{macrocode} +\ekv@exparg + {\long\def\ekv@set@eq@active@##1\ekv@stop##2\ekv@nil#2\ekv@mark\ekv@set@noeq}% + {\ekv@strip{##1}{\expandafter\ekv@set@pair\detokenize}\ekv@mark##2\ekv@nil} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@set@noeq} +% If no active equal sign was found by |\ekv@set@eq@active| there is no equal +% sign contained in the parsed list entry. In that case we have to check +% whether the entry is blank in order to ignore it (in which case we'll have +% to gobble the set-name which was put after these tests by |\ekv@set@other|). +% Else this is a |NoVal| key and the entry is passed on to |\ekv@set@key|. +% \begin{macrocode} +\edef\ekv@set@noeq + {% + \ekv@unexpanded + {% + \ekv@ifblank@##1\ekv@nil\ekv@ifempty@B\ekv@set@was@blank + \ekv@ifempty@A\ekv@ifempty@B + }% + \ekv@unexpanded\expandafter + {\ekv@strip{##1}{\expandafter\ekv@set@key\detokenize}\ekv@mark}% + } +\ekv@exparg + {% + \long\def\ekv@set@noeq + ##1\ekv@nil\ekv@mark\ekv@set@eq@active@\ekv@stop\ekv@mark + }% + {\ekv@set@noeq} +\expandafter\def\expandafter\ekv@set@was@blank + \expandafter\ekv@ifempty@A\expandafter\ekv@ifempty@B + \ekv@strip{\ekv@mark##1}##2\ekv@mark + {\ekv@set@other} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@endset@other} +% All that's left for |\ekv@set@other| is the macro which breaks the recursion +% loop at the end. This is done by gobbling all the remaining tokens. +% \begin{macrocode} +\long\def\ekv@endset@other + \ekv@stop + \ekv@eq@other\ekv@mark\ekv@stop\ekv@nil\ekv@mark\ekv@set@eq@other@a + =\ekv@mark\ekv@set@eq@active + {\ekv@set} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ekvbreak,\ekvbreakPreSneak,\ekvbreakPostSneak} +% Provide macros that can completely stop the parsing of |\ekvset|, who knows +% what it'll be useful for. +% \begin{macrocode} +\long\def\ekvbreak##1##2\ekv@stop#1##3{##1} +\long\def\ekvbreakPreSneak ##1##2\ekv@stop#1##3{##1##3} +\long\def\ekvbreakPostSneak##1##2\ekv@stop#1##3{##3##1} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ekvsneak,\ekvsneakPre} +% One last thing we want to do for |\ekvset| is to provide macros that just +% smuggle stuff after |\ekvset|'s effects. +% \begin{macrocode} +\long\def\ekvsneak##1##2\ekv@stop#1##3{##2\ekv@stop#1{##3##1}} +\long\def\ekvsneakPre##1##2\ekv@stop#1##3{##2\ekv@stop#1{##1##3}} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ekvparse} +% Additionally to the |\ekvset| macro we also want to provide an |\ekvparse| +% macro, that has the same scope as |\keyval_parse:NNn| from \pkg{expl3}. +% This is pretty analogue to the |\ekvset| implementation, we just put an +% |\unexpanded| here and there instead of other macros to stop the |\expanded| +% on our output. The |\unexpanded\expanded{{...}}| ensures that the material +% is in an alignment safe group at all time, and that it doesn't expand any +% further in an |\edef| or |\expanded| context. +% \begin{macrocode} +\long\def\ekvparse##1##2##3% + {% + \ekv@unexpanded\ekv@expanded + {{\ekv@parse{##1}{##2}\ekv@mark##3#1\ekv@stop#1}}% + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@parse} +% \begin{macrocode} +\long\def\ekv@parse##1##2##3#1% + {% + \ekv@gobble@from@mark@to@stop##3\ekv@endparse\ekv@stop + \ekv@parse@other{##1}{##2}##3,\ekv@stop,% + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@endparse} +% \begin{macrocode} +\long\def\ekv@endparse + \ekv@stop\ekv@parse@other##1\ekv@mark\ekv@stop,\ekv@stop,% + {} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@parse@other} +% \begin{macrocode} +\long\def\ekv@parse@other##1##2##3,% + {% + \ekv@gobble@from@mark@to@stop##3\ekv@endparse@other\ekv@stop + \ekv@eq@other##3\ekv@nil\ekv@mark\ekv@parse@eq@other@a + =\ekv@mark\ekv@parse@eq@active + {##1}{##2}% + \ekv@mark + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@parse@eq@other@a,\ekv@parse@eq@other@b} +% \begin{macrocode} +\long\def\ekv@parse@eq@other@a##1\ekv@stop + {% + \ekv@eq@active##1\ekv@nil\ekv@mark\ekv@parse@eq@other@active + #2\ekv@mark\ekv@parse@eq@other@b + } +\ekv@exparg + {% + \long\def\ekv@parse@eq@other@b + ##1\ekv@nil\ekv@mark\ekv@parse@eq@other@active\ekv@stop\ekv@mark + ##2\ekv@nil=\ekv@mark\ekv@parse@eq@active + }% + {\ekv@strip{##1}\ekv@parse@pair##2\ekv@nil} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@parse@eq@other@active} +% \begin{macrocode} +\ekv@exparg + {% + \long\def\ekv@parse@eq@other@active + ##1\ekv@stop##2\ekv@nil#2\ekv@mark + \ekv@parse@eq@other@b\ekv@mark##3=\ekv@mark\ekv@parse@eq@active + }% + {\ekv@strip{##1}\ekv@parse@pair##2=##3} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@parse@eq@active,\ekv@parse@eq@active@} +% \begin{macrocode} +\long\def\ekv@parse@eq@active + ##1\ekv@nil\ekv@mark\ekv@parse@eq@other@a\ekv@stop\ekv@mark + {% + \ekv@eq@active##1\ekv@nil\ekv@mark\ekv@parse@eq@active@ + #2\ekv@mark\ekv@parse@noeq + } +\ekv@exparg + {\long\def\ekv@parse@eq@active@##1\ekv@stop##2#2\ekv@mark\ekv@parse@noeq}% + {\ekv@strip{##1}\ekv@parse@pair##2} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@parse@noeq} +% \begin{macrocode} +\edef\ekv@parse@noeq + {% + \ekv@unexpanded + {% + \ekv@ifblank@##1\ekv@nil\ekv@ifempty@B\ekv@parse@was@blank + \ekv@ifempty@A\ekv@ifempty@B + }% + \ekv@unexpanded\expandafter{\ekv@strip{##1}\ekv@parse@key}% + } +\ekv@exparg + {% + \long\def\ekv@parse@noeq + ##1\ekv@nil\ekv@mark\ekv@parse@eq@active@\ekv@stop\ekv@mark + }% + {\ekv@parse@noeq} +\expandafter\def\expandafter\ekv@parse@was@blank + \expandafter\ekv@ifempty@A\expandafter\ekv@ifempty@B + \ekv@strip{\ekv@mark##1}\ekv@parse@key + {\ekv@parse@other} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@endparse@other} +% \begin{macrocode} +\long\def\ekv@endparse@other + \ekv@stop + \ekv@eq@other\ekv@mark\ekv@stop\ekv@nil\ekv@mark\ekv@parse@eq@other@a + =\ekv@mark\ekv@parse@eq@active + {\ekv@parse} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@parse@pair,\ekv@parse@pair@} +% \begin{macrocode} +\ekv@exparg{\long\def\ekv@parse@pair##1##2\ekv@nil}% + {\ekv@strip{##2}\ekv@parse@pair@{##1}} +\long\def\ekv@parse@pair@##1##2##3##4% + {% + \ekv@unexpanded{##4{##2}{##1}}% + \ekv@parse@other{##3}{##4}% + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@parse@key} +% \begin{macrocode} +\long\def\ekv@parse@key##1##2% + {% + \ekv@unexpanded{##2{##1}}% + \ekv@parse@other{##2}% + } +% \end{macrocode} +% \end{macro} +% +% Finally really setting things up with |\ekvset|'s temporary meaning: +% \begin{macrocode} +} +\catcode`\,=13 +\catcode`\==13 +\ekvset,= +% \end{macrocode} +% +% \begin{macro}{\ekvsetSneaked} +% This macro can be defined just by expanding |\ekvsneak| once after expanding +% |\ekvset|. To expand everything as much as possible early on we use a +% temporary definition. +% \begin{macrocode} +\edef\ekvsetSneaked + {% + \ekv@unexpanded{\ekvsneak{#2}}% + \ekv@unexpanded\expandafter{\ekvset{#1}{#3}}% + } +\ekv@expargtwice{\long\def\ekvsetSneaked#1#2#3}{\ekvsetSneaked} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ekvchangeset} +% Provide a macro that is able to switch out the current \set\ in |\ekvset|. +% 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}% + {% + \expandafter\expandafter\expandafter + \ekv@changeset\expandafter\csname\ekv@undefined@set{#1}\endcsname\ekv@empty + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@changeset} +% This macro does the real change-out of |\ekvchangeset|. |#2| will have a +% leading |\ekv@empty| so that braces aren't stripped accidentally, but that +% will not hurt and just expand to nothing in one step. +% \begin{macrocode} +\long\def\ekv@changeset#1#2\ekv@set@other#3{#2\ekv@set@other#1} +% \end{macrocode} +% \end{macro} +% +% +% \begin{macro}[internal] +% { +% \ekv@set@pair,\ekv@set@pair@a,\ekv@set@pair@b,\ekv@set@pair@c, +% \ekv@set@pair@d,\ekv@set@pair@e +% } +% |\ekv@set@pair| gets invoked with the space and brace stripped and +% |\detokenize|d key-name as its first, the value as the second, and the set +% name as the third argument. It provides tests for the key-macros and +% everything to be able to throw meaningful error messages if it isn't +% defined. We have two routes here, one if |\lastnamedcs| is defined and one +% if it isn't. The big difference is that if it is we can omit a |\csname| and +% instead just expand |\lastnamedcs| once to get the control sequence. +% If the macro is defined the value will be space and brace stripped and the +% key-macro called. Else branch into the error handling provided by +% |\ekv@set@pair|. +% \begin{macrocode} +\ekv@if@lastnamedcs + {% + \long\def\ekv@set@pair#1\ekv@mark#2\ekv@nil#3% + {% + \ifcsname #3{#1}\endcsname\ekv@set@pair@a\fi\@secondoftwo + {#2}% + {% + \ifcsname #3{}u\endcsname\ekv@set@pair@a\fi\@secondoftwo + {#2}% + {% + \ekv@ifdefined{#3{#1}N}% + \ekv@err@noarg + \ekv@err@unknown + #3% + }% + {#1}% + }% + \ekv@set@other#3% + } + \def\ekv@set@pair@a\fi\@secondoftwo + {\fi\expandafter\ekv@set@pair@b\lastnamedcs} + } + {% + \long\def\ekv@set@pair#1\ekv@mark#2\ekv@nil#3% + {% + \ifcsname #3{#1}\endcsname + \ekv@set@pair@a\fi\ekv@set@pair@c#3{#1}\endcsname + {#2}% + {% + \ifcsname #3{}u\endcsname + \ekv@set@pair@a\fi\ekv@set@pair@c#3{}u\endcsname + {#2}% + {% + \ekv@ifdefined{#3{#1}N}% + \ekv@err@noarg + \ekv@err@unknown + #3% + }% + {#1}% + }% + \ekv@set@other#3% + } + \def\ekv@set@pair@a\fi\ekv@set@pair@c{\fi\expandafter\ekv@set@pair@b\csname} + \long\def\ekv@set@pair@c#1\endcsname#2#3{#3} + } +\long\def\ekv@set@pair@b#1% + {% + \ifx#1\relax + \ekv@set@pair@e + \fi + \ekv@set@pair@d#1% + } +\ekv@exparg{\long\def\ekv@set@pair@d#1#2#3}{\ekv@strip{#2}#1} +\long\def\ekv@set@pair@e\fi\ekv@set@pair@d#1#2#3{\fi#3} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal] +% {\ekv@set@key,\ekv@set@key@a,\ekv@set@key@b,\ekv@set@key@c} +% Analogous to |\ekv@set@pair|, |\ekv@set@key| builds the |NoVal| key-macro and +% provides an error-branch. |\ekv@set@key@| will test whether the key-macro is +% defined and if so call it, else the errors are thrown. +% \begin{macrocode} +\ekv@if@lastnamedcs + {% + \long\def\ekv@set@key#1\ekv@mark#2% + {% + \ifcsname #2{#1}N\endcsname\ekv@set@key@a\fi\@firstofone + {% + \ifcsname #2{}uN\endcsname\ekv@set@key@a\fi\@firstofone + {% + \ekv@ifdefined{#2{#1}}% + \ekv@err@reqval + \ekv@err@unknown + #2% + }% + {#1}% + }% + \ekv@set@other#2% + } + \def\ekv@set@key@a\fi\@firstofone{\fi\expandafter\ekv@set@key@b\lastnamedcs} + } + {% + \long\def\ekv@set@key#1\ekv@mark#2% + {% + \ifcsname #2{#1}N\endcsname + \ekv@set@key@a\fi\ekv@set@key@c#2{#1}N\endcsname + {% + \ifcsname #2{}uN\endcsname + \ekv@set@key@a\fi\ekv@set@key@c#2{}uN\endcsname + {% + \ekv@ifdefined{#2{#1}}% + \ekv@err@reqval + \ekv@err@unknown + #2% + }% + {#1}% + }% + \ekv@set@other#2% + } + \def\ekv@set@key@a\fi\ekv@set@key@c{\fi\expandafter\ekv@set@key@b\csname} + \long\def\ekv@set@key@c#1N\endcsname#2{#2} + } +\long\def\ekv@set@key@b#1% + {% + \ifx#1\relax + \ekv@fi@secondoftwo + \fi + \@firstoftwo#1% + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ekvsetdef} +% Provide a macro to define a shorthand to use |\ekvset| on a specified \set. +% To gain the maximum speed |\ekvset| is expanded twice by +% |\ekv@exparg| so that during runtime the macro storing the set name +% is already built and one |\expandafter| doesn't have to be used. +% \begin{macrocode} +\ekv@expargtwice{\protected\def\ekvsetdef#1#2}% + {% + \romannumeral + \ekv@exparg{\ekv@zero\ekv@exparg{\long\def#1##1}}% + {\ekvset{#2}{##1}}% + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ekvsetSneakeddef,\ekvsetdefSneaked} +% And do the same for |\ekvsetSneaked| in the two possible ways, with a fixed +% sneaked argument and with a flexible one. +% \begin{macrocode} +\ekv@expargtwice{\protected\def\ekvsetSneakeddef#1#2}% + {% + \romannumeral + \ekv@exparg{\ekv@zero\ekv@exparg{\long\def#1##1##2}}% + {\ekvsetSneaked{#2}{##1}{##2}}% + } +\ekv@expargtwice{\protected\def\ekvsetdefSneaked#1#2#3}% + {% + \romannumeral + \ekv@exparg{\ekv@zero\ekv@exparg{\long\def#1##1}}% + {\ekvsetSneaked{#2}{#3}{##1}}% + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@alignsafe,\ekv@endalignsafe} +% These macros protect the usage of ampersands inside of alignment contexts. +% \begin{macrocode} +\begingroup +\catcode`\^^@=2 +\@firstofone{\endgroup + \def\ekv@alignsafe{\romannumeral\iffalse{\fi`^^@ } +} +\def\ekv@endalignsafe{\ifnum`{=\ekv@zero}\fi} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ekvoptarg,\ekvoptargTF} +% Provide macros to expandably collect an optional argument in brackets. The +% macros here are pretty simple in nature compared to \pkg{xparse}'s +% possibilities (they don't care for nested bracket levels). +% +% We start with a temporary definition to pre-expand |\ekv@alignsafe| (will be +% |#1|) and |\ekv@endalignsafe| (will be |#2|). As |\ekv@alignsafe| starts with +% a |\romannumeral| we use that to also control the number of steps needed +% instead of adding another |\romannumeral|. For this we have to remove the +% space token from the end of |\ekv@alignsafe|'s definition. +% \begin{macrocode} +\begingroup +\def\ekvoptarg#1#2{% +\endgroup +% \end{macrocode} +% The real definition starts an expansion context and afterwards grabs the +% arguments. |#1| will be the next step, |#2| the default value, and |#3| might +% be an opening bracket, or the mandatory argument. We check for the opening +% bracket, if it is found grab the optional argument, else leave |#1{#2}| in the +% input stream after ending the expansion context. +% \begin{macrocode} +\def\ekvoptarg{#1\ekv@optarg@a} +\long\def\ekv@optarg@a##1##2##3% + {% + \ekv@optarg@if\ekv@mark##3\ekv@mark\ekv@optarg@b\ekv@mark[\ekv@mark + #2% + \@firstofone{ ##1}{##2}{##3}% + }% +% \end{macrocode} +% The other variant of this will do roughly the same. Here, |#1| will be the +% next step if an optional argument is found, |#2| the next step else, and |#3| +% might be the opening bracket or mandatory argument. +% \begin{macrocode} +\def\ekvoptargTF{#1\ekv@optargTF@a} +\long\def\ekv@optargTF@a##1##2##3% + {% + \ekv@optarg@if\ekv@mark##3\ekv@mark\ekv@optargTF@b{##1}\ekv@mark[\ekv@mark + #2% + \@firstofone{ ##2}{##3}% + } +% \end{macrocode} +% The two macros to grab the optional argument have to remove the remainder of +% the test and the wrong next step as well as grabbing the argument. +% \begin{macrocode} +\long\def\ekv@optarg@b\ekv@mark[\ekv@mark\ifnum`##1\fi\@firstofone##2##3##4##5]% + {#2##2{##5}} +\long\def\ekv@optargTF@b + ##1\ekv@mark[\ekv@mark\ifnum`##2\fi\@firstofone##3##4##5]% + {#2 ##1{##5}} +} +% \end{macrocode} +% Do the definitions and add the test macro. We use |\ekv@strip| to remove the +% trailing space from the definition of |\ekv@alignsafe|. +% \begin{macrocode} +\ekv@exparg + {% + \expandafter\ekv@strip\expandafter + {\expandafter\ekv@mark\ekv@alignsafe}% + \ekvoptarg + }% + \ekv@endalignsafe +\long\def\ekv@optarg@if#1\ekv@mark[\ekv@mark{} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ekverr} +% \begin{macro}[internal]{\ekv@err@collect,\ekv@err@cleanup} +% 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 +% a runaway argument (to my knowledge the first version of this method was +% implemented by Jean-François Burnol, many thanks to him). The first step is to +% ensure that the second argument (which might contain user input) doesn't +% contain tokens we use as delimiters (in this case |\par|), this will be done +% by the front facing macro |\ekverr|. But first we set some other things up. +% +% We use a temporary definition for |\ekverr| to get multiple consecutive +% spaces. Then we set up the macro that will collect the error and the macro +% that will throw the error. The latter will have an unreasonable long name. +% This way we can convey more information. Though the information in the macro +% name is static and has to be somewhat general to fit every occurence. The +% important bit is that the long named macro has a delimited argument and is +% short which will throw the error at the |\par| at the end of +% |\ekv@err@collect|. This macro has the drawback that it will only print nicely +% if the |\newlinechar| is |^^J|. +% \begin{macrocode} +\def\ekv@err@cleanup\par{} +\def\ekv@err@collect#1% + {% + \def\ekv@err@collect##1\par##2% + {% + \expandafter + \ekv@err@cleanup + #1! ##2 Error: ##1\par + }% + \def#1##1\thanks@jfbu{}% + } +\def\ekverr{ } +\expandafter\ekv@err@collect\csname <an-expandable-macro>^^J% + completed due to above exception. \ekverr If the error^^J% + summary is \ekverr not comprehensible \ekverr see the package^^J% + documentation.^^J% + I will try to recover now. \ekverr If you're in inter-^^J% + active mode hit <return> \ekverr at the ? prompt and I^^J% + continue hoping recovery\endcsname +% \end{macrocode} +% \begin{macrocode} +\long\def\ekverr#1#2{\expandafter\ekv@err@collect\detokenize{#2}\par{#1}} +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}[internal]{\ekv@err} +% We define a shorthand to throw errors in \expkv. +% \begin{macrocode} +\ekv@exparg{\long\def\ekv@err#1}{\ekverr{expkv}{#1}} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}[internal] +% { +% \ekv@err@common,\ekv@err@common@, +% \ekv@err@unknown,\ekv@err@noarg,\ekv@err@reqval +% } +% Now we can use |\ekv@err| to set up some error messages so that we can later +% use those instead of the full strings. +% \begin{macrocode} +\long\def\ekv@err@common #1#2{\expandafter\ekv@err@common@\string#2{#1}} +\ekv@exparg{\long\def\ekv@err@common@#1`#2' #3.#4#5}% + {\ekv@err{#4 `#5' in 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{unwanted value for}{#1}} +\ekv@exparg{\long\def\ekv@err@reqval #1}{\ekv@err@common{missing value for}{#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} +% +% Now everything that's left is to reset the category code of |@|. +% \begin{macrocode} +\catcode`\@=\ekv@tmp +% \end{macrocode} +% +% \gobbledocstriptag +%</tex> +%^^A=<< +% +%^^A=<< +% +% \end{implementation}^^A=<< +% +% \clearpage +% \PrintIndex +% +\endinput +% +^^A vim: ft=tex fdm=marker fmr=>>=,=<< |