4

I have a bunch of \newcommand macros that each take several arguments. Sometimes I forget one of the arguments, and this can lead to the output not being as expected, as in this MWE, where the \foo command thinks its fourth argument is a newline character:

\documentclass{article}
\newcommand{\foo}[4]{%
    The four arguments were #1, #2, #3 and #4.%
}
\begin{document}

Here is the output of my command: \foo{one}{two}{three}

\end{document}

In a more complex situation forgetting an argument can lead to very uninformative error messages whose cause is hard to track down.

Because of this, I would like to make the curly braces around the arguments compulsory. In other words, I would like to redefine the command \foo such that the above example will fail to compile, with an error message along the lines of "Command \foo requires four arguments." Is this possible?

5
  • do you just want to catch end of paragraph (use \newcommand* instead of \newcommand) or do you want an error from \foo 1 2 3 4 ? Apr 4 at 13:07
  • @DavidCarlisle I'd like an error from \foo 1 2 3 4 also. The syntax doesn't have to be \foo{1}{2}{3}{4}, it could be something like \foo{1,2,3,4} instead - the point is just to make sure there's no way I can think I supplied the right number of arguments when I didn't.
    – N. Virgo
    Apr 4 at 13:09
  • Ulrich's answer then:-) Be careful what you ask for though. Apr 4 at 13:11
  • \foo{1,2,3,4} completely changes the question: it's easy to check for that even in expandable commands, eg \cline{1-2} {which is two arguments separated by - but same idea) Apr 4 at 13:15
  • @DavidCarlisle sorry for changing the question so much with that comment - I didn't realise that would be the case.
    – N. Virgo
    Apr 4 at 13:30

3 Answers 3

5

In comments you suggested other input syntax would be acceptable

\documentclass{article}
\newcommand*{\foo}[1]{\xfoo#1,\MISSING,\MISSING,\MISSING,\MISSING,\xfoo}
\def\xfoo#1,#2,#3,#4,#5\xfoo{%
    The four arguments were #1, #2, #3 and #4.%
}
\begin{document}

Here is the output of my command: \foo{one,two,three}

\end{document}

gives

! Undefined control sequence.
<argument> \MISSING 
                    
l.8 ...e output of my command: \foo{one,two,three}
                                                  
?

You could obviously define \MISSING to give a more specific error if needed.

3

A routine for grabbing curly-brace-nested arguments requires detecting curly braces by "looking ahead" at the next token in the token-stream. You can use \futurelet for obtaining information about the meaning of the next token, but by examining the meaning you cannot distinguish an explicit {1-character-token from an implicit character token like \bgroup (defined as \let\bgroup={). Besides this approaches via \futurelet do not work out in expansion-contexts as \futurelet denotes an assignment which - if at all - is carried out some time after expansion took place.

If expandability is not an issue and if you really desire such an interface, instead of implementing things based on \futurelet yourself, you can try with xparse's deprecated g- or G-type-argument. But you might be interested in the remarks with the GitHub-issue "xparse documentation deprecated options inside main text #1069".

Besides this, with g- and G-type-arguments the lookahead for detecting the presence of a curly opening brace seems to be based on examining the meaning of the next token of the token-stream and thus can be fooled by \bgroup.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
% Use expl3-infrastructure for providing error-message in case there are not
% enough curly-brace-nested arguments:
\prop_gput:Nnn \g_msg_module_type_prop { MyStuff } {}
\prop_gput:Nnn \g_msg_module_name_prop { MyStuff } {Macro-Defined-In-Preamble:}
\msg_new:nnnn {MyStuff} 
              {Not enough arguments}
              {Macro~#1:~Command~#1~requires~#2~arguments,~you~provided~#3.}
              {When~specifying~arguments~make~sure~they~are~nested~between~curly~braces.}
\cs_new:Npn \NotEnoughArgsError #1#2#3 {
  \exp_args:Nne \use:nn {
    \msg_error:nnnnn {MyStuff} {Not enough arguments}
   }{{\iow_char:N \\ \cs_to_str:N#1}}{#2}{#3}
}%
\cs_new:Npn \ttdetokenize #1 {\texttt{\detokenize{#1}}}
\ExplSyntaxOff

\NewDocumentCommand\foo{gggg}{%
  \IfNoValueTF{#1}{\NotEnoughArgsError{\foo}{4}{0}}{%
    \IfNoValueTF{#2}{\NotEnoughArgsError{\foo}{4}{1}}{%
      \IfNoValueTF{#3}{\NotEnoughArgsError{\foo}{4}{2}}{%
       \IfNoValueTF{#4}{\NotEnoughArgsError{\foo}{4}{3}}{%
          The four arguments were \ttdetokenize{#1}, 
          \ttdetokenize{#2}, \ttdetokenize{#3} and \ttdetokenize{#4}.%
       }%
      }%
    }%
  }%
}%

\begin{document}

\begin{tabular}{|l|p{.4\textwidth}|}
\hline
\textbf{Input}&\textbf{Result}\\
\hline
\verb*|\foo {A} {B} {C} {D}|&\foo {A} {B} {C} {D}\\
\hline
%
% Error-message about providing only 3 arguments:
\verb*|\foo {A} {B} {C}D|&\foo {A} {B} {C}D\\
\hline
%
% Error-message about providing only 2 arguments:
\verb*|\foo {A} {B} CD|&\foo {A} {B} CD\\
\hline
%
% Error-message about providing only 1 argument:
\verb*|\foo {A} BCD|&\foo {A} BCD\\
\hline
%
% Error-message about providing 0 arguments:
\verb*|\foo ABCD|&\foo ABCD\\
\hline
\multicolumn{2}{|l|}{\textbf{Be aware of this one:}}\\
\hline
% !!! No error message at all!!!
\verb*|\foo \bgroup {B} {C} {D}|&\foo \bgroup {B} {C} {D}\\
\hline
\end{tabular}

\end{document}

enter image description here

Excerpt of console-output:

! Macro-Defined-In-Preamble: Error: Macro \foo: Command \foo requires 4
(Macro-Defined-In-Preamble:)        arguments, you provided 3.

For immediate help type H <return>.
 ...                                              
                                                  
l.44 \verb*|\foo {A} {B} {C}D|&\foo {A} {B} {C}D
                                                \\
? 

! Macro-Defined-In-Preamble: Error: Macro \foo: Command \foo requires 4
(Macro-Defined-In-Preamble:)        arguments, you provided 2.

For immediate help type H <return>.
 ...                                              
                                                  
l.48 \verb*|\foo {A} {B} CD|&\foo {A} {B} C
                                           D\\
? 

! Macro-Defined-In-Preamble: Error: Macro \foo: Command \foo requires 4
(Macro-Defined-In-Preamble:)        arguments, you provided 1.

For immediate help type H <return>.
 ...                                              
                                                  
l.52 \verb*|\foo {A} BCD|&\foo {A} B
                                    CD\\
? 

! Macro-Defined-In-Preamble: Error: Macro \foo: Command \foo requires 4
(Macro-Defined-In-Preamble:)        arguments, you provided 0.

For immediate help type H <return>.
 ...                                              
                                                  
l.56 \verb*|\foo ABCD|&\foo A
                             BCD\\
? 
1

You can use the \NewDocumentCommand macro to create commands (now it's native, in old document you might need to load xparse). It takes as input the name of the macro, a string describing the arguments (m is a mandatory argument, O{xxx} is an optional argument whose default value is xxx…) and the macro definition:

\documentclass{article}
\NewDocumentCommand{\foo}{mmmm}{%
    The four arguments were #1, #2, #3 and #4.%
}
\begin{document}

Here is the output of my command: \foo{one}{two}{three}

\end{document}
Runaway argument?
! Paragraph ended before \foo  was complete.

4
  • 6
    This is only because the argument is not long and the missing argument results to be a (implicit) \par; you can achieve the same as the OP error changing the last argument to m+ (or +m, I forgot.). Try \foo{a}{b}{c} surprise... 😉
    – Rmano
    Apr 4 at 7:11
  • 3
    But this does point to another fix for OP's MWE: using \newcommand*{\foo}[4]{...} would have resulted in the same runaway argument error.
    – Teepeemm
    Apr 4 at 11:06
  • \foo1234 would give no error with this definition Apr 4 at 13:05
  • Oh good point, I didn't noticed that. I'll let the answer in case some people might be interested by this simple solution.
    – tobiasBora
    Apr 4 at 13:55

You must log in to answer this question.

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