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). Commented Dec 6, 2020 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. Commented Dec 6, 2020 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? Commented Dec 6, 2020 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
    Commented Dec 6, 2020 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
    Commented Dec 7, 2020 at 21:12

2 Answers 2

4

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
    Commented Dec 8, 2020 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. Commented Dec 8, 2020 at 22:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .