4

Given a token list e.g. \ArgumentSpecification containing some argument specification e.g. oom...

How can I count the resulting number of arguments?

For instance in this case the output would be 3 which can be easily counted with e.g. \tl_count; nevertheless in other more complex cases (+, O-type argument, etc.) \tl_count is incorrect.


Example use case -- if I want to write some NewDocumentCommand wrapper that somehow "preprocesses" the grabbed arguments, but it needs to know how many arguments are available:

%! TEX program = lualatex
\documentclass{article}
\begin{document}

\ExplSyntaxOn \NewDocumentCommand __test {om} { #1 #2 }

\NewDocumentCommand \WrapperNewDocumentCommand {m} { \NewDocumentCommand __inner {#1} { \tl_set:Nn __argi {##1} %\tl_set:Nn __argii {##2} % only run this if #1 contains two arguments %\tl_set:Nn __argiii {##3} % only run this if #1 contains two arguments } }

\WrapperNewDocumentCommand {m}

__inner {abc}

\ExplSyntaxOff

\end{document}

In the example above it works if there's exactly 1 argument; however I'd want the inner function to conditionally set \__argi, \__argii etc. depends on how many arguments there are.

Remark: The functional package source code does something similar https://github.com/lvjr/functional/blob/main/functional.sty#L154 ; however it hard codes \tl_count.

user202729
  • 7,143
  • 1
    There is no public interface (not in the kernel, at least) to do that. A while ago I needed to count the arguments of a command to pretty-print it using \ShowCommand, som I implemented something like that. After \__cmd_split_signature:n { <signature> } the int variable \l__cmd_current_arg_int will hold the number of arguments in <signature> plus one. Of course you should not use those names in your code, since they are private to the kernel, but you can take inspiration in the implementation to write your own. – Phelype Oleinik Jul 03 '22 at 02:01
  • Actually thinking about it, it might be possible to "try-catch error" with \batchmode → iterate over number of arguments → check error etc. Not that I know any way to actually check whether any error happened though, and it might be "undefined behavior" internally. – user202729 Jul 03 '22 at 02:04
  • I'd advise against the try-catch method: TeX can't really catch errors cleanly as other languages do, and you'd need very careful cleanup to avoid issues later (stray tokens, etc.). Besides, after 100 errors TeX would bail out, and the exit status would be nonzero. I'm writing something with the approach I mentioned above – Phelype Oleinik Jul 03 '22 at 02:08
  • Not sure if the parameter text of \<cmd> code is a reliable source. – muzimuzhi Z Jul 03 '22 at 02:38
  • 1
    @muzimuzhiZ It is, but it's internal, so... :) – Phelype Oleinik Jul 03 '22 at 02:41

1 Answers1

4

When you do \ShowCommand in a command defined with \NewDocumentCommand, LaTeX counts the number of arguments in the signature of the command and splits for pretty-printing. For example:

\NewDocumentCommand \foo { e{^_} E{^_}{{a}{b}} >{\SplitArgument}+O{} m }
  { do stuff }
\ShowCommand \foo

prints:

> \foo=document command:
  #1:e^
  #2:e_
  #3:E^{a}
  #4:E_{b}
  #5:>{\SplitArgument }+O{}
  #6:m
-> do stuff .

However, that is done by the internal \__cmd_split_signature:n, which counts the number of arguments and stores them in a token list for the pretty-printing, and there isn't a public interface for that. But you can copy the implementation and do something similar.

Here's an expandable version of that which just counts the arguments and returns the count (a shameless ripoff of the kernel command, with some limbs cut off because it just needs to count the arguments :)

\ExplSyntaxOn
\cs_new:Npn \usersixdigits_count_signature:n #1
  {
    \int_eval:n
      { 0 \__usersixdigits_split_signature_loop:Nw #1 \q_recursion_tail \q_recursion_stop }
  }
\cs_new:Npn \__usersixdigits_split_signature_loop:Nw #1
  {
    \quark_if_recursion_tail_stop:N #1
    \tl_if_exist:cTF { c__usersixdigits_show_type_#1_tl }
      {
        \use:c
          {
            __usersixdigits_show_
            \if_case:w \tl_use:c { c__usersixdigits_show_type_#1_tl } \exp_stop_f:
              delim \or: delims \or: delims_opt \or: opt \or:
              e \or: E \or: prefix \or: processor \fi: :Nw
          } #1
      }
      { +1 \__usersixdigits_split_signature_loop:Nw }
  }
\cs_set:Npn \__usersixdigits_tmp:w #1 #2
  {
    \quark_if_nil:nF {#1}
      { \tl_const:cn { c__usersixdigits_show_type_#1_tl } {#2} \__usersixdigits_tmp:w }
  }
\__usersixdigits_tmp:w t0 r1 d1 R2 D2 O3 e4 E5 +6 !6 >7 \q_nil \q_nil
\cs_new:Npn \__usersixdigits_show_delim:Nw #1 #2
  { +1 \__usersixdigits_split_signature_loop:Nw }
\cs_new:Npn \__usersixdigits_show_delims:Nw #1 #2 #3
  { +1 \__usersixdigits_split_signature_loop:Nw }
\cs_new:Npn \__usersixdigits_show_delims_opt:Nw #1 #2 #3 #4
  { +1 \__usersixdigits_split_signature_loop:Nw }
\cs_new:Npn \__usersixdigits_show_opt:Nw #1 #2
  { +1 \__usersixdigits_split_signature_loop:Nw }
\cs_new:Npn \__usersixdigits_show_e:Nw #1 #2
  { + \tl_count:n {#2} \__usersixdigits_split_signature_loop:Nw }
\cs_new:Npn \__usersixdigits_show_E:Nw #1 #2 #3
  { + \tl_count:n {#2} \__usersixdigits_split_signature_loop:Nw }
\cs_new:Npn \__usersixdigits_show_prefix:Nw #1
  { \__usersixdigits_split_signature_loop:Nw }
\cs_new:Npn \__usersixdigits_show_processor:Nw #1 #2
  { \__usersixdigits_split_signature_loop:Nw }

\typeout { \usersixdigits_count_signature:n { m } } \typeout { \usersixdigits_count_signature:n { +m } } \typeout { \usersixdigits_count_signature:n { +!m } } \typeout { \usersixdigits_count_signature:n { +!O{x} } } \typeout { \usersixdigits_count_signature:n { >{\SplitArgument}+!O{x} } }

\typeout { \usersixdigits_count_signature:n { e{^} E{^}{{a}{b}} >{\SplitArgument}+O{} m } }

\stop