Commands that may take a variable number of arguments

If you're willing to peek ahead, you can check whether there's "another argument" and keep gobbling them on the fly:

enter image description here

\documentclass{article}
\usepackage{etoolbox}% http://ctan.org/pkg/etoolbox

\makeatletter
\newcommand{\newdecl}[2]{\csgdef{decl@#1}{#2}}% Creates a declaration
\newcommand{\csvdel}{}% Delimiter used in CSV representation
\newcommand{\newusedecl}[2][,]{% Use a declaration
  \renewcommand{\csvdel}{\renewcommand{\csvdel}{#1\,}}% Delay \csvdel one cycle.
  \csname decl@#2\endcsname(\checknextarg}
\newcommand{\checknextarg}{\@ifnextchar\bgroup{\gobblenext}{}}% Check if another "argument" exists
\newcommand{\gobblenext}[1]{\csvdel#1\@ifnextchar\bgroup{\gobblenext}{)}}% Gobble next "argument"
\makeatother

% declare some interface routines
\newdecl{foo}{FOO}
\newdecl{bar}{BAR}

\begin{document}

\newusedecl{foo}{p1}{p2}{p3}\par
\newusedecl{bar}{p1}{p2}{p3}{p4}{p1}{p2}{p3}{p4}{p1}{p2}{p3}{p4}{p1}{p2}{p3}{p4}
  {p1}{p2}{p3}{p4}\par
\newusedecl[;]{foo}{p1}{p2}{p3}{p4}{p1}{p2}{p3}{p4}{p1}{p2}{p3}{p4}{p1}{p2}{p3}{p4}
  {p1}{p2}{p3}{p4}

\end{document}

The peeking is done using \@ifnextchar. For some explanation around this, see Understanding \@ifnextchar. The delayed use of \csvdel (the CSV delimiter) stems from Cunning (La)TeX tricks).

The optional argument to \newusedecl adapts \csvdel.


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


You've not given us much to go off of, but here's something that seems to implement what you want while being fed a comma separated list (in lieu of passing a variable number of arguments).

\documentclass{article}
\usepackage{xparse}
\newcounter{myargcounter}
\ExplSyntaxOn
\clist_new:N \l_myvararg_parameters_clist
\tl_new:N    \l_myvararg_current_item_tl
\NewDocumentCommand{\makecsv}{ m }
    {
        \clist_set:Nn \l_myvararg_parameters_clist { #1 }
        \int_while_do:nNnn { \clist_count:N \l_myvararg_parameters_clist } > { 1 }
            {
                \clist_pop:NN \l_myvararg_parameters_clist \l_myvararg_current_item_tl
                \tl_use:N \l_myvararg_current_item_tl,
            }   
         \clist_pop:NN \l_myvararg_parameters_clist
            \l_myvararg_current_item_tl
         {} ~ and ~ \tl_use:N \l_myvararg_current_item_tl
    }
\ExplSyntaxOff
\pagestyle{empty}
\begin{document}
\makecsv{a,b,c,d}

\makecsv{a,b,c,d,e,f,g}

\makecsv{a,b}

\end{document}