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}

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\\
?
\newcommand*
instead of\newcommand
) or do you want an error from\foo 1 2 3 4
?\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.\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)