From c42cae68d2e7deb3b18968354a3e1dec03e2da77 Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Tue, 25 May 2021 20:47:02 +0000 Subject: expkv (25may21) git-svn-id: svn://tug.org/texlive/trunk@59342 c570f23f-e606-0410-a88d-b1316a301751 --- Master/texmf-dist/source/latex/expkv/expkv.dtx | 559 ++++++++++++++++++++----- 1 file changed, 445 insertions(+), 114 deletions(-) (limited to 'Master/texmf-dist/source/latex/expkv') diff --git a/Master/texmf-dist/source/latex/expkv/expkv.dtx b/Master/texmf-dist/source/latex/expkv/expkv.dtx index 553065f32bd..37a67fc7812 100644 --- a/Master/texmf-dist/source/latex/expkv/expkv.dtx +++ b/Master/texmf-dist/source/latex/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} + {}{{$\langle$}key{$\rangle$}}{5} + {}{{$\langle$}set{$\rangle$}}{5} + {}{{$\langle$}set1{$\rangle$}}{6} + {}{{$\langle$}set2{$\rangle$}}{6} ^^A,literate=*{}{\key}{4}{}{\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 \ +% 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 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 \ +% 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 at the ? prompt and I +% continue hoping recovery was complete. +% +% \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|| 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 +% thrown in an expandable manner using |\ekverr|. 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 `'). +% Runaway argument? +% ! expkv Error: unknown key `' in 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 `'). +% Runaway argument? +% ! expkv Error: missing value for `' in 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 `'). +% Runaway argument? +% ! expkv Error: unwanted value for `' in set `' % \end{lstlisting} % % If you're using an undefined key in a set for which |\ekvredirectunknown| was % used, and the key isn't found in any of the other sets as well, you'll get: % \begin{lstlisting} -% ! Undefined control sequence. -% \! expkv Error: -% no key `' in sets {}{}... +% Runaway argument? +% ! expkv Error: no key `' in sets {}{}... % \end{lstlisting} % % If you're using an undefined |NoVal| key in a set for which % |\ekvredirectunknownNoVal| was used, and the key isn't found in any of the % other sets as well, you'll get: % \begin{lstlisting} -% ! Undefined control sequence. -% \! expkv Error: -% no NoVal key `' in sets {}... +% Runaway argument? +% ! expkv Error: no NoVal key `' in sets {}{}... % \end{lstlisting} % % If you're using a set for which you never executed one of the defining macros % from \autoref{sec:define} you'll get a low level \TeX\ error, as that isn't % actively tested by the parser (and hence will lead to undefined behaviour and % not be gracefully ignored). The error will look like -% \begin{lstlisting} +% \begin{lstlisting}[language={}] % ! Missing \endcsname inserted. % % \! expkv Error: 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 ^^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 \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}% -- cgit v1.2.3