# Is there a reliable way to get the number of arguments of a command?

While pondering over a test to distinguish two versions of a command I came up with the following example:

\documentclass{article}
\begin{document}
\def\testA#1{\#2->blabla}
\typeout{\meaning\testA}
\def\testB#1->\#2{blabla}
\typeout{\meaning\testB}
\end{document}


The output in the log is

macro:#1->\#2->blabla
macro:#1->\#2->blabla


So it is imho not possible to use \meaning to count the number of arguments of a command. Is there some other way (without executing the command)?

• If there is just one -> you are sure that the parameter text is what's before it; in the case there's more than one, try splitting at each of them and to rebuild the macro with \scantokens and compare with the original (via \ifx). With some luck you'll be able to get away, but of course catcodes get into the way. – egreg Apr 22 '16 at 16:44
• My impression is that the problem is not solvable in full generality. In regexpatch I have a “rebuild” based test; if it's not passed, the patching macros signal failure and don't touch the command. – egreg Apr 22 '16 at 16:58
• you can tell your examples have 1 parameter by discounting the \#2 as you get macro:#1->\#2->blabla but then macro:#1->Y#2->blabla if \escapechar=Y but @egreg's example (if corrected to \edef\hm{\string#}\let\xp\expandafter \xp\def\xp\x\xp#\xp1\hm2{Bummer!} is a bit harder – David Carlisle Apr 22 '16 at 19:51
• @UlrikeFischer yes but both of them have 1 parameter the fact that \#2 changes to Y#2 shows that that is the csname starting with # not a catcode 12 \  followed by #2 for a second parameter – David Carlisle Apr 22 '16 at 20:18
• Could you use the l3regex package to expand the macro, search for all of the #1, #2,..., strip out the #'s and then return the largest integer you find? Personally, expl3 scares me so I don't know if this is feasible. – Andrew Aug 5 '16 at 3:54


Note though, that packages loaded following the preamble setup will be processed with this information remembered. Thus, my MWE can and does tell me about the arguments of the loaded stackengine package macros.

EDITED to work with \newcommand* invocations, as well.



EDITED to show it also works with \csname definitions, too.

\documentclass{article}
\makeatletter
{\gdef\StarVer{T}\newcommandaux}%
{\gdef\StarVer{F}\newcommandaux}%
}
\def\newcommandaux#1{\saverootname{#1}\futurelet\testchar\MaybeHasArgsCom}
\def\saverootname#1{\xdef\Mname{\expandafter\@gobble\string#1}}
%
\def\MaybeHasArgsCom{\ifx[\testchar \let\next\HasArgsCom
\else%
\expandafter\xdef\csname\Mname ARGS\endcsname{0}%
\expandafter\xdef\csname\Mname OPTARGS\endcsname{0}%
\if T\StarVer%
\let\next\HasNoArgsStarCom%
\else%
\let\next\HasNoArgsCom%
\fi%
\fi%
\next}
\def\HasArgsCom[#1]{%
\xdef\SaveArgs{#1}%
\expandafter\xdef\csname\Mname ARGS\endcsname{#1}%
\futurelet\testchar\MaybeHasOptArgCom%
}
\def\MaybeHasOptArgCom{%
\ifx[\testchar%
\expandafter\xdef\csname\Mname OPTARGS\endcsname{1}%
\if T\StarVer%
\let\next\HasOptArgStarCom%
\else
\let\next\HasOptArgCom%
\fi%
\else%
\expandafter\xdef\csname\Mname OPTARGS\endcsname{0}%
\if T\StarVer%
\let\next\HasNoOptArgStarCom%
\else
\let\next\HasNoOptArgCom%
\fi%
\fi%
\expandafter\next\expandafter[\SaveArgs]}
%
\long\def\HasOptArgCom[#1][#2]#3{%
\expandafter\SVnewcommand\csname\Mname\endcsname[#1][#2]{#3}%
}
\def\HasOptArgStarCom[#1][#2]#3{%
\expandafter\SVnewcommand\expandafter*\csname\Mname\endcsname[#1][#2]{#3}%
}
\long\def\HasNoOptArgCom[#1]#2{%
\expandafter\SVnewcommand\csname\Mname\endcsname[#1]{#2}%
}
\long\def\HasNoOptArgStarCom[#1]#2{%
\expandafter\SVnewcommand\expandafter*\csname\Mname\endcsname[#1]{#2}%
}
\long\def\HasNoArgsCom#1{%
\expandafter\SVnewcommand\csname\Mname\endcsname{#1}%
}
\long\def\HasNoArgsStarCom#1{%
\expandafter\SVnewcommand\expandafter*\csname\Mname\endcsname{#1}%
}
%
\makeatother
\parskip 1em
\usepackage[T1]{fontenc}
\usepackage{lmodern,stackengine}
\begin{document}
\newcommand*\XYZ{xyz}
\string\XYZ, whose value is \XYZ,
has \XYZARGS{} arguments
and \XYZOPTARGS{} optional arguments.

\newcommand\PDQ[1]{p#1d#1q}
\detokenize{\PDQ{123\par123}}, whose value is \PDQ{123\par123}, has \PDQARGS{} argument(s)
and \PDQOPTARGS{} optional arguments.

\newcommand*\ggg[2]{g#1g#2gx}
\string\ggg\{1\}\{2\}, whose value is \ggg{1}{2}, has \gggARGS{} argument(s)
and \gggOPTARGS{} optional arguments.

\newcommand\mytest[1][Q]{#1}
\string\mytest, whose value is \mytest, has \mytestARGS{} argument(s)
and \mytestOPTARGS{} optional arguments.

\string\mytest[X], whose value is \mytest[X], has \mytestARGS{} argument(s)
and \mytestOPTARGS{} optional arguments.

\newcommand\othertest[3][I]{([#1]#2,#3)}
\string\othertest\{j\}\{k\}, whose value is \othertest{j}{k}, has
\othertestARGS{} argument(s) and \othertestOPTARGS{} optional arguments.

\string\othertest[i]\{j\}\{k\}, whose value is \othertest[i]{j}{k}, has
\othertestARGS{} argument(s) and \othertestOPTARGS{} optional arguments.

\expandafter\newcommand\csname X1\endcsname[1]{*#1*}
\string\csname X1\string\endcsname\{OOO\}, whose value is
\csname X1\endcsname{OOO}, has \csname X1ARGS\endcsname{} argument(s) and
\csname X1OPTARGS\endcsname{} optional arguments.

\string\stackengine{} has \stackengineARGS{} arguments

\string\stackon{} has \stackonARGS{} arguments and \stackonOPTARGS{} optional argument.

\end{document}



\def\newcommandaux#1{%
\saverootname{#1}%
\expandafter\xdef\csname\Mname STAR\endcsname{\StarVer}%
\futurelet\testchar\MaybeHasArgsCom%
}



• Woudln't you just need to check only until the first optional? I.e., check for *, grab the command to define, and then see the following optional. And then save that number. – Manuel Apr 5 '17 at 12:56
• @Manuel If i understand the thrust of your question, one could just check for the 1st optional, which would indicate, for example, that \stackon has 3 arguments. But because of the use of square brackets for optional argument invocation, it is also very useful to know if \stackon has any optional argument usage. And to do this, I need to check if the \newcommand invocation uses two sets of optional arguments. – Steven B. Segletes Apr 5 '17 at 13:06
• Ah, okey, didn't read the answer but seemed to long. So you are not only saving the number of arguments but also the information about if the command has an optional argument. – Manuel Apr 5 '17 at 13:07
• @Manuel Correct. And I am considering on whether to also save whether the \newcommand was invoked with a * or not. That is potentially useful. – Steven B. Segletes Apr 5 '17 at 13:09
• With xparse it's easy easy easy :) A few ifs and you define anything that is necessary, and then put the original \newcommand` there. – Manuel Apr 5 '17 at 13:15