0

I'm writing a package and I need to get the number of arguments in the macro. Say, user has to define a macros

\def\usermacro#1#2....

Then he calls

\dosomestuffdefinedinmypackage

and this command takes \usermacro and uses it in a way depending on the number of arguments it has, so I need to find a way to know it.

Preferably without using another package, I what to understand how does it work instead of learning a spell. Thanks!

Alex
  • 43
  • 4
    see https://tex.stackexchange.com/q/305806/2388 – Ulrike Fischer Oct 07 '20 at 19:54
  • there is another question on site in general if you allow all the definitions possible it's not really feasible to do this in tex. – David Carlisle Oct 07 '20 at 19:54
  • oh I was just about to search for it but @UlrikeFischer had a head start – David Carlisle Oct 07 '20 at 19:55
  • @UlrikeFischer Well, this relies on changing \newcommand... One could build something with \meaning. – Skillmon Oct 07 '20 at 19:56
  • 3
    @Skillmonlikestopanswers.xyz if you know the original macro was defined with newcommand yes but in general with delimited arguments and catcode other # etc you really can't tell. Also typically you need the effective use eg you want to know \section is used with arguments even though \meaning will tell you it has none. – David Carlisle Oct 07 '20 at 20:05
  • @DavidCarlisle I know... But at least in the simple case we can build something. – Skillmon Oct 07 '20 at 20:06
  • The task is not trivial at all. E.g., arguments might be delimited by hashes that are not of catcode 6: \expandafter\def\expandafter\testA\expandafter#\expandafter1\string#2{#1/blabla}\typeout{\meaning\testA}\def\testB#1#2{#1/blabla}\typeout{\meaning\testB}\typeout{meanings \ifx\testA\testB are equal\else differ\fi}\stop Here the macros \testA and \testB do exactly the same but are considered different: \catcode`\Y=6\relax\def\testA#1#2{}\def\testBY1Y2{}\typeout{\meaning\testA}\typeout{\meaning\testB}\typeout{meanings \ifx\testA\testB are equal\else differ\fi}\stop – Ulrich Diez Dec 23 '21 at 12:49
  • Interestingly parameter-text and definition-text containing implicit parameter-characters doesn't matter: \let\para=#\def\testA#1#2{arg1: #1 arg2: #2}\def\testB\para1\para2{arg1: \para1 arg2: \para2}\typeout{\testA{1}{2}}\typeout{\testB{1}{2}}\typeout{\meaning\testA}\typeout{\meaning\testB}\typeout{meanings \ifx\testA\testB are equal\else differ\fi}\stop – Ulrich Diez Dec 23 '21 at 13:03

2 Answers2

4

The following defines the macro \getnumargs which takes two arguments. The first is an arbitrary macro, the second the macro which should store the result.

It'll store the number of arguments taken by the first macro in the second.

Caveat: This only gets how many arguments exactly that macro will take, not more not less. It'll also fail if the macro takes delimited arguments. A few examples where it is technically right, but most likely doesn't return what you had in mind are included.

\documentclass[]{article}

\makeatletter \edef\getnumargs@ifmacro {% \noexpand\expandafter\noexpand\getnumargs@ifmacro@ \noexpand\getnumargs@meaning\relax\noexpand\getnumargs@ifmacro@true \detokenize{macro:}\relax\noexpand\getnumargs@ifmacro@false } \def\getnumargs@ifmacro@true#1\getnumargs@ifmacro@false#2{#2} \expandafter\def\expandafter\getnumargs@ifmacro@ \expandafter#\expandafter1\detokenize{macro:}#2\relax {} \newcommand*\getnumargs@ifmacro@false[1] {% \PackageError{Alex}{}{Not a macro!}% \def\getnumargs@num{0}% } \newcommand\getnumargs[2] {% \begingroup \edef\getnumargs@meaning{\meaning#1}% \getnumargs@ifmacro {% \expandafter\getnumargs@settmp\expandafter{\getnumargs@meaning}% \getnumargs@getnum }% \expandafter \endgroup \expandafter\def\expandafter#2\expandafter{\getnumargs@num}% } \newcommand\getnumargs@settmp[1] {% \expandafter\def\expandafter\getnumargs@tmp \getnumargs@getargs#1\relax##1##2##3\relax{##2}% } \expandafter\def\expandafter\getnumargs@getargs \expandafter#\expandafter1\detokenize{macro:}#2->#3\relax {#2} \edef\getnumargs@getnum {% \edef\noexpand\getnumargs@num {% \noexpand\the\noexpand\numexpr \noexpand\getnumargs@tmp \string#1\string#2\string#3% \string#4\string#5\string#6% \string#7\string#8\string#9% .{10}\relax -1\relax }% }

\makeatother

\newcommand\noargs{} \newcommand\onearg [1]{} \newcommand\twoargs [2]{} \newcommand\threeargs[3]{} \newcommand\fourargs [4]{} \newcommand\fiveargs [5]{} \newcommand\sixargs [6]{} \newcommand\sevenargs[7]{} \newcommand\eightargs[8]{} \newcommand\nineargs [9]{}

\begin{document} \getnumargs\section\tmp \tmp

\getnumargs\texttt\tmp \tmp

\getnumargs\emph\tmp \tmp

\getnumargs\noargs\tmp \tmp

\getnumargs\onearg\tmp \tmp

\getnumargs\twoargs\tmp \tmp

\getnumargs\threeargs\tmp \tmp

\getnumargs\fourargs\tmp \tmp

\getnumargs\fiveargs\tmp \tmp

\getnumargs\sixargs\tmp \tmp

\getnumargs\sevenargs\tmp \tmp

\getnumargs\eightargs\tmp \tmp

\getnumargs\nineargs\tmp \tmp \end{document}

Skillmon
  • 60,462
  • Sorry for no reply. I'm trying to reintegrate the code to understand which part does what... – Alex Oct 08 '20 at 19:20
  • @Alex If you have any questions leave a comment. – Skillmon Oct 10 '20 at 20:25
  • @Skillmon It also fails with undelimited arguments if a parameter-character differing from # is used: \catcode`\Y=6\relax\def\nineargsbY1Y2Y3Y4Y5Y6Y7Y8Y9{}\catcode`\Y=11\relax\getnumargs\nineargsb\tmp\typeout{\meaning\tmp}\stop But this can be neglected. Who would do that in daily life for purposes other than producing edge cases. :-) – Ulrich Diez Dec 23 '21 at 13:30
  • @UlrichDiez correct :) – Skillmon Dec 23 '21 at 15:08
2

Inspired by user202729's answer, here is a much simpler coding:

\documentclass{article}

\ExplSyntaxOn

\NewExpandableDocumentCommand{\getnumargs}{m} { \int_eval:n { \str_count:e { \cs_argument_spec:N #1 } / 2 } } \cs_generate_variant:Nn \str_count:n { e }

\ExplSyntaxOff

\newcommand\noargs{} \newcommand\onearg [1]{} \newcommand\twoargs [2]{} \newcommand\threeargs[3]{} \newcommand\fourargs [4]{} \newcommand\fiveargs [5]{} \newcommand\sixargs [6]{} \newcommand\sevenargs[7]{} \newcommand\eightargs[8]{} \newcommand\nineargs [9]{}

\begin{document}

\getnumargs\texttt

\getnumargs\emph

\getnumargs\noargs

\getnumargs\onearg

\getnumargs\twoargs

\getnumargs\threeargs

\getnumargs\fourargs

\getnumargs\fiveargs

\getnumargs\sixargs

\getnumargs\sevenargs

\getnumargs\eightargs

\getnumargs\nineargs

\texttt{\edef\test{\getnumargs\nineargs}\meaning\test}

\end{document}

The number of required expansion steps is greater than two, but that's not really important. The idea is that if the number of steps is one, good, we can use o as argument specifier if needed; otherwise use e.

enter image description here

egreg
  • 1,121,712