3

This is certainly a duplicate but I cannot find a straightforward and simple conversion method.

Is there a universal way to convert \command{x}{y} to \commandEnhanced{x,y} ?

(The question is general, this is why i do not provide an MWE.)

EDIT

So now, going really to the heart of the question (mainly for curiosity's sake I admit), i.e. how to program a conversion command \enhance such as, for any command whose syntax is \command{x}{y}, the command \enhance{\command} creates a new command \commandEnhanced{x,y} ?

I don't know about nested arguments so I use "Arg?" instead, but that would be like:

\newcommand{\enhance}[1]{%
      \NewDocumentCommand{\#1EnhancedAUX}{mm}{--#Arg1?--#Arg2?--}
      \NewDocumentCommand{\#1Enhanced}{>{\SplitArgument{1}{,}}m}{\EnhancedAUX#Arg?}}

I just don't know what to type instead of "Arg?".

5
  • 3
    What do you mean by "convert"? If we are talking about string replacements, this should be easy enough to do in your editor. If a simple search & replace can't do the trick, a regular expression most certainly will.
    – Ingmar
    Jun 9, 2022 at 7:52
  • 1
    @Vincent If you're hoping to tell TeX to “detect” if \command has been called with several arguments, that' s a bad idea. \textbf{Foo}{bar} is perfectly legitimate TeX syntax. bar is simply inside a brace group. This plus the fact that in your question, there is a \command with two different syntaxes...
    – frougon
    Jun 9, 2022 at 8:00
  • 2
    Maybe \NewDocumentCommand with \SplitArgument? tex.stackexchange.com/a/605337/263192
    – user263192
    Jun 9, 2022 at 8:24
  • 1
    Near duplicat of macros - Command with arguments separated by comma - TeX - LaTeX Stack Exchange except in this case the arguments are "discrete" instead of "in a list"/.
    – user202729
    Jun 9, 2022 at 9:55
  • 1
    My first thought was: patch command. An MWE really is needed, or at least a use-case or some context. Second thought was: an expl3 sequence. Third thought: regex }{ into ,. ... "command" has multiple meanings: control sequence, cs name, definition, syntax, meaning, argument specification, output or result; in sequence or in parallel?; across or within groups?; recursive?; ...
    – Cicada
    Jun 9, 2022 at 10:59

5 Answers 5

7

Let me try to interpret the question.

You know how to define a two-argument command, say

\newcommand{\command}[2]{--#1--#2--}

but would prefer that the user syntax is

\command{x,y}

instead of \command{x}{y} that would be required by the above definition.

Here's \NewDocumentCommand coming to the rescue:

\NewDocumentCommand{\command}{>{\SplitArgument{1}{,}}m}{\commandaux#1}
\NewDocumentCommand{\commandaux}{mm}{--#1--#2--}

The “preprocessor directive” \SplitArgument{1}{,} tells LaTeX that the argument should be examined and split at the first appearance of ,; then the parts before and after this comma will be forwarded enclosed in braces.

So with the call \command{x,y}, the next thing LaTeX will see is

\commandaux{x}{y}

Note. If the comma is missing, the second braced group will contain the special token list -NoValue- and you might test for it

\NewDocumentCommand{\commandaux}{mm}{%
  --#1--%
  \IfNoValueF{#2}{#2--}%
}

In this way, \command{x,y} would produce --x--y--, whereas \command{x} would do --x--.


Just for fun

If \command is a “simple command” defined with \newcommand and no optional argument, one can access the number of arguments using the idea in https://tex.stackexchange.com/a/627923/4427

So we can subtract 1 and pass the required number to \SplitArgument. This allows calling the original command with the appropriate number of braced groups.

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\enhance}{m}
 {
  \krebs_enhance:N #1
 }

\cs_new_protected:Nn \krebs_enhance:N
 {
  \exp_args:Ncx
  \NewDocumentCommand % jump over this
  {\cs_to_str:N #1 Enhanced} % form a csname
  {>{\SplitArgument{\int_eval:n { \str_count:e { \cs_argument_spec:N #1 } / 2 - 1}}{,}}m}
  { #1##1 }
 }
\cs_generate_variant:Nn \str_count:n { e }

\ExplSyntaxOff

\newcommand{\command}[2]{--#1--#2--}

\enhance{\command}

\begin{document}

\command{x}{y}

\commandEnhanced{x,y}

\end{document}

enter image description here

If you type in less items than required, you'll see -NoValue- popping out.

Alternatively

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\enhance}{m}
 {
  \krebs_enhance:N #1
 }

\cs_new_protected:Nn \krebs_enhance:N
 {
  \exp_args:Nc \NewDocumentCommand { \cs_to_str:N #1 Enhanced } { m }
   {
    \exp_last_unbraced:Ne #1 { \clist_map_function:nN { ##1 } \__krebs_enhance_brace:n }
   }
 }
\cs_new:Nn \__krebs_enhance_brace:n { {#1} }

\ExplSyntaxOff

\newcommand{\command}[2]{--#1--#2--}

\enhance{\command}

\begin{document}

\command{x}{y}

\commandEnhanced{x,y}

\end{document}

Here the argument is processed to brace each item. In case you provide less items than required, you're on your own.

3
  • 1
    @VincentKrebs Fun indeed. No, optional arguments cannot be supported. And I'm not going further on this theme.
    – egreg
    Jun 9, 2022 at 16:08
  • 1
    @VincentKrebs Well, basically there's no real "reflection" in TeX to check if a command was designed to take optional argument (in fact "optional argument" isn't even a thing in TeX. Long story, learn LaTeX programming if you're interested) if you want to do that you have to just define your own command based on each case. Just use the o-type argument of expl3 then pass the resulting argument along.
    – user202729
    Jun 9, 2022 at 16:24
  • @VincentKrebs For optional arguments, use the standard syntax.
    – egreg
    Jun 9, 2022 at 20:46
4

If you accept another syntax with parentheses: \command(x,y), then you can define the macro very straightforward:

\def\command(#1,#2){first: #1, second #2.}

If you insist to braces: \command{x,y}, then you must remove these braces first and then you can use previous definition:

\def\command#1{\commandX(#1)}
\def\commandX(#1,#2){first: #1, second #2.}

test: \command{x,y}
\bye
3
  • Looks sound. I'm a beginner, I need to process this. I usually use \newcommand[2]{...}. Thank you very much. Jun 9, 2022 at 17:26
  • When you type \command#1 in the first line, you are saying that #1 is the argument of \command, but #1 is not enclosed into curly brackets, which is what got me confused at first. Now, i realize (if I understand correctly) that this is because curly brackets have no other purpose than defining a group. In turn, if you do enclose #1 into curly brackets, #1 will be interpreted as the argument of \def (or won't it?), which obviously we do not want. So, an alternative syntax would be to type \def{\command{#1}}{\commandX(#1)}; would that be equivalent? Jun 9, 2022 at 21:37
  • 1
    @VincentKrebs Your example \def{...} is wrong syntax. The declared control sequence is missed here. See (for example) my text TeX in a Nutshell petr.olsak.net/ftp/olsak/optex/tex-nutshell.pdf section 9. The \command in my second example has unseparated parameter #1. The outer {...} are removed when the parameter is scanned. The \commandX has obligatory prefix (, the parameter #1 separated by , and the parameter #2 separated by ).
    – wipet
    Jun 10, 2022 at 5:11
2

Another interpretation: redefining the command locally (=inside a group scope (to pass the value onto some other step, say)):

\documentclass{article}
\newcommand\myc[2]{{\renewcommand\myc[1]{x -- x ##1 x --   x}\myc{#1,#2}}}
\begin{document}
>>\myc{a}{b}<<
\end{document}

command

3
  • 1
    @VincentKrebs Thanks. I've been doing meta-commands recently, that's why the redefinition idea. If you put an if test in there, it becomes conditional. And another useful step is to redefine it back to the original meaning - or use a toggle.
    – Cicada
    Jun 9, 2022 at 16:54
  • Can you explain to me what ##1 means? I always use #1. Jun 9, 2022 at 20:21
  • 1
    @VincentKrebs When defining a macro inside another macro, double the number of #: e.g. \newcommand\XX[2]{\newcommand\YY[1]{##1}} defines a \newcommand\YY[1]{#1} (if you were typing it in ordinary code), so that \YY{z} produces z. #1 belongs to the \XX macro.
    – Cicada
    Jun 10, 2022 at 6:21
2

Your request subsumes two tasks:

  1. A routine for mapping a comma-list to a list of undelimited arguments where a macro-token is prepended for processing the delimited arguments.
  2. A routine \enhance{\command} which defines \commandEnhanced to apply the mapping-routine and hereby having the token \command prepended to the list of undelimited arguments which comes into being by the mapping.


Let's look at the first task:

In the example below the routine for mapping a comma-list to a list of undelimited arguments is called \PassCommaListAsUndelimitedListTo.

\PassCommaListAsUndelimitedListTo{⟨comma list⟩}{⟨code⟩} turns the ⟨comma list⟩ into a list of undelimited arguments where ⟨code⟩ is prepended.

E.g., \PassCommaListAsUndelimitedListTo{A, B, C}{⟨code⟩} yields ⟨code⟩{A}{B}{C}.


The code for \PassCommaListAsUndelimitedListTo is inspired by the function \clist_map_tokens:nn from expl3/LaTeX3 release 2021-05-05 or newer.

\clist_map_tokens:nn {⟨comma list⟩} {⟨code⟩} calls ⟨code⟩{⟨item⟩} for every ⟨item⟩ stored in the ⟨comma list⟩.

If an ⟨item⟩ of the ⟨comma list⟩ can be considered a set of tokens that is nested into a pair of matching curly braces, that outermost pair of surrounding matching curly braces will be stripped off.

\clist_map_tokens:nn ignores/discards/does not map empty ⟨item⟩s of the ⟨comma list⟩.


You can use something like \clist_map_tokens:nn for creating an expandable "mechanism"
\PassCommaListAsUndelimitedListTo{⟨comma list⟩}{⟨code⟩}
which turns a ⟨comma list⟩ into a list of undelimited arguments where ⟨code⟩ is prepended.

"Something like" because the circumstance of \clist_map_tokens:nn ignoring/discarding/not mapping empty ⟨item⟩s of the ⟨comma list⟩ might be a bit counter-intuitive—e.g., \PassCommaListAsUndelimitedListTo {A, , C}{\macro} would not yield \macro{A}{}{C} but would yield \macro{A}{C} if \clist_map_tokens:nn was used.

You can can easily derive a function from the definition of \clist_map_tokens:nn which does not ignore/discard empty ⟨item⟩s of the ⟨comma list⟩ by omitting the check for emptiness/blankness which is in \clist_map_tokens:nn's definition—if you do that, you need to be picky about the list having a trailing comma denoting an empty item behind that comma:

\ExpöSyntaxOn
\cs_new:Npn \PassCommaListAsUndelimitedList_clist_map_tokens:nn #1#2
 {
   \__PassCommaListAsUndelimitedList_clist_map_tokens_n:nw {#2}
   \prg_do_nothing: #1 , \s__clist_stop \clist_map_break: ,
   \prg_break_point:Nn \clist_map_break: { }
 }
\cs_new:Npn \__PassCommaListAsUndelimitedList_clist_map_tokens_n:nw #1#2 ,
 {
   \__clist_use_none_delimit_by_s_stop:w #2 \s__clist_stop
   \tl_trim_spaces_apply:oN {#2} \use_ii_i:nn
   \__clist_map_unbrace:wn , {#1}
   \__PassCommaListAsUndelimitedList_clist_map_tokens_n:nw {#1} \prg_do_nothing:
 }
\ExplSyntaxOff

The function
\PassCommaListAsUndelimitedList_clist_map_tokens:nn {⟨comma list⟩} {⟨code⟩}
calls ⟨code⟩{⟨item⟩} for every ⟨item⟩ stored in the ⟨comma list⟩.
Unlike the function \clist_map_tokens:nn the function \PassCommaListAsUndelimitedList_clist_map_tokens:nn does not discard/ignore empty comma-list items but maps them to undelimited empty arguments.

Using \PassCommaListAsUndelimitedList_clist_map_tokens:nn you can define \PassCommaListAsUndelimitedListTo as follows:

As \PassCommaListAsUndelimitedList_clist_map_tokens:nn's ⟨code⟩-argument use a macro/function \__PassCommaListAsUndelimitedList_grab_next:nwn which grabs several arguments:

  • the tokens forming the comma-list-item of the current iteration (n-type-argument)
  • the tokens forming the next \PassCommaListAsUndelimitedList_clist_map_tokens:nn-iteration as a delimited argument (w-type-argument) where the delimiter \__PassCommaListAsUndelimitedList_Reserved:n is trailed by
  • an argument holding the macro-token/code that shall be prepended to the undelimited arguments and the undelimited arguments gathered so far (n-type-argument).

This macro/function reinserts the tokens forming the next \PassCommaListAsUndelimitedList_clist_map_tokens:nn-iteration, the \__PassCommaListAsUndelimitedList_Reserved:n-delimiter and the argument holding the macro-token/code that shall be prepended to the undelimited arguments and the undelimited arguments gathered so far with the tokens forming the comma-list-item of the current iteration appended.

This way the combination of \PassCommaListAsUndelimitedList_clist_map_tokens:nn and \__PassCommaListAsUndelimitedList_grab_next:nwn iteratively accumulates the comma-list-items within the undelimited argument behind the delimiter \__PassCommaListAsUndelimitedList_Reserved:n.

When iterating is done, expanding \__PassCommaListAsUndelimitedList_Reserved:n removes the braces that surround that undelimited argument.

\ExplSyntaxOn
\cs_new:Npn \__PassCommaListAsUndelimitedList_grab_next:nwn #1#2\__PassCommaListAsUndelimitedList_Reserved:n #3 {
  % #1 - current comma-list-item
  % #2 - tokens forming the next `\clist_map_tokens:nn`-iteration
  % #3 - code that shall prepend undelimited arguments and undelimited arguments gathered so far
  #2 \__PassCommaListAsUndelimitedList_Reserved:n {#3{#1}}
}
\cs_new:Npn \__PassCommaListAsUndelimitedList_Reserved:n #1 {#1}
\cs_new:Npn \PassCommaListAsUndelimitedListTo #1#2 {
  \PassCommaListAsUndelimitedList_clist_map_tokens:nn {#1} {\__PassCommaListAsUndelimitedList_grab_next:nwn} 
  \__PassCommaListAsUndelimitedList_Reserved:n {#2}
}
\ExplSyntaxOff

With this code you can do, e.g.,
\PassCommaListAsUndelimitedListTo{A, B, C}{\macro}
for obtaining \macro{A}{B}{C}.

With this code you can do, e.g.,
\PassCommaListAsUndelimitedListTo{A, , C}{\macro}
for obtaining \macro{A}{}{C}.

With this code you can do, e.g.,
\PassCommaListAsUndelimitedListTo{A, B, }{\macro}
for obtaining \macro{A}{B}{}.

(You need to be picky about the list having a trailing comma denoting an empty item behind that comma.)


Let's look at the second task:

You can use \cs_to_str:N for obtaining the name of a control-sequence-token without leading backslash/escape-character. (As a special case with the nameless control-sequence-token, obtainable via \csname\endcsname or via having at the end of a line of .tex-input while the integer-parameter \endlinechar has a negative value, you get
csname⟨current escape-char⟩endcsname.)

Thus you can define \enhance in terms of \cs_to_str:N and \PassCommaListAsUndelimitedListTo:

\ExplSyntaxOn
\cs_new:Npn \enhance #1 {
  \cs_new:cpn {\cs_to_str #1 Enhanced} ##1 {
    \PassCommaListAsUndelimitedListTo { ##1 }{ #1 }
  }
}
\ExplSyntaxOff

Putting the pieces together you get:

\documentclass{article}

% \PassCommaListAsUndelimitedList_clist_map_tokens:nn {⟨comma list⟩} {⟨code⟩}
% Calls ⟨code⟩ {⟨item⟩} for every ⟨item⟩ stored in the ⟨comma list⟩.
% Does - unlike \clist_map_tokens:nn - not discard empty comma-list items.

\ExplSyntaxOn
\cs_new:Npn \__PassCommaListAsUndelimitedList_grab_next:nwn #1#2\__PassCommaListAsUndelimitedList_Reserved:n #3 {
  % #1 - current comma-list-item
  % #2 - tokens forming the next `\clist_map_tokens:nn`-iteration
  % #3 - code that shall prepend undelimited arguments and undelimited arguments gathered so far
  #2 \__PassCommaListAsUndelimitedList_Reserved:n {#3{#1}}
}
\cs_new:Npn \__PassCommaListAsUndelimitedList_Reserved:n #1 {#1}
\cs_new:Npn \PassCommaListAsUndelimitedListTo #1#2 {
  \PassCommaListAsUndelimitedList_clist_map_tokens:nn {#1} {\__PassCommaListAsUndelimitedList_grab_next:nwn} 
  \__PassCommaListAsUndelimitedList_Reserved:n {#2}
}
\cs_new:Npn \enhance #1 {
  \cs_new:cpn {\cs_to_str:N #1 Enhanced} ##1 {
    \PassCommaListAsUndelimitedListTo { ##1 }{ #1 }
  }
}
\cs_new:Npn \PassCommaListAsUndelimitedList_clist_map_tokens:nn #1#2
 {
   \__PassCommaListAsUndelimitedList_clist_map_tokens_n:nw {#2}
   \prg_do_nothing: #1 , \s__clist_stop \clist_map_break: ,
   \prg_break_point:Nn \clist_map_break: { }
 }
\cs_new:Npn \__PassCommaListAsUndelimitedList_clist_map_tokens_n:nw #1#2 ,
 {
   \__clist_use_none_delimit_by_s_stop:w #2 \s__clist_stop
   \tl_trim_spaces_apply:oN {#2} \use_ii_i:nn
   \__clist_map_unbrace:wn , {#1}
   \__PassCommaListAsUndelimitedList_clist_map_tokens_n:nw {#1} \prg_do_nothing:
 }
%\cs_new:Npn \__clist_map_unbrace:wn #1, #2 { #2 {#1} }
\ExplSyntaxOff

\newcommand\macro[3]{%
  {\frenchspacing\textnormal{\detokenize{Arg 1: (#1) Arg 2: (#2) Arg 3: (#3)}}}%
}%
\enhance{\macro}

\begin{document}

\noindent \verb|\macroEnhanced{A, B, C}| yields\\
\verb|\PassCommaListAsUndelimitedListTo{A, B, C}{\macro}|, which in turn yields\\
\verb|\macro{A}{B}{C}|.

\smallskip

\noindent Thus the following three commands in the end yield the same:

\smallskip

\noindent \verb|\macroEnhanced{A, B, C}|:\\
\macroEnhanced{A, B, C}

\smallskip

\noindent \verb|\PassCommaListAsUndelimitedListTo {A, B, C}{\macro}|:\\
\PassCommaListAsUndelimitedListTo {A, B, C}{\macro}

\smallskip

\noindent \verb|\macro{A}{B}{C}|:\\
\macro{A}{B}{C}.

\bigskip\hrule\bigskip

\noindent \verb|\macroEnhanced{A, , C}| yields\\
\verb|\PassCommaListAsUndelimitedListTo{A, , C}{\macro}|, which in turn yields\\
\verb|\macro{A}{}{C}|.

\smallskip

\noindent Thus the following three commands in the end yield the same:

\smallskip

\noindent \verb|\macroEnhanced{A, , C}|:\\
\macroEnhanced{A, , C}

\smallskip

\noindent \verb|\PassCommaListAsUndelimitedListTo {A, , C}{\macro}|:\\
\PassCommaListAsUndelimitedListTo {A, , C}{\macro}

\smallskip

\noindent \verb|\macro{A}{}{C}|:\\
\macro{A}{}{C}.

\bigskip\hrule\bigskip

\noindent \verb|\macroEnhanced{A, , }| yields\\
\verb|\PassCommaListAsUndelimitedListTo{A, , }{\macro}|, which in turn yields\\
\verb|\macro{A}{}{}|.

\smallskip

\noindent Thus the following three commands in the end yield the same:

\smallskip

\noindent \verb|\macroEnhanced{A, , }|:\\
\macroEnhanced{A, , }

\smallskip

\noindent \verb|\PassCommaListAsUndelimitedListTo {A, , }{\macro}|:\\
\PassCommaListAsUndelimitedListTo {A, , }{\macro}

\smallskip

\noindent \verb|\macro{A}{}{}|:\\
\macro{A}{}{}.

\end{document}

enter image description here

13
  • 1
    Wow, this is concise. LaTeX 3 allows for very advanced coding, I can see. Jun 9, 2022 at 17:17
  • 1
    @VincentKrebs If the argument of \commandEnhanced is the comma-list and \command shall get undelimited arguments instead, you need to do \newcommand{\commandEnhanced}[1]{ \PassCommaListAsUndelimitedListTo{#1}{\command}}. I just edited my answer and added a command \enhance which does such definitions. Then you can do \commandEnhanced{A, B, C} to get \command{A}{B}{C}. Jun 9, 2022 at 18:22
  • 1
    @VincentKrebs You're welcome. But I am not really happy with my approach because of the discarding of empty comma-list-items when the mapping is done. Probably a mapping-function other than \clist_map_tokens:nn, which - unlike \clist_map_tokens:nn - preserves empty comma-list-items might be more intuitive in such applications. Jun 9, 2022 at 18:27
  • 1
    @UlrichDiez You may try \seq_set_split:Nnn. :)
    – gusbrs
    Jun 9, 2022 at 18:41
  • 1
    @VincentKrebs It is just for exhibiting that \PassCommaListAsUndelimitedListTo{A,B,C}{\macro} and \macroEnhanced{A, B, C} yield the same. Because expansion of \macroEnhanced{A, B, C} yields \PassCommaListAsUndelimitedListTo{A,B,C}{\macro}. That in turn at some stage of expansion yields \macro{A}{B}{C}. Jun 9, 2022 at 21:31
1

Your request subsumes two tasks:

  1. A routine for mapping a comma-list to a list of undelimited arguments where a macro-token is prepended for processing the delimited arguments.
  2. A routine \enhance{\command} which defines \commandEnhanced to apply the mapping-routine and hereby having the token \command prepended to the list of undelimited arguments which comes into being by the mapping.


If you wish to specify defaults for mandatory arguments in case the comma-list provided by the user does not contain as many items as mandatory arguments are needed or in case some of the comma-list-items are spcified empty/blank, I can offer an interface

\PassCommaListAsUndelimitedListTo {⟨comma list⟩}%
                                  {⟨emptiness or (message-delivering) tokens to
                                    prepend to  ⟨tokens where undelimited arguments are to be
                                    appended⟩ in case comma-list has more items
                                    than defaults are provided⟩}%
                                  {⟨tokens where undelimited arguments are to be appended⟩}%
                                  {
                                    {⟨default for undelimited argument 1⟩}
                                    {⟨default for undelimited argument 2⟩}
                                    ...
                                    {⟨default for undelimited argument k⟩}
                                  }

Accordingly with this interface syntax of \enhance is

\enhance{⟨macro⟩}
        {%
         ⟨emptiness or (message-delivering) tokens to prepend to the call
          of ⟨macro⟩ in case ⟨macro⟩Enhanced's
          comma-list-argument has more items than there are defaults⟩
        }%
        {%
          {⟨default for ⟨macro⟩'s 1st undelimited argument⟩}%
          {⟨default for ⟨macro⟩'s 2nd undelimited argument⟩}%
          ...
          {⟨default for ⟨macro⟩'s k-th undelimited argument⟩}%
        }

If ⟨macro⟩Enhanced's comma-list provided by the user contains more elements than you have defaults, spurious comma-list-items are ignored and ⟨emptiness or (message-delivering) tokens to prepend...⟩ are prepended to the tokens that form the call to ⟨macro⟩.

I.e., the amount of undelimited arguments to be appended to ⟨tokens where undelimited arguments are to be appended⟩/⟨macro⟩ in any case corresponds to the amount of defaults specified.

\documentclass{article}

\ExplSyntaxOn

%% --------------------------------------------------------------------------------
%%  Stuff for error-messages:

\msg_new:nnn { PassCommaListAsUndelimitedList }
             { too-many-comma-items }
             { #1Enhanced 's~argument~holds~a~comma-list~with~more~than~#2~components~
               although~there~should~be~at~most~#2~components~because~the~underlying~
               macro~#1~processes~#2~undelimited~arguments.~
               Spurious~components~ignored.}
\prop_gput:Nnn \g_msg_module_type_prop { PassCommaListAsUndelimitedList } {}
\prop_gput:Nnx \g_msg_module_name_prop { PassCommaListAsUndelimitedList } {LaTeX}
\cs_new:Npn \PassCommaListAsUndelimitedListToTooManyItemsError #1#2 {%
   % #1 macro token
   % #2 word or digits denoting amount of arguments
   \exp_args:Nno
     \use:n 
     { \msg_error:nnnn  { PassCommaListAsUndelimitedList } { too-many-comma-items } } 
     { 
       \exp:w\exp_after:wN\exp_after:wN\exp_after:wN\exp_after:wN
             \exp_after:wN\exp_after:wN\exp_after:wN\exp_end:     
        \exp_after:wN\exp_after:wN\exp_after:wN \c_backslash_str\cs_to_str:N#1
     } 
     { #2 }
}%
%
% \PassCommaListAsUndelimitedListToTooManyItemsError{\macro}{<amount>}
%
%  yields an error-message
%
%  \macroEnhanced's argument holds a comma list with more than <amount> components
%  although there should be at most <amount> components because the underlying
%  macro \macro processes <amount> undelimited arguments.
%  Spurious components ignored.

%% --------------------------------------------------------------------------------
%%  Map code to items of comma-list:

% \PassCommaListAsUndelimitedList_clist_map_tokens:nn {<comma list>} 
%                                                     {<code if item is not blank>}
%                                                     {<code if item is blank>}
% Calls  <code if item is not blank>{<item>}  for every non-blank <item> stored in the <comma list>.
% Calls  <code if item is blank>{<item>}  for every blank <item> stored in the <comma list>.

\cs_new:Npn \PassCommaListAsUndelimitedList_clist_map_tokens:nnn #1#2#3
% #1 - comma list
% #2 - code if item is not blank
% #3 - code if item is blank
 {
   \__PassCommaListAsUndelimitedList_clist_map_tokens_n:nw {{#2}{#3}}
   \prg_do_nothing: #1 , \s__clist_stop \clist_map_break: ,
   \prg_break_point:Nn \clist_map_break: { }
 }
\cs_new:Npn \__PassCommaListAsUndelimitedList_clist_map_tokens_n:nw #1#2 ,
 {
   \__clist_use_none_delimit_by_s_stop:w #2 \s__clist_stop
   \tl_trim_spaces_apply:oN {#2} \use_ii_i:nn
   \__clist_map_unbrace:wn , {\tl_if_empty:oTF{\use_none:nn #2 ? }{\use_ii:nn}{\use_i:nn}#1}
   \__PassCommaListAsUndelimitedList_clist_map_tokens_n:nw {#1} \prg_do_nothing:
 }
\cs_new:Npn \__clist_map_unbrace:wn #1, #2 { #2 {#1} }

%% --------------------------------------------------------------------------------
%%  Map comma-list to list of undelimited arguments and prepend tokens for processing
%%  undelimited arguments and - in case there are more comma-items than defaults -
%%  prepend some error- or warning-message-tokens.
%
%  \PassCommaListAsUndelimitedListTo{<comma-list>}
%                                   {<message-delivering tokens to prepend to #3 in case comma-list 
%                                     has more items than defaults are provided>}
%                                   {<tokens where undelimited arguments are to be appended>}
%                                   {<list of undelimited arguments denoting defaults in case 
%                                     corresponding comma-list-item is specified blank or not
%                                     enough comma-list-items are specified>}
%
%    E.g.,  \PassCommaListAsUndelimitedListTo{A, B, C}{Problem}{\macro}{%
%              {Default for \macro's 1st arg}   
%              {Default for \macro's 2nd arg}   
%              {Default for \macro's 3rd arg}   
%           }%
%    yields: \macro{A}{B}{C}
%
%    E.g.,  \PassCommaListAsUndelimitedListTo{A, , C}{Problem}{\macro}{%
%              {Default for \macro's 1st arg}   
%              {Default for \macro's 2nd arg}   
%              {Default for \macro's 3rd arg}   
%           }%
%    yields: \macro{A}{Default for \macro's 2nd arg}{C}
%
%    E.g.,  \PassCommaListAsUndelimitedListTo{A}{Problem}{\macro}{%
%              {Default for \macro's 1st arg}   
%              {Default for \macro's 2nd arg}   
%              {Default for \macro's 3rd arg}   
%           }%
%    yields: \macro{A}{Default for \macro's 2nd arg}{Default for \macro's 3rd arg}
%
%    E.g.,  \PassCommaListAsUndelimitedListTo{A, B, C, D}{Problem}{\macro}{%
%              {Default for \macro's 1st arg}   
%              {Default for \macro's 2nd arg}   
%              {Default for \macro's 3rd arg}   
%           }%
%    yields: Problem\macro{A}{B}{C}
%
%    E.g.,  \PassCommaListAsUndelimitedListTo{A, B, , D}{Problem}{\macro}{%
%              {Default for \macro's 1st arg}   
%              {Default for \macro's 2nd arg}   
%              {Default for \macro's 3rd arg}   
%           }%
%    yields: Problem\macro{A}{B}{Default for \macro's 3rd arg}
%
%    E.g.,  \PassCommaListAsUndelimitedListTo{A, {}, C}{Problem}{\macro}{%
%              {Default for \macro's 1st arg}   
%              {Default for \macro's 2nd arg}   
%              {Default for \macro's 3rd arg}   
%           }%
%    yields: \macro{A}{}{C}

\cs_new:Npn \PassCommaListAsUndelimitedListTo #1#2#3#4 {
  % #1 comma-list
  % #2 tokens to prepend to #3 in case comma-list has more items than defaults are provided
  % #3 tokens where undelimited arguments are to be appended
  % #4 defaults for undelimited arguments
  \exp_args:Nno \use:nn
  { \PassCommaListAsUndelimitedList_clist_map_tokens:nnn {#1} 
                                                         {\__PassCommaListAsUndelimitedList_grab_next_nonblank:nnwnn{#2}}
                                                         {\__PassCommaListAsUndelimitedList_grab_next_blank:nnwnn{#2}}
   \__PassCommaListAsUndelimitedList_Reserved:nn {#3}
  } { \exp:w \tl_trim_spaces_apply:nN {#4} \exp_end: }
}
\cs_new:Npn \__PassCommaListAsUndelimitedList_Reserved:nn #1#2 {
  % #1 - code that shall prepend undelimited arguments and undelimited arguments gathered so far
  % #2 - remaining defaults for undelimited argument
  #1#2
}
\cs_new:Npn \__PassCommaListAsUndelimitedList_grab_next_nonblank:nnwnn #1#2#3\__PassCommaListAsUndelimitedList_Reserved:nn #4#5 {
  % #1 - code to prepend in case comma-list has more items than defaults are provided
  % #2 - current comma-list-item
  % #3 - tokens forming the next `\clist_map_tokens:nn`-iteration
  % #4 - code that shall prepend undelimited arguments and undelimited arguments gathered so far
  % #5 - defaults
  \tl_if_empty:oTF { \use_none:nn ? #5 ? } 
                   {\clist_map_break:n{#1#4} #3 } 
                   {
                     \exp_args:Nno 
                      \use:n
                      {
                        #3 \__PassCommaListAsUndelimitedList_Reserved:nn {#4{#2}}
                      } {\use_i:nn {} #5}
                   }
}
\cs_new:Npn \__PassCommaListAsUndelimitedList_grab_next_blank:nnwnn #1#2#3\__PassCommaListAsUndelimitedList_Reserved:nn #4#5 {
  % #1 - code to prepend in case comma-list has more items than defaults are provided
  % #2 - current comma-list-item
  % #3 - tokens forming the next `\clist_map_tokens:nn`-iteration
  % #4 - code that shall prepend undelimited arguments and undelimited arguments gathered so far
  % #5 - defaults
  \tl_if_empty:oTF { \use_none:nn ? #5 ? } 
                   {\clist_map_break:n{#1#4} #3 } 
                   {
                     \exp_args:Nno 
                      \use:n
                      {
                          \exp_args:Nno \use:n
                          { #3 \__PassCommaListAsUndelimitedList_Reserved:nn }
                          { \exp:w \exp_args:Nno \use:n { \exp_end: #4} {\tl_head:w #5 {} \q_stop} } 
                      } {\use_i:nn {} #5}
                   }
}

%% --------------------------------------------------------------------------------
%%  Define \<macro>Enhanced to process a comma-list by mapping it to a list of undelimited
%%  arguments where the token <macro> is prepended and where - in case the comma-list has 
%%  more items than defaults for macro are provided - some tokens for delivering message are
%%  prepended to the token \macro
%%
%%  Syntax:
%%
%%    \enhance{<macro>}%
%%            {<additional error-tokens in case <macro>Enhanced's comma-list-argument
%%              has more items than there are defaults>}%
%%            {%
%%              {<default for <macro>'s 1st argument in case it is not provided via <macro>Enhanced's comma-list>}%
%%              {<default for <macro>'s 2nd argument in case it is not provided via <macro>Enhanced's comma-list>}%
%%              ...
%%              {<default for <macro>'s k-th argument in case it is not provided via <macro>Enhanced's comma-list>}%
%%            }%
%%
\cs_new:Npn \enhance #1 #2 #3{
  % #1 macro
  % #2 tokens to prepend to macro in case comma-list has more items than defaults for macro are provided
  % #3 defaults
  \cs_new:cpn {\cs_to_str:N #1 Enhanced} ##1 {
    \PassCommaListAsUndelimitedListTo { ##1 }{ #2 }{ #1 }{ #3 }
  }
}
\ExplSyntaxOff

\newcommand\macro[3]{%
  {\frenchspacing
     \textnormal{Arg 1: (#1)}\\
     \textnormal{Arg 2: (#2)}\\
     \textnormal{Arg 3: (#3)}
  }%
}%
\enhance{\macro}%
        {\PassCommaListAsUndelimitedListToTooManyItemsError{\macro}{three}}%
        {
          {Default for \texttt{\string\macro}'s argument 1}
          {Default for \texttt{\string\macro}'s argument 2}
          {Default for \texttt{\string\macro}'s argument 3}
        }

\begin{document}

\enlargethispage{4cm}\null\par\kern-3cm

\noindent \verb|\macroEnhanced{A, B, C}|:\\
\macroEnhanced{A, B, C}

\bigskip\hrule\bigskip

\noindent \verb|\macroEnhanced{A, , C}|:\\
\macroEnhanced{A, , C}

\bigskip\hrule\bigskip

\noindent \verb|\macroEnhanced{A, , }|:\\
\macroEnhanced{A, , }

\bigskip\hrule\bigskip

\noindent \verb|\macroEnhanced{A, {} , {}}|:\\
\macroEnhanced{A, {} , {}}

\bigskip\hrule\bigskip

\noindent \verb|\macroEnhanced{A}|:\\
\macroEnhanced{A}

\bigskip\hrule\bigskip

\noindent \verb|\macroEnhanced{A, B}|:\\
\macroEnhanced{A, B}

\bigskip\hrule\bigskip

%% The following yields an error-message about \macroEnhanced's argument
%% holding a comma-list with more than three components although there
%% should be at most three components.
%% The raising of this error-message is intended/correct behavior.
%% As \macro processes only three undelimited arguments, three defaults
%% for these arguments were specified when calling \enhance for defining
%% \macroEnhanced.
%% If the comma-list that forms \macroEnhanced's argument provides more
%% than three arguments/provides more arguments than defaults were provided,
%% this is a problem where the user should be informed about by means of a
%% message.
%% The following is a test whether the message intended/needed in this case
%% is really raised.
%%
\noindent \verb|\macroEnhanced{A, B, C, D, E}|:\\
\macroEnhanced{A, B, C, D, E}

{\itshape

\bigskip

\noindent On the console and in the log-file you also get something like:

\begin{verbatim}
! LaTeX Error: \macroEnhanced's argument holds a comma-list with more than
(LaTeX)        three components although there should be at most three
(LaTeX)        components because the underlying macro \macro processes
(LaTeX)        three undelimited arguments. Spurious components ignored.

Type <return> to continue.
 ...                                              
                                                  
l.305 \macroEnhanced{A, B, C, D, E}
\end{verbatim}

\noindent This is the intended behavior.

}

\end{document}

enter image description here

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .