TransWikia.com

expl3 - keyval_parse:NNn - why only functions which absorb exactly 1 respective 2 arguments?

TeX - LaTeX Asked on May 14, 2021

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:NMyStuff_ProcessAsKeyval:nnn}:~exp_args:Notexttt{tl_to_str:n{#1}}.
  This~is~the~second~argument~of~texttt{token_to_str:NMyStuff_ProcessAsKeyval:nnn}:~exp_args:Notexttt{tl_to_str:n{#2}}.
  This~is~the~key~which~was~passed~on~by~the~keyval~parser:~exp_args:Notexttt{tl_to_str:n{#3}}.
}
cs_new:Nn MyStuff_ProcessInCaseKeyAndValue:nnnn {
  This~is~the~first~argument~of~texttt{token_to_str:NMyStuff_ProcessAsKeyval:nnn}:~exp_args:Notexttt{tl_to_str:n{#1}}.
  This~is~the~second~argument~of~texttt{token_to_str:NMyStuff_ProcessAsKeyval:nnn}:~exp_args:Notexttt{tl_to_str:n{#2}}.
  This~is~the~key~which~was~passed~on~by~the~keyval~parser:~exp_args:Notexttt{tl_to_str:n{#3}}.
  This~is~the~value~which~was~passed~on~by~the~keyval~parser:~exp_args:Notexttt{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.

2 Answers

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:NMyStuff_ProcessAsKeyval:nnn}:~#1.
  This~is~the~second~argument~of~texttt{token_to_str:NMyStuff_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:NMyStuff_ProcessAsKeyval:nnn}:~#1.
  This~is~the~second~argument~of~texttt{token_to_str:NMyStuff_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}

Correct answer by Skillmon on May 14, 2021

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:NMyStuff_ProcessAsKeyval:nnn}:~#1.
  This~is~the~second~argument~of~texttt{token_to_str:NMyStuff_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:NMyStuff_ProcessAsKeyval:nnn}:~#1.
  This~is~the~second~argument~of~texttt{token_to_str:NMyStuff_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}

Answered by egreg on May 14, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP