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?".

  • 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 09 '22 at 07: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 09 '22 at 08:00
  • 2
    Maybe \NewDocumentCommand with \SplitArgument? https://tex.stackexchange.com/a/605337/263192 –  Jun 09 '22 at 08: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 09 '22 at 09: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 09 '22 at 10:59

5 Answers5

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.

egreg
  • 1,121,712
  • 1
    @VincentKrebs Fun indeed. No, optional arguments cannot be supported. And I'm not going further on this theme. – egreg Jun 09 '22 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 09 '22 at 16:24
  • @VincentKrebs For optional arguments, use the standard syntax. – egreg Jun 09 '22 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

wipet
  • 74,238
  • Looks sound. I'm a beginner, I need to process this. I usually use \newcommand[2]{...}. Thank you very much. – Vincent Krebs Jun 09 '22 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? – Vincent Krebs Jun 09 '22 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 http://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 '22 at 05: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

Cicada
  • 10,129
  • 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 09 '22 at 16:54
  • Can you explain to me what ##1 means? I always use #1. – Vincent Krebs Jun 09 '22 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 '22 at 06: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

Ulrich Diez
  • 28,770
  • 1
    Wow, this is concise. LaTeX 3 allows for very advanced coding, I can see. – Vincent Krebs Jun 09 '22 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}. – Ulrich Diez Jun 09 '22 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. – Ulrich Diez Jun 09 '22 at 18:27
  • 1
    @UlrichDiez You may try \seq_set_split:Nnn. :) – gusbrs Jun 09 '22 at 18:41
  • @gusbrs Thanks for pointing this out. \seq_set_split:Nnn implies the need of using a sequence-variable which in turn implies that macro-mechanisms based on \seq_set_split:Nnn will not be fully expandable/might break in expansion-contexts. If expandability is not a requirement, \seq_set_split:Nnn seems to be the better approach. – Ulrich Diez Jun 09 '22 at 19:04
  • @UlrichDiez True, you need the assignment to use it for this purpose, thus loose expandability... – gusbrs Jun 09 '22 at 19:20
  • @Ulrich Something confuses me. At the end, shouldn't \PassCommaListAsUndelimitedListTo{A,B,C}{\macro} be removed, since we already typed \enhanced{\macro} which has the same effect? Or did you leave it for clarity's sake? – Vincent Krebs Jun 09 '22 at 20:28
  • 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}. – Ulrich Diez Jun 09 '22 at 21:31
  • 1
    @VincentKrebs What do you mean by "don't define the value for #2"If I do\newcommand{\gloss}[2]{\textit{#1}\space `#2'}and\enhance{\gloss}, then\gloss{A}{B}and\glossEnhanced{A,B}` yield the same. – Ulrich Diez Jun 09 '22 at 23:19
  • 1
    @VincentKrebs You defined \gloss to process two mandatory arguments. In the call \gloss{A} only one mandaory argument is provided. So \gloss{A} is not called correctly. – Ulrich Diez Jun 09 '22 at 23:23
  • 1
    @Ulrich Sorry, when I receive a lot of information in a small time span, it's a lot to process and I relate one to another things I shouldn't. Here, I didn't realize that the optionality allowed through use of \IfNoValueF was linked to using \SplitArgument and had to be "forgotten" when working on the automatic conversion command. – Vincent Krebs Jun 10 '22 at 00:09
  • 1
    @VincentKrebs Don't worry ;-) I've caused much more bizarre things while struggling with information flooding ;-) To complicate matters, communication does not take place in our native languages. Instead of worrying, we might be better off relaxing and coming to terms with the fact that we are doomed by circumstances. ;-) – Ulrich Diez Jun 10 '22 at 15:08
  • 1
    Thanks for this because I always feel like I'm doing things wrong somehow so it's good to read ;) and thanks for your time on this "fun" little problem ;) – Vincent Krebs Jun 10 '22 at 15:14
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 {&langle;comma list&rangle;}%
                                  {&langle;emptiness or (message-delivering) tokens to
                                    prepend to  &langle;tokens where undelimited arguments are to be
                                    appended&rangle; in case comma-list has more items
                                    than defaults are provided&rangle;}%
                                  {&langle;tokens where undelimited arguments are to be appended&rangle;}%
                                  {
                                    {&langle;default for undelimited argument 1&rangle;}
                                    {&langle;default for undelimited argument 2&rangle;}
                                    ...
                                    {&langle;default for undelimited argument k&rangle;}
                                  }

Accordingly with this interface syntax of \enhance is

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

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 &lt;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

Ulrich Diez
  • 28,770