summaryrefslogtreecommitdiff
path: root/macros/generic/expkv
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2021-05-25 03:01:10 +0000
committerNorbert Preining <norbert@preining.info>2021-05-25 03:01:10 +0000
commit07dc96bc3bbd691fc52aca15a172eaf41a89d873 (patch)
treeeb7c276d652209d0d5eb4448b07aaadd440aa4c7 /macros/generic/expkv
parent5f0d648973cab5784f4bdb361bbe261b60493432 (diff)
CTAN sync 202105250301
Diffstat (limited to 'macros/generic/expkv')
-rw-r--r--macros/generic/expkv/README.md2
-rw-r--r--macros/generic/expkv/expkv.dtx559
-rw-r--r--macros/generic/expkv/expkv.pdfbin444584 -> 481077 bytes
3 files changed, 446 insertions, 115 deletions
diff --git a/macros/generic/expkv/README.md b/macros/generic/expkv/README.md
index 512cd829ca..adc545fd80 100644
--- a/macros/generic/expkv/README.md
+++ b/macros/generic/expkv/README.md
@@ -1,7 +1,7 @@
-------------------------------------------------------------------------------
# expkv -- an expandable key=val implementation
-Version 2021-04-11 v1.7a
+Version 2021-05-24 v1.8
Released under the LaTeX Project Public License v1.3c or later
See http://www.latex-project.org/lppl.txt
diff --git a/macros/generic/expkv/expkv.dtx b/macros/generic/expkv/expkv.dtx
index 553065f32b..37a67fc781 100644
--- a/macros/generic/expkv/expkv.dtx
+++ b/macros/generic/expkv/expkv.dtx
@@ -76,34 +76,130 @@ and the derived files expkv.pdf
\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
+ {
+ approx,
+ begin,
+ empty,
+ item,
+ LaTeX,
+ makeatletter,makeatother,
+ newcommand,newdimen,
+ RequirePackage,
+ rule,
+ TeX,
+ textit,texttt,
+ usepackage,
+ }
+ ,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=.53em
+ ,basewidth=\mylstwd
,gobble=2
,basicstyle=\fontfamily{jkp}\itshape
- ,morekeywords=^^A
- {^^A
- \ekvdef,\ekvdefNoVal,^^A
- \ekvlet,\ekvletNoVal,\ekvletkv,\ekvletkvNoVal,^^A
- \ekvdefunknown,\ekvdefunknownNoVal,^^A
- \ekvredirectunknown,\ekvredirectunknownNoVal,^^A
- \ekvset,\ekvsetSneaked,^^A
- \ekvsetdef,\ekvsetSneakeddef,\ekvsetdefSneaked,^^A
- \ekvparse,^^A
- \ekvVersion,\ekvDate,^^A
- \ekvifdefined,\ekvifdefinedNoVal,\ekvifdefinedset,^^A
- \ekvbreak,\ekvbreakPreSneak,\ekvbreakPostSneak,^^A
- \ekvsneak,\ekvsneakPre,^^A
- \ekvchangeset
- }
- ,morecomment=[l]\%
+ ,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={\{}{{\CodeSymbol\{}}{1}
- {\}}{{\CodeSymbol\}}}{1}
+ ,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*\CodeSymbol[1]{\textbf{#1}}
+\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}}}
@@ -474,7 +570,13 @@ and the derived files expkv.pdf
% 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}
@@ -564,11 +666,15 @@ and the derived files expkv.pdf
% 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\footnotemark.
-% If you need control over the necessary steps of expansion you
-% can use |\expanded| around it. 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.
+% 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
@@ -576,14 +682,6 @@ and the derived files expkv.pdf
% 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}
-% \footnotetext
-% {^^A
-% This is a change in behaviour, previously (v0.3 and before) \cs{ekvparse}
-% would expand in exactly two steps. This isn't always necessary, but makes
-% the parsing considerably slower. If this is necessary for your application
-% you can put an \cs[no-index]{expanded} around it and will still be faster
-% since you need only a single \cs[no-index]{expandafter} this way.^^A
-% }
% \example
% \begin{lstlisting}
% \ekvparse{\handlekey{S}}{\handlekeyval{S}}{foo = bar, key, baz={zzz}}
@@ -599,11 +697,11 @@ and the derived files expkv.pdf
% another macro, you should use |\expanded| as only then the input stream will
% contain the output above:
% \begin{lstlisting}
-% \expandafter\handle\expanded{\ekvparse\k\kv{foo = bar, key, baz={zzz}}}
+% \expandafter\parse\expanded{\ekvparse\k\kv{foo = bar, key, baz={zzz}}}
% \end{lstlisting}
% would expand to
% \begin{lstlisting}
-% \handle\kv{foo}{bar}\k{key}\kv{baz}{zzz}
+% \parse\kv{foo}{bar}\k{key}\kv{baz}{zzz}
% \end{lstlisting}
%
% \subsection{Other Macros}
@@ -673,7 +771,7 @@ and the derived files expkv.pdf
% list everything that has been |\ekvsneak|ed will be left in the input
% stream. A small usage example is shown in \autoref{sec:sneakex}.
% \end{function}
-% \example Define a key |secret| in the set |foo| that will sneak out a macro
+% \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}}
@@ -696,6 +794,124 @@ and the derived files expkv.pdf
% {\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).
+% \end{function}
+% |\ekvoptarg| expands in exactly two steps 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 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}{\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.
+% \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]
+% {%
+% \number\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}
@@ -744,17 +960,18 @@ and the derived files expkv.pdf
% \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}
+% \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{minipage}{.5\linewidth}
% \begin{lstlisting}
% \ourmacro{}\par
% \ourmacro{width}\par
% \ourmacro{width=5pt}\par
% \end{lstlisting}
% \end{minipage}^^A
-% \begin{minipage}{\dimexpr\linewidth-6cm\relax}
+% \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
@@ -763,7 +980,7 @@ and the derived files expkv.pdf
% \ourmacro{}\par
% \ourmacro{width}\par
% \ourmacro{width=5pt}\par
-% \end{minipage}
+% \end{exresult}
%
% \paragraph{The same keys using \protect\expkvd}
% Using \expkvd\ we can set up the equivalent key using a \kv\ interface, after
@@ -785,7 +1002,7 @@ and the derived files expkv.pdf
%
% Another small example could be a \kv\ driven |\rule| alternative, because I
% keep forgetting the correct order of its arguments. First we define the keys
-% (and initialize the used macros to store the keys):
+% (and initialize the macros used to store the keys):
% \begin{lstlisting}
% \makeatletter
% \newcommand*\myrule@ht{1ex}
@@ -794,12 +1011,14 @@ and the derived files expkv.pdf
% \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}
+% \newcommand*\myrule[1][]
+% {\begingroup\myruleset{#1}\myrule@out\endgroup}
% \end{lstlisting}
% And finally the output:
% \begin{lstlisting}
@@ -807,15 +1026,15 @@ and the derived files expkv.pdf
% \makeatother
% \end{lstlisting}
% And we can use it:\\
-% \begin{minipage}{7cm}
+% \begin{minipage}{.5\linewidth}
% \begin{lstlisting}
% a\myrule\par
-% a\myrule[ht=2ex,raise=-.5ex]\par
+% a\myrule[ht=2ex,lower=.5ex]\par
% \myruleset{wd=5pt}
% a\myrule
% \end{lstlisting}
% \end{minipage}^^A
-% \begin{minipage}{\dimexpr\linewidth-7cm\relax}
+% \begin{exresult}[nobeforeafter,box align=center]{.5\linewidth}
% \makeatletter
% \newcommand*\myrule@ht{1ex}
% \newcommand*\myrule@wd{0.1em}
@@ -823,16 +1042,17 @@ and the derived files expkv.pdf
% \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,raise=-.5ex]\par
+% a\myrule[ht=2ex,lower=.5ex]\par
% \myruleset{wd=5pt}
% a\myrule
-% \end{minipage}
-%
+% \end{exresult}
+%
%
% \subsubsection{An Expandable \kv\ Macro Using \cs[no-index]{ekvsneak}}
% \label{sec:sneakex}
@@ -877,7 +1097,10 @@ and the derived files expkv.pdf
% 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.
+% 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}
@@ -887,13 +1110,10 @@ and the derived files expkv.pdf
% \ekvdefNoVal{expex}{degree}{\ekvsneakPre{\deg{d}}}
% \ekvdefNoVal{expex}{radian}{\ekvsneakPre{\deg{}}}
% \ekvdefNoVal{expex}{internal}{\ekvsneakPre{\sine@rnd}}
-% \newcommand*\sine[2]
+% \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}
-% 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
@@ -922,33 +1142,34 @@ and the derived files expkv.pdf
% \makeatother
% \end{lstlisting}
% Let's test our macro:\\
-% \begin{minipage}{.7\linewidth}
+% \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}
+% \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}
+% \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[2]
-% {\ekvset{expex}{#1,internal}\rnd{3}\deg{d}\f{sin}\sine@stop{#2}}^^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{minipage}
+% \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
@@ -956,18 +1177,19 @@ and the derived files expkv.pdf
% way easier to code for the user.
% \begin{lstlisting}
% \makeatletter
-% \ekvcSplitAndForward\sine\sine@
+% \newcommand*\sine{\ekvoptargTF\sine@a{\sine@b{sin}{d}{3}}}
+% \ekvcSplitAndForward\sine@a\sine@b
% {
% f=sin,
% unit=d,
% round=3,
% }
-% \ekvcSecondaryKeys\sine
+% \ekvcSecondaryKeys\sine@a
% {
% nmeta degree={unit=d},
% nmeta radian={unit={}},
% }
-% \newcommand*\sine@[4]{\fpeval{round(#1#2(#4),#3)}}
+% \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
@@ -982,8 +1204,8 @@ and the derived files expkv.pdf
%
% \subsubsection{Load Time}
%
-% \file{expkv.tex} checks whether \eTeX\ is available. If it isn't, an error
-% will be thrown using |\errmessage|:
+% \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 required.
% \end{lstlisting}
@@ -1010,57 +1232,52 @@ and the derived files expkv.pdf
% \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|<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
+% 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}
-% ! Undefined control sequence.
-% <argument> \! expkv Error:
-% unknown key (`<key>', set `<set>').
+% 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}
-% ! Undefined control sequence.
-% <argument> \! expkv Error:
-% value required (`<key>', set `<set>').
+% 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}
-% ! Undefined control sequence.
-% <argument> \! expkv Error:
-% value forbidden (`<key>', set `<set>').
+% 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}
-% ! Undefined control sequence.
-% <argument> \! expkv Error:
-% no key `<key>' in sets {<set1>}{<set2>}...
+% 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}
-% ! Undefined control sequence.
-% <argument> \! expkv Error:
-% no NoVal key `<key>' in sets {<set1>}...
+% 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}
+% \begin{lstlisting}[language={}]
% ! Missing \endcsname inserted.
% <to be read again>
% \! expkv Error: Set `<set>' undefined.
@@ -1197,8 +1414,8 @@ and the derived files expkv.pdf
% loading \yax\ broke \pkg{options} for me. The tested definition was:
% \begin{lstlisting}[belowskip=0pt]
% \usepackage{yax}
-% \defactiveparameter yax {\storevalue\myheight yax:height } % key setup
-% \setparameterlist{yax}{ height = 6 } % benchmarked
+% \defactiveparameter yax {\storevalue\myheight yax:height } % setup
+% \setparameterlist{yax}{ height = 6 } % benchmark
% \end{lstlisting}
%
% \begin{table}
@@ -1313,11 +1530,16 @@ and the derived files expkv.pdf
\fi
% \end{macrocode}
%
-% Check whether \eTeX\ is available -- \expkv\ requires \eTeX.
+% Check whether \eTeX\ and |\expanded| are available -- \expkv\ requires \eTeX.
% \begin{macrocode}
-\begingroup\expandafter\expandafter\expandafter\endgroup
-\expandafter\ifx\csname numexpr\endcsname\relax
- \errmessage{expkv requires e-TeX}
+\begingroup
+ \edef\ekvtmpa{\string\expanded}
+ \edef\ekvtmpb{\meaning\expanded}
+ \expandafter
+\endgroup
+\ifx\ekvtmpa\ekvtmpb
+\else
+ \errmessage{expkv Error: e-TeX and \noexpand\expanded required}
\expandafter\endinput
\fi
% \end{macrocode}
@@ -1325,8 +1547,8 @@ and the derived files expkv.pdf
% \begin{macro}{\ekvVersion,\ekvDate}
% We're on our first input, so lets store the version and date in a macro.
% \begin{macrocode}
-\def\ekvVersion{1.7a}
-\def\ekvDate{2021-04-11}
+\def\ekvVersion{1.8}
+\def\ekvDate{2021-05-24}
% \end{macrocode}
% \end{macro}
%
@@ -2092,9 +2314,12 @@ and the derived files expkv.pdf
% 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.
+% 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@parse{##1}{##2}\ekv@mark##3#1\ekv@stop#1}
+\long\def\ekvparse##1##2##3%
+ {\unexpanded\expanded{{\ekv@parse{##1}{##2}\ekv@mark##3#1\ekv@stop#1}}}
% \end{macrocode}
% \end{macro}
%
@@ -2440,18 +2665,124 @@ and the derived files expkv.pdf
% \end{macrocode}
% \end{macro}
%
-% \begin{macro}[internal]{\ekv@err,\ekv@err@}
+% \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|).
+% \begin{macrocode}
+\begingroup
+\def\ekvoptarg#1#2{%
+\endgroup
+% \end{macrocode}
+% The real definition starts an expansion context after grabbing 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}
+\long\def\ekvoptarg##1##2##3%
+ {%
+ \romannumeral
+ #1%
+ \ekv@optarg@if\ekv@mark##3\ekv@mark\ekv@optarg\ekv@mark[\ekv@mark
+ #2%
+ \@firstofone{\ekv@zero##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}
+\long\def\ekvoptargTF##1##2##3%
+ {%
+ \romannumeral
+ #1%
+ \ekv@optarg@if\ekv@mark##3\ekv@mark\ekv@optargTF{##1}\ekv@mark[\ekv@mark
+ #2%
+ \@firstofone{\ekv@zero##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\ekv@mark[\ekv@mark\ifnum`##1\fi\@firstofone##2##3##4##5]%
+ {#2##2{##5}}
+\long\def\ekv@optargTF##1\ekv@mark[\ekv@mark\ifnum`##2\fi\@firstofone##3##4##5]%
+ {#2\ekv@zero##1{##5}}
+}
+% \end{macrocode}
+% Do the definitions and add the test macro.
+% \begin{macrocode}
+\ekv@exparg{\expandafter\ekvoptarg\expandafter{\ekv@alignsafe}}\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
-% undefined control sequences.
-% \begin{macrocode}
-\def\ekv@err#1%
+% 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%
{%
- \long\def\ekv@err##1{\expandafter\ekv@err@\@firstofone{#1##1.}\ekv@stop}%
+ \def\ekv@err@collect##1\par##2%
+ {%
+ \expandafter
+ \ekv@err@cleanup
+ #1! ##2 Error: ##1\par
+ }%
+ \def#1##1\thanks@jfbu{}%
}
-\begingroup\expandafter\endgroup
-\expandafter\ekv@err\csname ! expkv Error:\endcsname
-\def\ekv@err@{\expandafter\ekv@gobbleto@stop}
+\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}
%
@@ -2465,10 +2796,10 @@ and the derived files expkv.pdf
% \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', set `#2')}}
+ {\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{value forbidden}{#1}}
-\ekv@exparg{\long\def\ekv@err@reqval #1}{\ekv@err@common{value required}{#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}%
diff --git a/macros/generic/expkv/expkv.pdf b/macros/generic/expkv/expkv.pdf
index 95b8d60b57..1f9248c9ec 100644
--- a/macros/generic/expkv/expkv.pdf
+++ b/macros/generic/expkv/expkv.pdf
Binary files differ