1

interface3.pdf says:

\keyval_parse:NNn ⟨function1⟩ ⟨function2⟩ {⟨key–value list⟩}
Parses the ⟨key–value list⟩ into a series of ⟨keys⟩ and associated ⟨values⟩, or keys alone (if no ⟨value⟩ was given). ⟨function1⟩ should take one argument, while ⟨function2⟩ should absorb two arguments. After \keyval_parse:NNn has parsed the ⟨key–value list⟩, ⟨function1⟩ is used to process keys given with no value and ⟨function2⟩ is used to process keys given with a value. The order of the ⟨keys⟩ in the ⟨key–value list⟩ is preserved.
...

My question is:

Why are you urged to use functions only which absorb exactly 1 respective 2 arguments?

I ask this because this prevents you from placing \keyval_parse:NNn into macro-definitions where

  • ⟨function1⟩ processes several arguments whereof the last one is the ⟨key⟩ while the other arguments come from arguments/parameters of the macro-definition.
  • ⟨function2⟩ processes several arguments whereof the last but one is the ⟨key⟩ and the last is the ⟨value⟩ while the other arguments come from arguments/parameters of the macro-definition.

I would have liked to do something like this:

\cs_new:Nn \MyStuff_ProcessInCaseOnlyKey:nnn {
  This~is~the~first~argument~of~\texttt{\token_to_str:N\MyStuff_ProcessAsKeyval:nnn}:~\exp_args:No\texttt{\tl_to_str:n{#1}}.\\
  This~is~the~second~argument~of~\texttt{\token_to_str:N\MyStuff_ProcessAsKeyval:nnn}:~\exp_args:No\texttt{\tl_to_str:n{#2}}.\\
  This~is~the~key~which~was~passed~on~by~the~keyval~parser:~\exp_args:No\texttt{\tl_to_str:n{#3}}.\\
}
\cs_new:Nn \MyStuff_ProcessInCaseKeyAndValue:nnnn {
  This~is~the~first~argument~of~\texttt{\token_to_str:N\MyStuff_ProcessAsKeyval:nnn}:~\exp_args:No\texttt{\tl_to_str:n{#1}}.\\
  This~is~the~second~argument~of~\texttt{\token_to_str:N\MyStuff_ProcessAsKeyval:nnn}:~\exp_args:No\texttt{\tl_to_str:n{#2}}.\\
  This~is~the~key~which~was~passed~on~by~the~keyval~parser:~\exp_args:No\texttt{\tl_to_str:n{#3}}.\\
  This~is~the~value~which~was~passed~on~by~the~keyval~parser:~\exp_args:No\texttt{\tl_to_str:n{#4}}.\\
}
\cs_new:Nn \MyStuff_ProcessAsKeyval:nnn {
  \keyval_parse:NNn { \MyStuff_ProcessInCaseOnlyKey:nnn {#1}{#2} } 
                    { \MyStuff_ProcessInCaseKeyAndValue:nnnn {#1}{#2} }
                    {#3}
}

I would have liked to be able to use it without the need of doubling hashes in case \MyStuff_ProcessAsKeyval:nnn is something whose 1st and 2nd argument are to be used for providing "inline-code" with arguments.

For example I would have liked to be able to do

\MyStuff_ProcessAsKeyval:nnn 
  {\cs_set:Nn \MyStuff_tempa:n{This~is~a~temporary~macro's~argument:~#1}}
  {\cs_set:Nn \MyStuff_tempb:n{This~is~another~temporary~macro's~argument:~#1}}
  {keyA=valueA, keyB=valueB}

instead of

\MyStuff_ProcessAsKeyval:nnn 
  {\cs_set:Nn \MyStuff_tempa:n{This~is~a~temporary~macro's~argument:~##1}}
  {\cs_set:Nn \MyStuff_tempb:n{This~is~another~temporary~macro's~argument:~##1}}
  {keyA=valueA, keyB=valueB}

.

(Of course it should not be \keyval_parse:NNn but \keyval_parse:nnn. But \keyval_parse:nnn is not available and cannot be derived as a variant. Of course - instead of passing on #1 and #2 - I could have TeX define two temporary macros with each execution of \MyStuff_ProcessAsKeyval:nnn and use them in the definitions of \MyStuff_ProcessInCaseOnlyKey:n and \MyStuff_ProcessInCaseKeyAndValue:nn, but this is cumbersome and seems inefficient.)

I don't provide MWE because I ask about the reason for things being implemented as they are.

I am not asking for help with bug-tracking.

9
  • So you want something like \keyval_parse_inline:nnn {<inline code for only key>} {<inline code for key-value pair>} {<keyval list>}? I guess it could be implemented, but it wouldn't be a lot far from \cs_set:Npn \__diez_tmpa:n { <inline code for only key> } \cs_set:Npn \__diez_tmpb:nn { <inline code for key-value pair> } then \keyval_parse:NNn \__diez_tmpa:n \__diez_tmpb:nn { <keyval list> }, which is more or less what _inline functions do (modulo some bookkeeping for nesting). Dec 6 '20 at 0:03
  • @PhelypeOleinik As I already wrote in the question: "Of course - instead of passing on #1 and #2 - I could have TeX define a bunch of temporary macros and use them in the definitions of \MyStuff_ProcessInCaseOnlyKey:n and \MyStuff_ProcessInCaseKeyAndValue:nn..." You suggest having \MyStuff_ProcessInCaseOnlyKey:n and \MyStuff_ProcessInCaseKeyAndValue:nn as temporary macros themselves. Either way the macro I have in mind would define two temporary macros whenever it is carried out. Imo this is inefficient. Dec 6 '20 at 0:32
  • Ah, so you want an (possibly expandable) approach like in \tl_map_tokens:nn. That should be possible too (although quite a lot more complicated). However all the argument passing-around would make the whole thing a lot less efficient than just defining two temporary macros. Or are you thinking of something else? Dec 6 '20 at 0:47
  • 1
    Well, actually the difference between \keyval_parse:NNn and \keyval_parse:nnn would only be a few more braces here and there in the internal code. During no step of parsing is it really important that it's a legal N-type argument. The original use case (l3keys code) didn't need to forward any tokens. Without the braces the code is a tiny bit faster, that's all. If you don't want to use temporary definitions (because you want to stay expandable, or what-not) you could as well e-expand the \keyval_parse:NNn and put the other arguments behind some markers.
    – Skillmon
    Dec 6 '20 at 16:36
  • 1
    The main reason really is: better performance. And it wasn't necessary for the use cases that showed up until now, so nobody bothered.
    – Skillmon
    Dec 7 '20 at 21:12
5

The main reason for \keyval_parse:NNn to take N-type arguments instead of a group of tokens (n-type) is performance; and the fact that this simply wasn't needed.

Quick and dirty proof of concept that \keyval_parse:NNn is rather easy to adapt to this use. All I did was to search for NN and replace it with nn as well as putting the missing braces around the arguments in the first few steps of parsing. (well, and I used new names, instead of keyval the new module is named ulrichkeyval).

I'm not sure whether I spotted every place the braces are missing, but the code works, so I guess so...

The result is code that is roughly 5% slower for the uses with a single token (benchmarked for 100 keys and 100 key=value pair, as well as 1 blank element).

\documentclass[]{article}

\ExplSyntaxOn
\scan_new:N \s__ulrichkeyval_nil
\scan_new:N \s__ulrichkeyval_mark
\scan_new:N \s__ulrichkeyval_stop
\scan_new:N \s__ulrichkeyval_tail
\group_begin:
  \cs_set_protected:Npn \__ulrichkeyval_tmp:NN #1#2
    {
      \cs_new:Npn \ulrichkeyval_parse:nnn ##1 ##2 ##3
        { \__ulrichkeyval_loop_active:nnw {##1} {##2} \s__ulrichkeyval_mark ##3 #1 \s__ulrichkeyval_tail #1 }
      \cs_new:Npn \__ulrichkeyval_loop_active:nnw ##1 ##2 ##3 #1
        {
          \__ulrichkeyval_if_recursion_tail:w ##3
            \__ulrichkeyval_end_loop_active:w \s__ulrichkeyval_tail
          \__ulrichkeyval_loop_other:nnw {##1} {##2} ##3 , \s__ulrichkeyval_tail ,
          \__ulrichkeyval_loop_active:nnw {##1} {##2} \s__ulrichkeyval_mark
        }
      \cs_new:Npn \__ulrichkeyval_split_other:w ##1 = ##2 \s__ulrichkeyval_mark ##3 ##4 \s__ulrichkeyval_stop
        { ##3 ##1 \s__ulrichkeyval_stop \s__ulrichkeyval_mark ##2 }
      \cs_new:Npn \__ulrichkeyval_split_active:w ##1 #2 ##2 \s__ulrichkeyval_mark ##3 ##4 \s__ulrichkeyval_stop
        { ##3 ##1 \s__ulrichkeyval_stop \s__ulrichkeyval_mark ##2 }
      \cs_new:Npn \__ulrichkeyval_loop_other:nnw ##1 ##2 ##3 ,
        {
          \__ulrichkeyval_if_recursion_tail:w ##3
            \__ulrichkeyval_end_loop_other:w \s__ulrichkeyval_tail
          \__ulrichkeyval_split_active:w ##3 \s__ulrichkeyval_nil
            \s__ulrichkeyval_mark \__ulrichkeyval_split_active_auxi:w
            #2 \s__ulrichkeyval_mark \__ulrichkeyval_clean_up_active:w
            \s__ulrichkeyval_stop
          {##1} {##2}
          \__ulrichkeyval_loop_other:nnw {##1} {##2} \s__ulrichkeyval_mark
        }
      \cs_new:Npn \__ulrichkeyval_split_active_auxi:w ##1 \s__ulrichkeyval_stop
        {
          \__ulrichkeyval_split_other:w ##1 \s__ulrichkeyval_nil
            \s__ulrichkeyval_mark \__ulrichkeyval_misplaced_equal_after_active_error:w
            = \s__ulrichkeyval_mark \__ulrichkeyval_split_active_auxii:w
            \s__ulrichkeyval_stop
        }
      \cs_new:Npn \__ulrichkeyval_split_active_auxii:w
          ##1 \s__ulrichkeyval_nil \s__ulrichkeyval_mark \__ulrichkeyval_misplaced_equal_after_active_error:w
          \s__ulrichkeyval_stop \s__ulrichkeyval_mark
        { \__ulrichkeyval_trim:nN { ##1 } \__ulrichkeyval_split_active_auxiii:w }
      \cs_new:Npn \__ulrichkeyval_split_active_auxiii:w ##1 ##2 \s__ulrichkeyval_nil
        {
          \__ulrichkeyval_split_active:w ##2 \s__ulrichkeyval_nil
            \s__ulrichkeyval_mark \__ulrichkeyval_misplaced_equal_in_split_error:w
            #2 \s__ulrichkeyval_mark \__ulrichkeyval_split_active_auxiv:w
            \s__ulrichkeyval_stop
            { ##1 }
        }
      \cs_new:Npn \__ulrichkeyval_split_active_auxiv:w
          ##1 \s__ulrichkeyval_nil \s__ulrichkeyval_mark \__ulrichkeyval_misplaced_equal_in_split_error:w
          \s__ulrichkeyval_stop \s__ulrichkeyval_mark
        {
          \__ulrichkeyval_split_other:w ##1 \s__ulrichkeyval_nil
            \s__ulrichkeyval_mark \__ulrichkeyval_misplaced_equal_in_split_error:w
            = \s__ulrichkeyval_mark \__ulrichkeyval_split_active_auxv:w
            \s__ulrichkeyval_stop
        }
      \cs_new:Npn \__ulrichkeyval_split_active_auxv:w
          ##1 \s__ulrichkeyval_nil \s__ulrichkeyval_mark \__ulrichkeyval_misplaced_equal_in_split_error:w
          \s__ulrichkeyval_stop \s__ulrichkeyval_mark
        { \__ulrichkeyval_trim:nN { ##1 } \__ulrichkeyval_pair:nnnn }
      \cs_new:Npn \__ulrichkeyval_clean_up_active:w
          ##1 \s__ulrichkeyval_nil \s__ulrichkeyval_mark \__ulrichkeyval_split_active_auxi:w \s__ulrichkeyval_stop \s__ulrichkeyval_mark
        {
          \__ulrichkeyval_split_other:w ##1 \s__ulrichkeyval_nil
            \s__ulrichkeyval_mark \__ulrichkeyval_split_other_auxi:w
            = \s__ulrichkeyval_mark \__ulrichkeyval_clean_up_other:w
            \s__ulrichkeyval_stop
        }
      \cs_new:Npn \__ulrichkeyval_split_other_auxi:w ##1 \s__ulrichkeyval_stop
        { \__ulrichkeyval_trim:nN { ##1 } \__ulrichkeyval_split_other_auxii:w }
      \cs_new:Npn \__ulrichkeyval_split_other_auxii:w ##1 ##2 \s__ulrichkeyval_nil
        {
          \__ulrichkeyval_split_other:w ##2 \s__ulrichkeyval_nil
            \s__ulrichkeyval_mark \__ulrichkeyval_misplaced_equal_in_split_error:w
            = \s__ulrichkeyval_mark \__ulrichkeyval_split_other_auxiii:w
            \s__ulrichkeyval_stop
            { ##1 }
        }
      \cs_new:Npn \__ulrichkeyval_split_other_auxiii:w
          ##1 \s__ulrichkeyval_nil \s__ulrichkeyval_mark \__ulrichkeyval_misplaced_equal_in_split_error:w
          \s__ulrichkeyval_stop \s__ulrichkeyval_mark
        { \__ulrichkeyval_trim:nN { ##1 } \__ulrichkeyval_pair:nnnn }
      \cs_new:Npn \__ulrichkeyval_clean_up_other:w
          ##1 \s__ulrichkeyval_nil \s__ulrichkeyval_mark \__ulrichkeyval_split_other_auxi:w \s__ulrichkeyval_stop \s__ulrichkeyval_mark
        {
          \__ulrichkeyval_if_blank:w ##1 \s__ulrichkeyval_nil \s__ulrichkeyval_stop \__ulrichkeyval_blank_true:w
            \s__ulrichkeyval_mark \s__ulrichkeyval_stop \use:n
            { \__ulrichkeyval_trim:nN { ##1 } \__ulrichkeyval_key:nnn }
        }
      \cs_new:Npn \__ulrichkeyval_misplaced_equal_after_active_error:w
          \s__ulrichkeyval_mark ##1 \s__ulrichkeyval_stop \s__ulrichkeyval_mark ##2 \s__ulrichkeyval_nil
          \s__ulrichkeyval_mark ##3 \s__ulrichkeyval_nil ##4 ##5
        {
          \__kernel_msg_expandable_error:nn
            { kernel } { misplaced-equals-sign }
        }
      \cs_new:Npn \__ulrichkeyval_misplaced_equal_in_split_error:w
          \s__ulrichkeyval_mark ##1 \s__ulrichkeyval_stop \s__ulrichkeyval_mark ##2 \s__ulrichkeyval_nil
          ##3 ##4 ##5
        {
          \__kernel_msg_expandable_error:nn
            { kernel } { misplaced-equals-sign }
        }
      \cs_new:Npn \__ulrichkeyval_end_loop_other:w
          \s__ulrichkeyval_tail
          \__ulrichkeyval_split_active:w ##1 \s__ulrichkeyval_nil
          \s__ulrichkeyval_mark \__ulrichkeyval_split_active_auxi:w
          #2 \s__ulrichkeyval_mark \__ulrichkeyval_clean_up_active:w
          \s__ulrichkeyval_stop
          ##2 ##3
          \__ulrichkeyval_loop_other:nnw ##4 \s__ulrichkeyval_mark
        { }
      \cs_new:Npn \__ulrichkeyval_end_loop_active:w
          \s__ulrichkeyval_tail
          \__ulrichkeyval_loop_other:nnw ##1 , \s__ulrichkeyval_tail ,
          \__ulrichkeyval_loop_active:nnw ##2 \s__ulrichkeyval_mark
        { }
    }
  \char_set_catcode_active:n { `\, }
  \char_set_catcode_active:n { `\= }
  \__ulrichkeyval_tmp:NN , =
\group_end:
\cs_new:Npn \__ulrichkeyval_pair:nnnn #1 #2 #3 #4
  {
    \__ulrichkeyval_if_blank:w \s__ulrichkeyval_mark #2 \s__ulrichkeyval_nil \s__ulrichkeyval_stop \__ulrichkeyval_blank_key_error:w
      \s__ulrichkeyval_mark \s__ulrichkeyval_stop
    \exp_not:n { #4 { #2 } { #1 } }
  }
\cs_new:Npn \__ulrichkeyval_key:nnn #1 #2 #3
  {
    \__ulrichkeyval_if_blank:w \s__ulrichkeyval_mark #1 \s__ulrichkeyval_nil \s__ulrichkeyval_stop \__ulrichkeyval_blank_key_error:w
      \s__ulrichkeyval_mark \s__ulrichkeyval_stop
    \exp_not:n { #2 { #1 } }
  }
\cs_new:Npn \__ulrichkeyval_if_empty:w #1 \s__ulrichkeyval_mark \s__ulrichkeyval_stop { }
\cs_new:Npn \__ulrichkeyval_if_blank:w \s__ulrichkeyval_mark #1 { \__ulrichkeyval_if_empty:w \s__ulrichkeyval_mark }
\cs_new:Npn \__ulrichkeyval_if_recursion_tail:w \s__ulrichkeyval_mark #1 \s__ulrichkeyval_tail { }
\cs_new:Npn \__ulrichkeyval_blank_true:w \s__ulrichkeyval_mark \s__ulrichkeyval_stop \use:n #1 #2 #3 { }
\cs_new:Npn \__ulrichkeyval_blank_key_error:w \s__ulrichkeyval_mark \s__ulrichkeyval_stop \exp_not:n #1
  {
    \__kernel_msg_expandable_error:nn
      { kernel } { blank-key-name }
  }
\group_begin:
  \cs_set_protected:Npn \__ulrichkeyval_tmp:n #1
    {
      \cs_new:Npn \__ulrichkeyval_trim:nN ##1
        {
          \__ulrichkeyval_trim_auxi:w
            ##1
            \s__ulrichkeyval_nil
            \s__ulrichkeyval_mark #1 { }
            \s__ulrichkeyval_mark \__ulrichkeyval_trim_auxii:w
            \__ulrichkeyval_trim_auxiii:w
            #1 \s__ulrichkeyval_nil
            \__ulrichkeyval_trim_auxiv:w
          \s__ulrichkeyval_stop
        }
      \cs_new:Npn \__ulrichkeyval_trim_auxi:w ##1 \s__ulrichkeyval_mark #1 ##2 \s__ulrichkeyval_mark ##3
        {
          ##3
          \__ulrichkeyval_trim_auxi:w
          \s__ulrichkeyval_mark
          ##2
          \s__ulrichkeyval_mark #1 {##1}
        }
      \cs_new:Npn \__ulrichkeyval_trim_auxii:w \__ulrichkeyval_trim_auxi:w \s__ulrichkeyval_mark \s__ulrichkeyval_mark ##1
        {
          \__ulrichkeyval_trim_auxiii:w
          ##1
        }
      \cs_new:Npn \__ulrichkeyval_trim_auxiii:w ##1 #1 \s__ulrichkeyval_nil ##2
        {
          ##2
          ##1 \s__ulrichkeyval_nil
          \__ulrichkeyval_trim_auxiii:w
        }
      \cs_new:Npn \__ulrichkeyval_trim_auxiv:w \s__ulrichkeyval_mark ##1 \s__ulrichkeyval_nil ##2 \s__ulrichkeyval_stop ##3
        { ##3 { ##1 } }
    }
  \__ulrichkeyval_tmp:n { ~ }
\group_end:

\cs_new:Nn \MyStuff_ProcessInCaseOnlyKey:nnn {
  This~is~the~first~argument~of~\texttt{\token_to_str:N\MyStuff_ProcessAsKeyval:nnn}:~#1.\\
  This~is~the~second~argument~of~\texttt{\token_to_str:N\MyStuff_ProcessAsKeyval:nnn}:~#2.\\
  This~is~the~key~which~was~passed~on~by~the~keyval~parser:~#3.\par
}
\cs_new:Nn \MyStuff_ProcessInCaseKeyAndValue:nnnn {
  This~is~the~first~argument~of~\texttt{\token_to_str:N\MyStuff_ProcessAsKeyval:nnn}:~#1.\\
  This~is~the~second~argument~of~\texttt{\token_to_str:N\MyStuff_ProcessAsKeyval:nnn}:~#2.\\
  This~is~the~key~which~was~passed~on~by~the~keyval~parser:~#3.\\
  This~is~the~value~which~was~passed~on~by~the~keyval~parser:~#4.\par
}
\cs_new:Nn \MyStuff_ProcessAsKeyval:nnn {
  \ulrichkeyval_parse:nnn
    { \MyStuff_ProcessInCaseOnlyKey:nnn {#1}{#2} } 
    { \MyStuff_ProcessInCaseKeyAndValue:nnnn {#1}{#2} }
    {#3}
}
\cs_new_eq:NN \MyStuff \MyStuff_ProcessAsKeyval:nnn
\ExplSyntaxOff

\begin{document}
\MyStuff{A}{B}{key,key=val,val,val=key}
\end{document}
1

You should have all arguments expressed:

\MyStuff_ProcessInCaseOnlyKey:nnn {#1}{#2}{##1}

and

\MyStuff_ProcessInCaseKeyAndValue:nnnn {#1}{#2}{##1}{##2}

I mean really: there is no rule why the key and value should be passed as the last arguments. And the code is clearer.

\documentclass{article}

\ExplSyntaxOn

\cs_new_protected:Nn \ulrich_keyval_parse:nnn
 {
  \cs_set:Nn \__ulrich_keyval_parse_single:n { #1 }
  \cs_set:Nn \__ulrich_keyval_parse_single:nn { #2 }
  \keyval_parse:NNn \__ulrich_keyval_parse_single:n \__ulrich_keyval_parse_single:nn { #3 }
 }

\ulrich_keyval_parse:nnn
 { \iow_term:n { key~is~#1 } }
 { \iow_term:n { key~is~#1;~value~is~#2 } }
 {key,key=val,val,val=key}

\begin{document}

\cs_new_protected:Nn \MyStuff_ProcessInCaseOnlyKey:nnn
 {
  This~is~the~first~argument~of~\texttt{\token_to_str:N\MyStuff_ProcessAsKeyval:nnn}:~#1.\\
  This~is~the~second~argument~of~\texttt{\token_to_str:N\MyStuff_ProcessAsKeyval:nnn}:~#2.\\
  This~is~the~key~which~was~passed~on~by~the~keyval~parser:~#3.
 }
\cs_new_protected:Nn \MyStuff_ProcessInCaseKeyAndValue:nnnn
 {
  This~is~the~first~argument~of~\texttt{\token_to_str:N\MyStuff_ProcessAsKeyval:nnn}:~#1.\\
  This~is~the~second~argument~of~\texttt{\token_to_str:N\MyStuff_ProcessAsKeyval:nnn}:~#2.\\
  This~is~the~key~which~was~passed~on~by~the~keyval~parser:~#3.\\
  This~is~the~value~which~was~passed~on~by~the~keyval~parser:~#4.
 }
\cs_new:Nn \MyStuff_ProcessAsKeyval:nnn
 {
  \ulrich_keyval_parse:nnn
   { \MyStuff_ProcessInCaseOnlyKey:nnn {#1}{#2}{##1} } 
   { \MyStuff_ProcessInCaseKeyAndValue:nnnn {#1}{#2}{##1}{##2} }
   {#3}
 }

\MyStuff_ProcessAsKeyval:nnn { A } { B } { key,key=val }

\end{document}
2
  • 1
    Well, the only reason why the key and value should be passed as last arguments is analogy to \tl_map_tokens:nn, and the fact that this can be done fully expandable without a big performance hit.
    – Skillmon
    Dec 8 '20 at 8:09
  • Assume I want to use \MyStuff_ProcessAsKeyval:nnn 's 1st and 2nd argument for passing on some inline-code without the need for hash-doubling. With Skillmon's approach I can do this. With yours I can't because hash-doubling is needed for distinguishing arguments of \__ulrich_keyval_parse_single:n/\__ulrich_keyval_parse_single:nn from arguments of the inline-code. Dec 8 '20 at 22:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.