From f7e5d81fe9880913485f7f8c541eeca7adf4c195 Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Thu, 23 Jan 2020 03:01:31 +0000 Subject: CTAN sync 202001230301 --- macros/generic/expkv/README.md | 31 + macros/generic/expkv/expkv.dtx | 1451 ++++++++++++++++++++++++++++++++++++++++ macros/generic/expkv/expkv.pdf | Bin 0 -> 333595 bytes 3 files changed, 1482 insertions(+) create mode 100644 macros/generic/expkv/README.md create mode 100644 macros/generic/expkv/expkv.dtx create mode 100644 macros/generic/expkv/expkv.pdf (limited to 'macros/generic') diff --git a/macros/generic/expkv/README.md b/macros/generic/expkv/README.md new file mode 100644 index 0000000000..4401986b55 --- /dev/null +++ b/macros/generic/expkv/README.md @@ -0,0 +1,31 @@ +------------------------------------------------------------------------------- +# expkv -- an expandable key=val implementation + +Version 2020-01-22 v0.3 + +Released under the LaTeX Project Public License v1.3c or later +See http://www.latex-project.org/lppl.txt + +Hosted at https://github.com/Skillmon/tex_expkv + +------------------------------------------------------------------------------- + +Copyright (C) 2020 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 provides an expandable key=val implementation that is somewhat fast, but +not the fastest key=val implementation available. It is generic code and +completely self-contained. There is a LaTeX package `expkv.sty` included to play +nice on LaTeX's package loading system, but that package is not needed and does +not provide more functionality than the generic code in `expkv.tex`. diff --git a/macros/generic/expkv/expkv.dtx b/macros/generic/expkv/expkv.dtx new file mode 100644 index 0000000000..58f8c192db --- /dev/null +++ b/macros/generic/expkv/expkv.dtx @@ -0,0 +1,1451 @@ +% \iffalse meta-comment +% +% File: expkv.dtx Copyright (C) 2020 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 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 + +\endpreamble +% stop docstrip adding \endinput +\postamble +\endpostamble +\generate{\file{expkv.sty}{\from{expkv.dtx}{pkg}}} +\generate{\file{expkv.tex}{\from{expkv.dtx}{tex}}} +\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{siunitx} +\RequirePackage{xcolor} +\RequirePackage{caption} +\RequirePackage{microtype} +\RequirePackage{accsupp} +\lstset + { + ,flexiblecolumns=false + ,basewidth=.53em + ,gobble=2 + ,basicstyle=\fontfamily{jkp}\itshape + ,morekeywords=^^A + {^^A + \ekvdef,\ekvdefNoVal,\ekvlet,\ekvletNoVal,\ekvletkv,\ekvletkvNoVal,^^A + \ekvset,\ekvparse,\ekvVersion,\ekvDate,\ekvifdefined,^^A + \ekvifdefinedNoVal,\ekvbreak,\ekvbreakPreSneak,\ekvbreakPostSneak,^^A + \ekvsneak,\ekvsneakPre + } + ,morecomment=[l]\% + ,commentstyle=\color[gray]{0.4} + ,literate={\{}{{\CodeSymbol\{}}{1} + {\}}{{\CodeSymbol\}}}{1} + ^^A,literate=*{}{\key}{4}{}{\set}{4} + } +\newcommand*\CodeSymbol[1]{\textbf{#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*\expkv + {^^A + \texorpdfstring + {^^A + \mbox + {^^A + \BeginAccSupp{ActualText=expkv}^^A + \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 + \EndAccSupp{}^^A + }^^A + } + {expkv}^^A + } +\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{\def,\edef,\,,\=,\begingroup,\catcode,\chardef,\csname,\endcsname} +\DoNotIndex{\endgroup,\endinput,\errmessage,\expandafter,\input,\let,\long} +\DoNotIndex{\noexpand,\protected,\ProvidesFile,\ProvidesPackage,\relax,\space} +\DoNotIndex{\@,\unexpanded,\string,\expanded} +\DoNotIndex{\ifcsname} +\DoNotIndex{\ifx} +\DoNotIndex{\else} +\DoNotIndex{\fi} +\@gobble\fi ^^A ignoring \ifx and \ifcsname, 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 + \href{https://github.com/Skillmon/tex_expkv} + {\expkv}^^A + \\[\medskipamount] + \Large an expandable \kv\ implementation^^A + }{expkv - an expandable = implementation}^^A + } + \date{\ekvDate\space v\ekvVersion} + \author{Jonathan P. Spratte\thanks{\protect\randomize{jspratte@yahoo.de}}} + \DocInput{expkv.dtx} +\end{document} +%^^A=<< +% \fi +% +% \maketitle +% +% \begin{abstract} +% \noindent\parfillskip=0pt +% \expkv\ provides a small interface for \kv\ parsing. The parsing macro is +% fully expandable, the \meta{code} of your keys might be not. \expkv\ is pretty +% fast, but not the fastest available \kv\ solution (\pkg{keyval} for instance +% is thrice as fast, but not expandable and it might strip braces it shouldn't +% have stripped). +% \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 or as a \LaTeX\ package. To use it, just +% use one of: +% \begin{lstlisting} +% \usepackage{expkv} % LaTeX +% \input expkv % plainTeX +% \end{lstlisting} +% The \LaTeX\ package doesn't do more than \file{expkv.tex}, except calling +% |\ProvidesPackage| and setting things up such that \file{expkv.tex} will use +% |\ProvidesFile|. +% +% \subsection{Setting up Keys}\label{sec:define} +% +% 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|, +% prefixes allowed for |\let| can prefix those with ``|let|'' in their name, +% accordingly. Neither \set\ nor \key\ are allowed to be empty for new keys and +% must not contain a |\par| or tokens that expand to it -- they must be legal +% inside of |\csname ...\endcsname|. +% +% \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} +% +% \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} +% +% \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. +% \end{function} +% +% \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} +% +% \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. +% \end{function} +% +% \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. +% \end{function} +% +% \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. +% \end{function} +% +% \begin{function}{\ekvparse} +% \begin{syntax} +% \cs{ekvparse}\meta{cs1}\meta{cs2}\{\kv,\ldots\} +% \end{syntax} +% This macro parses the \kv\ pairs and provides those list elements which are +% only keys as the argument to \meta{cs1}, and those which are a \kv\ pair to +% \meta{cs2} as two arguments. It expands in exactly two steps of expansion. +% |\ekvbreak| and |\ekvsneak| and their relatives don't work in |\ekvparse|. +% It is analogue to \pkg{expl3}'s |\keyval_parse:NNn|. +% +% As a small example: +% \begin{lstlisting} +% \ekvparse\handlekey\handlekeyval{foo = bar, key, baz={zzz}} +% \end{lstlisting} +% would expand in exactly two steps to +% \begin{lstlisting} +% \handlekeyval{foo}{bar}\handlekey{key}\handlekeyval{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). +% \end{function} +% +% \subsection{Miscellaneous} +% +% \subsubsection{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} +% +% \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} +% +% \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. A small usage example is shown in +% \autoref{sec:sneakex}. +% \end{function} +% +% \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 default definition of |\ekv@name@set| is +% ``\texttt{\csname ekv@name@set\endcsname{\set}}'' and the default of +% |\ekv@name@key| is ``\texttt{\csname ekv@name@key\endcsname{\key}}''. +% The complete name is build using |\ekv@name| which is equivalent to +% \texttt{\cs[no-index]{ekv@name@set}\marg{set}\cs{ekv@name@key}\marg{key}}. +% For |NoVal| keys an additional |N| gets appended irrespective of these +% macros' definition, so their name is +% \cs[no-index]{\csname ekv@name\endcsname{\set}{\key}N}. You might redefine +% |\ekv@name@set| and |\ekv@name@key| locally but \emph{don't redefine} +% |\ekv@name|! +% \end{function} +% +% \subsubsection{Bugs} +% +% Just like \pkg{keyval}, \expkv\ is bug free. But if you find \pmso{bugs}hidden +% features\footnote{Thanks, David!} you can tell me about them either via mail +% (see the first page) or directly on GitHub if you have an account there: +% \url{https://github.com/Skillmon/tex_expkv} +% +% \subsubsection{Comparisons} +% +% Comparisons of speed are done with a very simple test key and the help of the +% \pkg{l3benchmark} package. The key and its usage should be equivalent to +% \begin{lstlisting} +% \protected\ekvdef{test}{height}{\def\myheight{#1}} +% \ekvset{test}{ height = 6 } +% \end{lstlisting} +% and only the usage of the key, not its definition, is benchmarked. +% +% 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. For the impatient, the essence of these comparisons +% regarding speed and buggy behaviour is contained in \autoref{tab:comp}. +% +% In this subsubsection is no benchmark of |\ekvparse| and |\keyval_parse:NNn| +% contained, as most other packages don't provide equivalent features to my +% knowledge. |\ekvparse| is slightly faster than |\ekvset|, but keep in mind +% that it does less. The same is true for |\keyval_parse:NNn| compared to +% |\keys_set:nn| of \pkg{expl3} (where the difference is much bigger). +% +% \paragraph{\pkg{keyval}} is between two and three times faster and has a +% comparable feature set just a slightly different way how it handles keys +% without values. That might be considered a drawback, as it limits the +% versatility, but also as an advantage, as it might reduce doubled code. Keep +% in mind that as soon as someone loads \pkg{xkeyval} the performance of +% \pkg{keyval} gets replaced by \pkg{xkeyval}'s. +% +% Also \pkg{keyval} has a bug, which unfortunately can't really be resolved +% without breaking backwards compatibility for \emph{many} documents, namely it +% strips braces from the argument before stripping spaces if the argument isn't +% surrounded by spaces, also it might strip more than one set of braces. Hence +% all of the following are equivalent in their outcome, though the last two +% lines should result in something different than the first two: +% \begin{lstlisting}[belowskip=0pt] +% \setkeys{foo}{bar=baz} +% \setkeys{foo}{bar= {baz}} +% \setkeys{foo}{bar={ baz}} +% \setkeys{foo}{bar={{baz}}} +% \end{lstlisting} +% +% \paragraph{\pkg{xkeyval}} is more than ten 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 over 200 times slower -- which is funny, because +% it aims to be ``[\ldots] faster than these earlier packages [referring to +% \pkg{keyval} and \pkg{xkeyval}].'' 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}). +% +% \paragraph{\pkg{l3keys}} is at least three 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 about one and a half times slower for one key, +% but has an \emph{enormous} feature set. However, since adding additional keys +% doesn't add as much needed time for \pkg{pgfkeys} compared to \expkv, it gets +% faster than \expkv\ at around eight \kv\ pairs. +% 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~$8$. Also I +% don't know whether this might introduce new bugs. +% +% \paragraph{\pkg{kvsetkeys} with \pkg{kvdefinekeys}} is about two times slower, +% but it works even if commas and equals have category codes different from 12. +% Else the features of the keys are equal to those of \pkg{keyval}, the parser +% has more features, though. +% +% \paragraph{\pkg{options}} is a bit faster than \expkv\ (almost +% \SI{10}{\percent}) and 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) and from what I can see, there is no +% direct way to define the equivalent test key. Nevertheless, I tested the +% closest possible equivalent of my test key while siding for \pkg{simplekv}'s +% design not forcing something into it it doesn't seem to be designed for. It is +% more than four times slower and has hard to predict behaviour regarding brace +% and space stripping, similar to \pkg{keyval}. The tested definition was: +% \begin{lstlisting}[belowskip=0pt] +% \usepackage{simplekv} +% \setKVdefault[simplekv]{height={ abc}} % key setup +% \setKV[simplekv]{ height = 6 } % benchmarked +% \end{lstlisting} +% +% \paragraph{\pkg{yax}} is about 13 times slower. It has a pretty strange +% syntax, imho, and again a direct equivalent is hard to define. It has the +% premature unbracing bug, too. Also somehow loading \pkg{yax} broke +% \pkg{options} for me. The tested definition was: +% \begin{lstlisting}[belowskip=0pt] +% \usepackage{yax} +% \defactiveparameter yax {\storevalue\myheight yax:height } % key setup +% \setparameterlist{yax}{ height = 6 } % benchmarked +% \end{lstlisting} +% +% \begin{table} +% \def\fnsym{\textcolor{red!80!black}{*}} +% \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 column ``Category Fragile'' lists +% whether the parsing breaks with active commas or equal signs.^^A +% \label{tab:comp}^^A +% } +% \begin{tabular}{l*2{S[table-format=4.3]}ccc} +% \toprule +% Package & {$p_1$} & {$p_0$} & Brace-Bug & Category Fragile & Date \\ +% \midrule +% \pkg{keyval} & 13.415 & 2.229 & \yes & \yes & 2014-10-28 \\ +% \pkg{options} & 25.254 & 12.160 & \yes & \yes & 2015-03-01 \\ +% \expkv & 31.711 & 5.124 & \no & \no & 2020-01-15 \\ +% \pkg{pgfkeys} & 26.215 & 43.141 & \yes & \yes & 2020-01-08 \\ +% \pkg{kvsetkeys} & {\fnsym} & {\fnsym} & \no & \no & 2019-12-15 \\ +% \pkg{l3keys} & 107.434 & 23.372 & \no & \no & 2020-01-12 \\ +% \pkg{simplekv} & 162.046 & 1.014 & \yes & \yes & 2017-08-08 \\ +% \pkg{xkeyval} & 258.505 & 168.761 & \yes & \yes & 2014-12-03 \\ +% \pkg{yax} & 510.662 & 37.961 & \yes & \yes & 2010-01-22 \\ +% \pkg{ltxkeys} & 3937.979 & 4512.835 & \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{9.653}$, $p_1=\num{40.896}$, and $p_0=\num{67.268}$. Of course the +% other packages might not really have linear run-time, but at least from 1~to +% 20~keys the fits don't seem too bad (the maximum ratio $p_2/p_1$ +% for the other packages is \num{8.987e-3}). If one extrapolates the fits +% for 100 \kv\ pairs one finds that most of them match pretty well, the +% exception being \pkg{ltxkeys}, which behaves quadratic as well with +% $p_2=\num{29.773}$, $p_1=\num{3312.744}$, and $p_0=\num{6805.363}$. +% \end{table} +% +% \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 initialization. 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 the 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}{6cm} +% \begin{lstlisting} +% \ourmacro{}\par +% \ourmacro{width}\par +% \ourmacro{width=5pt}\par +% \end{lstlisting} +% \end{minipage}^^A +% \begin{minipage}{\dimexpr\linewidth-6cm\relax} +% \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{minipage} +% +% \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 +% 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. +% +% \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[2] +% {\ekvset{expex}{#1,internal}\rnd{3}\deg{d}\f{sin}\sine@stop{#2}} +% \end{lstlisting} +% For the sake of simplicity we defined the macro |\sine| with two mandatory +% arguments, the first being the \kv\ list, the second the argument to the +% trigonometric function. We could've used \pkg{xparse}'s facilities here to +% define an expandable macro which takes an optional argument instead. +% +% 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}{.7\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}\hfill +% \begin{minipage}{.2\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[2] +% {\ekvset{expex}{#1,internal}\rnd{3}\deg{d}\f{sin}\sine@stop{#2}}^^A +% \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{minipage} +% +% +% \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\ is available. If it isn't, an error +% will be thrown using |\errmessage|: +% \begin{lstlisting} +% ! expkv Error: e-TeX 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 by providing an undefined macro. In the +% following messages \lstinline|| gets replaced with the problematic key's +% name, and \lstinline|| 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} +% ! Undefined control sequence. +% \! expkv Error: +% unknown key (`', 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} +% ! Undefined control sequence. +% \! expkv Error: +% value required (`', 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} +% ! Undefined control sequence. +% \! expkv Error: +% value forbidden (`', set `'). +% \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} +% ! Missing \endcsname inserted. +% +% \ekv( +% \end{lstlisting} +% +% \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 +% +%^^A=<< +%^^A main file >>= +% \subsection{The Generic Code} +% The rest of this implementation will be the generic code. +% \gobbledocstriptag +%<*tex> +% +% Check whether \eTeX\ is available -- \expkv\ requires \eTeX. +% \begin{macrocode} +\begingroup\expandafter\expandafter\expandafter\endgroup +\expandafter\ifx\csname numexpr\endcsname\relax + \errmessage{expkv requires e-TeX} + \expandafter\endinput +\fi +% \end{macrocode} +% +% We make sure that it's only input once: +% \begin{macrocode} +\expandafter\ifx\csname ekvVersion\endcsname\relax +\else + \expandafter\endinput +\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{0.3} +\def\ekvDate{2020-01-22} +% \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. +% \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} +% { +% \@gobble,\@firstofone,\@firstoftwo,\@secondoftwo, +% \ekv@gobbletostop,\ekv@fi@gobble,\ekv@fi@secondoftwo +% } +% 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. +% \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@gobbletostop#1\ekv@stop{} +\long\def\ekv@fi@gobble\fi\@firstofone#1{\fi} +\long\def\ekv@fi@secondoftwo\fi\@firstoftwo#1#2{\fi#2} +% \end{macrocode} +% \end{macro} +% As you can see |\ekv@gobbletostop| 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} +% { +% \ekv@ifempty,\ekv@ifempty@,\ekv@ifempty@true,\ekv@ifempty@false, +% \ekv@ifempty@true@F +% } +% 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{} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\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. +% \begin{macrocode} +\long\def\ekv@ifblank@\ekv@mark#1{\ekv@ifempty@\ekv@ifempty@A} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\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, slower than +% the typical expandable test for undefined control sequences, but faster for +% defined ones. Since we want to be as fast as possible for correct input, this +% is to be preferred. +% \begin{macrocode} +\def\ekv@ifdefined#1% + {% + \expandafter + \ifx\csname\ifcsname #1\endcsname #1\else relax\fi\endcsname\relax + \ekv@fi@secondoftwo + \fi + \@firstoftwo + } +% \end{macrocode} +% \end{macro} +% \begin{macro} +% { +% \ekv@ifdefined@pair,\ekv@ifdefined@pair@,\ekv@ifdefined@key, +% \ekv@ifdefined@key@ +% } +% Since we can save some time if we only have to create the control sequence +% once when we know beforehand how we want to use it, we build some other macros +% for those cases (which we'll have quite often, once per key usage). +% \begin{macrocode} +\def\ekv@ifdefined@pair#1#2% + {% + \expandafter\ekv@ifdefined@pair@ + \csname + \ifcsname #1{#2}\endcsname + #1{#2}% + \else + relax% + \fi + \endcsname + } +\def\ekv@ifdefined@pair@#1% + {% + \ifx#1\relax + \ekv@fi@secondoftwo + \fi + \@firstoftwo + {\ekv@set@pair@#1\ekv@mark}% + } +\def\ekv@ifdefined@key#1#2% + {% + \expandafter\ekv@ifdefined@key@ + \csname + \ifcsname #1{#2}N\endcsname + #1{#2}N% + \else + relax% + \fi + \endcsname + } +\def\ekv@ifdefined@key@#1% + {% + \ifx#1\relax + \ekv@fi@secondoftwo + \fi + \@firstoftwo#1% + } +% \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#1#2{\ekv@name@set{#1}\ekv@name@key{#2}} +\def\ekv@name@set#1{ekv#1(} +\def\ekv@name@key#1{#1)} +% \end{macrocode} +% \end{macro} +% \begin{macro}{\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). +% \begin{macrocode} +\protected\def\ekv@checkvalid#1#2% + {% + \ekv@ifempty{#1}% + {% + \def\ekv@tmp{}% + \errmessage{expkv Error: empty set name not allowed}% + }% + {% + \ekv@ifempty{#2}% + {% + \def\ekv@tmp{}% + \errmessage{expkv Error: empty key name not allowed}% + }% + \@secondoftwo + }% + \@gobble + } +% \end{macrocode} +% \end{macro} +% \begin{macro}{\ekvifdefined,\ekvifdefinedNoVal} +% And provide user-level macros to test whether a key is defined. +% \begin{macrocode} +\def\ekvifdefined#1#2{\ekv@ifdefined{\ekv@name{#1}{#2}}} +\def\ekvifdefinedNoVal#1#2{\ekv@ifdefined{\ekv@name{#1}{#2}N}} +% \end{macrocode} +% \end{macro} +% +% \begin{macro} +% { +% \ekvdef,\ekvdefNoVal,\ekvlet,\ekvletNoVal,\ekvletkv,\ekvletkvNoVal, +% \ekv@defset +% } +% Set up the key defining macros |\ekvdef| etc. +% \begin{macrocode} +\protected\long\def\ekvdef#1#2#3% + {% + \ekv@checkvalid{#1}{#2}% + {% + \expandafter\def\csname\ekv@name{#1}{#2}\endcsname##1{#3}% + \ekv@defset{#1}% + }% + } +\protected\long\def\ekvdefNoVal#1#2#3% + {% + \ekv@checkvalid{#1}{#2}% + {% + \expandafter\def\csname\ekv@name{#1}{#2}N\endcsname{#3}% + \ekv@defset{#1}% + }% + } +\protected\def\ekvlet#1#2#3% + {% + \ekv@checkvalid{#1}{#2}% + {% + \expandafter\let\csname\ekv@name{#1}{#2}\endcsname#3% + \ekv@defset{#1}% + }% + } +\protected\def\ekvletNoVal#1#2#3% + {% + \ekv@checkvalid{#1}{#2}% + {% + \expandafter\let\csname\ekv@name{#1}{#2}N\endcsname#3% + \ekv@defset{#1}% + }% + } +\protected\def\ekvletkv#1#2#3#4% + {% + \ekv@checkvalid{#1}{#2}% + {% + \expandafter\let\csname\ekv@name{#1}{#2}\expandafter\endcsname + \csname\ekv@name{#3}{#4}\endcsname + \ekv@defset{#1}% + }% + } +\protected\def\ekvletkvNoVal#1#2#3#4% + {% + \ekv@checkvalid{#1}{#2}% + {% + \expandafter\let\csname\ekv@name{#1}{#2}N\expandafter\endcsname + \csname\ekv@name{#3}{#4}N\endcsname + \ekv@defset{#1}% + }% + } +\protected\def\ekv@defset#1% + {% + \expandafter\edef\csname\ekv@name@set{#1}\endcsname##1% + {\ekv@name@set{#1}\ekv@name@key{##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 replacing the active commas with commas of category other can be done +% beforehand. That's why we define |\ekvset| here with a temporary meaning just +% to set up the things with two different category codes. |#1| will be a +% \texttt{,\textsubscript{13}} and |#2| will be a \texttt{=\textsubscript{13}}. +% \begin{macrocode} +\def\ekvset#1#2{% +\endgroup +\long\def\ekvset##1##2% + {% + \expandafter\ekv@set\csname\ekv@name@set{##1}\endcsname + \ekv@mark##2#1\ekv@stop#1{}% + } +% \end{macrocode} +% \end{macro} +% \begin{macro}{\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@ifstop##2\ekv@endset\ekv@mark\ekv@stop +% \end{macrocode} +% else go on with other commas, +% \begin{macrocode} + \ekv@set@other##1##2,\ekv@stop,% +% \end{macrocode} +% and get the next active comma delimited \kv\ pair. +% \begin{macrocode} + \ekv@set##1\ekv@mark + } +% \end{macrocode} +% \end{macro} +% \begin{macro}{\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@mark\ekv@stop\ekv@set@other##1,\ekv@stop,\ekv@set##2\ekv@mark + ##3% + {##3} +% \end{macrocode} +% \end{macro} +% \begin{macro}{\ekv@set@other} +% The macro |\ekv@set@other| is guaranteed to get only single \kv\ pairs. +% So here we need to split at equal signs. If there is no equal sign, we need to +% test whether we're done and if not this is a |NoVal| key. +% \begin{macrocode} +\long\def\ekv@set@other##1##2,% + {% + \ekv@ifblank@##2\ekv@nil\ekv@ifempty@B\ekv@ifempty@true@F + \ekv@ifempty@A\ekv@ifempty@B\@firstofone + {% + \ekv@ifhas@eq@other##2=\ekv@ifempty@B\ekv@ifempty@false + \ekv@ifempty@A\ekv@ifempty@B\@firstoftwo + {\ekv@set@eq@other##1##2\ekv@stop}% + {% + \ekv@ifhas@eq@active##2#2\ekv@ifempty@B\ekv@ifempty@false + \ekv@ifempty@A\ekv@ifempty@B\@firstoftwo + {\ekv@set@eq@active##1##2\ekv@stop}% + {% + \ekv@ifstop##2\ekv@endset@other\ekv@mark\ekv@stop + \ekv@strip{##2}\ekv@set@key##1% + }% + }% + }% + \ekv@set@other##1\ekv@mark% + } +% \end{macrocode} +% \end{macro} +% \begin{macro}{\ekv@set@eq@other} +% |\ekv@set@eq@other| might not be the correct break point, there might be an +% active equal sign in the currently parsed key-name. If so, we have to resplit. +% \begin{macrocode} +\long\def\ekv@set@eq@other##1##2=% + {% + \ekv@ifhas@eq@active##2#2\ekv@ifempty@B\ekv@ifempty@false + \ekv@ifempty@A\ekv@ifempty@B\@firstoftwo + {\ekv@set@eq@active##1##2=}% + {\ekv@strip{##2}\ekv@set@pair##1}% + } +% \end{macrocode} +% \end{macro} +% \begin{macro}{\ekv@set@eq@active} +% We need to handle the active equal signs. +% \begin{macrocode} +\long\def\ekv@set@eq@active##1##2#2% + {% + \ekv@strip{##2}\ekv@set@pair##1% + } +% \end{macrocode} +% \end{macro} +% \begin{macro}{\ekv@ifhas@eq@other,\ekv@ifhas@eq@active,\ekv@endset@other} +% And we have to set up the testing macros for our equal signs and +% |\ekv@endset@other|. +% \begin{macrocode} +\long\def\ekv@ifhas@eq@other\ekv@mark##1={\ekv@ifempty@\ekv@ifempty@A} +\long\def\ekv@ifhas@eq@active\ekv@mark##1#2{\ekv@ifempty@\ekv@ifempty@A} +\long\def\ekv@endset@other + \ekv@mark\ekv@stop\ekv@strip##1\ekv@set@key##2% + \ekv@set@other##3\ekv@mark + {} +% \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. +% \begin{macrocode} +\long\def\ekvparse##1##2##3% + {% + \expanded{\ekv@parse##1##2\ekv@mark##3#1\ekv@stop#1}% + } +% \end{macrocode} +% \end{macro} +% \begin{macro}{\ekv@parse} +% \begin{macrocode} +\long\def\ekv@parse##1##2##3#1% + {% + \ekv@ifstop##3\ekv@endparse\ekv@mark\ekv@stop + \ekv@parse@other##1##2##3,\ekv@stop,% + \ekv@parse##1##2\ekv@mark + } +% \end{macrocode} +% \end{macro} +% \begin{macro}{\ekv@endparse} +% \begin{macrocode} +\long\def\ekv@endparse + \ekv@mark\ekv@stop\ekv@parse@other##1,\ekv@stop,\ekv@parse##2\ekv@mark + {} +% \end{macrocode} +% \end{macro} +% \begin{macro}{\ekv@parse@other} +% \begin{macrocode} +\long\def\ekv@parse@other##1##2##3,% + {% + \ekv@ifblank@##3\ekv@nil\ekv@ifempty@B\ekv@ifempty@true@F + \ekv@ifempty@A\ekv@ifempty@B\@firstofone + {% + \ekv@ifhas@eq@other##3=\ekv@ifempty@B\ekv@ifempty@false + \ekv@ifempty@A\ekv@ifempty@B\@firstoftwo + {\unexpanded{##2}\ekv@parse@eq@other##3\ekv@stop}% + {% + \ekv@ifhas@eq@active##3#2\ekv@ifempty@B\ekv@ifempty@false + \ekv@ifempty@A\ekv@ifempty@B\@firstoftwo + {\unexpanded{##2}\ekv@parse@eq@active##3\ekv@stop}% + {% + \ekv@ifstop##3\ekv@endparse@other\ekv@mark\ekv@stop + \unexpanded{##1}{\ekv@strip{##3}\unexpanded}% + }% + }% + }% + \ekv@parse@other##1##2\ekv@mark + } +% \end{macrocode} +% \end{macro} +% \begin{macro}{\ekv@parse@eq@other} +% \begin{macrocode} +\long\def\ekv@parse@eq@other##1=% + {% + \ekv@ifhas@eq@active##1#2\ekv@ifempty@B\ekv@ifempty@false + \ekv@ifempty@A\ekv@ifempty@B\@firstoftwo + {\ekv@parse@eq@active##1=}% + {{\ekv@strip{##1}\unexpanded}\ekv@parse@pair\ekv@mark}% + } +% \end{macrocode} +% \end{macro} +% \begin{macro}{\ekv@parse@eq@active} +% \begin{macrocode} +\long\def\ekv@parse@eq@active##1#2% + {% + {\ekv@strip{##1}\unexpanded}\ekv@parse@pair\ekv@mark + } +% \end{macrocode} +% \end{macro} +% \begin{macro}{\ekv@endparse@other} +% \begin{macrocode} +\long\def\ekv@endparse@other + \ekv@mark\ekv@stop\unexpanded##1% + \ekv@parse@other##2\ekv@mark + {} +% \end{macrocode} +% \end{macro} +% \begin{macro}{\ekv@parse@pair} +% \begin{macrocode} +\long\def\ekv@parse@pair##1\ekv@stop + {% + {\ekv@strip{##1}\unexpanded}% + } +% \end{macrocode} +% \end{macro} +% +% Finally really setting things up with |\ekvset|'s temporary meaning: +% \begin{macrocode} +} +\begingroup +\catcode`\,=13 +\catcode`\==13 +\ekvset,= +% \end{macrocode} +% +% \begin{macro}{\ekv@ifstop} +% The |\ekv@ifstop| test works similar to our if-empty test, but instead of +% using tokens which are used nowhere else (|\ekv@ifempty@A| and +% |\ekv@ifempty@B|) we use |\ekv@mark| and |\ekv@stop|. +% \begin{macrocode} +\long\def\ekv@ifstop#1\ekv@mark\ekv@stop{} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ekv@set@pair,\ekv@set@pair@} +% |\ekv@set@pair| needs to split the argument at the |=| sign and check +% whether the key is defined. +% \begin{macrocode} +\long\def\ekv@set@pair#1#2% + {% + \ekv@ifdefined@pair#2{#1}% + {% + \ekv@ifdefined{#2{#1}N}% + \ekv@err@noarg + \ekv@err@unknown + #2{#1}% + \ekv@gobbletostop + }% + } +\long\def\ekv@set@pair@#1#2\ekv@stop + {% + \ekv@strip{#2}#1% + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ekv@set@key} +% \begin{macrocode} +\long\def\ekv@set@key#1#2% + {% + \ekv@ifdefined@key#2{#1}% + {% + \ekv@ifdefined{#2{#1}}% + \ekv@err@reqval + \ekv@err@unknown + #2{#1}% + }% + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ekv@err,\ekv@err@} +% Since |\ekvset| is fully expandable as long as the code of the keys is (which +% is unlikely) we want to somehow throw expandable errors, in our case via +% undefined control sequences. +% \begin{macrocode} +\begingroup +\edef\ekv@err + {% + \endgroup + \unexpanded{\long\def\ekv@err}##1% + {% + \unexpanded{\expandafter\ekv@err@\@firstofone}% + {\expandafter\noexpand\csname ! expkv Error:\endcsname ##1.}% + \unexpanded{\ekv@stop}% + }% + } +\ekv@err +\def\ekv@err@{\expandafter\ekv@gobbletostop} +% \end{macrocode} +% \end{macro} +% \begin{macro} +% { +% \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}} +\long\def\ekv@err@common@#1#2#3#4#5(#6#7{\ekv@err{#6 (`#7', set `#5')}} +\long\def\ekv@err@unknown#1#2{\ekv@err@common{unknown key}#1{#2}} +\long\def\ekv@err@noarg #1#2{\ekv@err@common{value forbidden}#1{#2}} +\long\def\ekv@err@reqval #1#2{\ekv@err@common{value required}#1{#2}} +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\ekv@strip,\ekv@strip@a,\ekv@strip@b,\ekv@strip@c} +% Finally 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|. +% \begin{macrocode} +\def\ekv@strip#1% + {% + \long\def\ekv@strip##1% + {% + \ekv@strip@a + ##1% + \ekv@nil + \ekv@mark#1% + #1\ekv@nil{}% + \ekv@stop + }% + \long\def\ekv@strip@a##1\ekv@mark#1##2\ekv@nil##3% + {% + \ekv@strip@b##3##1##2\ekv@nil + }% + \long\def\ekv@strip@b##1#1\ekv@nil + {% + \ekv@strip@c##1\ekv@nil + }% + \long\def\ekv@strip@c\ekv@mark##1\ekv@nil##2\ekv@stop##3% + {% + ##3{##1}% + }% + } +\ekv@strip{ } +% \end{macrocode} +% \end{macro} +% +% Now everything that's left is to reset the category code of |@|. +% \begin{macrocode} +\catcode`\@=\ekv@tmp +% \end{macrocode} +% +% \gobbledocstriptag +% +%^^A=<< +% +%^^A=<< +% +% \end{implementation}^^A=<< +% +% \clearpage +% \PrintIndex +% +\endinput +% +^^A vim: ft=tex fdm=marker fmr=>>=,=<< diff --git a/macros/generic/expkv/expkv.pdf b/macros/generic/expkv/expkv.pdf new file mode 100644 index 0000000000..d3bf8d0cac Binary files /dev/null and b/macros/generic/expkv/expkv.pdf differ -- cgit v1.2.3