[Tex/LaTex] Commands that may take a variable number of arguments

macrosrecursion

I have a situation where I want to define a command that takes a variable number of arguments, where the number of arguments is known programmatically via a \count, and process the parameters in some way (say as if they are a list).

As an example, say I'd like to output the parameters as a comma-separated list.

\newcommand{\makecsv}[N]{#1, #2, ..., #N}

The code that I've come up with to do this kind of operation (in a generic-ish way) essentially takes a command, \csv and expands it recursively N times. \csv needs to know how to continue the recursion, and has some state that I'd like to thread through the recursion (rather than using \global).

\documentclass{report}
\usepackage{etoolbox}

\makeatletter
\newcommand{\ifzero}[3]{%
  % #1: count
  % #2: state for #3
  % #3: macro to expand to
  % - should take at least 2 parameters
  % - ##1: count threaded through
  % - ##2: macro state threaded through
  \ifnum\c > 0
    \def\tmp@f##1##2##3{##1{##2}{##3}}%
    \advance#1 -1%
  \else
    \def\tmp@f##1##2##3{)}% note closeparen here (could be param)
  \fi
  \tmp@f{#3}{#1}{#2}%
}
\makeatother

\newcommand{\csv}[3]{
  % #1: count
  % #2: separator state
  % #3: string to concat
  % 
  #2#3\ifzero{#1}{, }{\csv}%
}

\newcommand{\makecsv}[1]{%
  \ifzero{#1}{}{\csv}%
}

\makeatletter
\newcommand{\decl}[3]{%
  % #1: decl id
  % #2: decl symbol
  % #3: # params
  \csgdef{decl@#1}{#2}%
  \global\expandafter\newcount\csname decl@#1@nparams\endcsname%
  \csuse{decl@#1@nparams} #3\relax%
}

\newcommand{\usedecl}[1]{%
  \newcount\c
  \c \the\csuse{decl@#1@nparams}
  \csuse{decl@#1}(\makecsv{\c}%
}
\makeatother

% declare some interface routines
\decl{foo}{FOO}{3}
\decl{bar}{BAR}{4}

\begin{document}
\usedecl{foo}{p1}{p2}{p3}\par
\usedecl{bar}{p1}{p2}{p3}{p4}\par
\end{document}

Is this a reasonable thing to do in 2e, or is there some sort of standard approach to this that is normally used?

Edit 1

It seems like my original MWE wasn't adequate to describe why someone might want this. I've updated the MWE with a use case. \decl allows authors to declaratively define a C-style function, and \usedecl allows the author to generate a use of the function, with its parameters bound to specific arguments.

This is similar enough to what I'm doing that it should help motivate the example.

Best Answer

As commented, here a solution that uses \@ifnextchar. I also implemented checks against too many or too few arguments (or why are they provided by the user?).

The \@ifnextchar(or its “very internal” big brother \kernel@ifnextchar) skips spaces which results in removed spaces in the third and fourth example.

Code

\documentclass{report}
\usepackage{etoolbox}
\makeatletter
\newcommand*{\decl}[3]{%
  % #1: decl id
  % #2: decl symbol
  % #3: # params
  \csdef{decl@symbol@#1}{#2}%
  \expandafter\newcount\csname c@decl@params@#1\endcsname
  \csuse{c@decl@params@#1}=#3\relax
}
\newcount\decl@params@check
\newcommand*{\usedecl}[1]{%
  \def\decl@name{#1}%
  \edef\decl@params{\the\csuse{c@decl@params@#1}}%
  \def\decl@symbol{\csuse{decl@symbol@#1}}%
  \decl@params@check=\z@
  \let\decl@list\@gobble % the \@gobble removes the first , (expandable)
  \def\decl@next{\kernel@ifnextchar\bgroup\use@decl\use@decl@finish}%
  \decl@next
}
\newcommand*{\use@decl}[1]{%
  \advance\decl@params@check\@ne
  \expandafter\ifnum\the\decl@params@check>\decl@params\relax % too many!
    \PackageWarning{decl}{You have used more params than the \decl@name\space function expected!
                                                    I ignore this (and any following) param, ok?}% but insert the extra argument anyway?!
     \def\decl@next{\use@decl@finish{#1}}% the extra pair of braces {} keeps '#1' local as it is in the input stream
  \else
    \expandafter\def\expandafter\decl@list\expandafter{\decl@list\decl@list@sep#1}%
  \fi
  \decl@next
}
\newif\ifuse@decl@message
\newcommand*{\use@decl@finish}{%
  \ifnum\decl@params@check<\decl@params\relax % too few!
    \expandafter\@firstoftwo
  \else
    \expandafter\@secondoftwo
  \fi
  {%
    \ifuse@decl@message\else
      \PackageWarning{decl}{You have used fewer params than the \decl@name\space function expected! I'm filling up with '??'!}%
      \use@decl@messagetrue
    \fi
    \use@decl{??}}
  {%
    \decl@symbol\decl@list@start\decl@list\decl@list@end
    \use@decl@messagefalse
  }%
}
\newcommand*{\setdeclstart}[1]{\def\decl@list@start{#1}}
\newcommand*{\setdeclend}[1]{\def\decl@list@end{#1}}
\newcommand*{\setdeclsep}[1]{\def\decl@list@sep{#1}}
\makeatother

\setdeclstart{(}
\setdeclend{)}
\setdeclsep{, }

% declare some interface routines
\decl{foo}{FOO}{3}
\decl{bar}{BAR}{4}

\begin{document}
given $P$, $Q$ and $R$ such \dots\ that $\usedecl{foo}{P}{Q}{R}$ results in \dots\par
given P, Q and R such \dots\ that \usedecl{foo}{P}{Q}{R}\ results in \dots\par
\usedecl{foo}{p1}{p2}{p3}\par
\usedecl{bar}{p1}{p2}{p3}{p4}\par
\usedecl{bar}{p1} foo\par
\usedecl{foo}{p1}{p2}{p3} {p4}\par
\end{document}

Output

enter image description here

Related Question