我有一堆\newcommand
宏,每个宏都带有多个参数。有时我会忘记其中一个参数,这可能会导致输出不符合预期,就像在这个 MWE 中一样,命令\foo
认为它的第四个参数是换行符:
\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}
在更复杂的情况下,忘记一个参数可能会导致非常不具信息性的错误消息,而且其原因很难追踪。
因此,我想强制使用花括号括住参数。换句话说,我想重新定义命令,\foo
这样上面的示例将无法编译,并显示类似“命令\foo
需要四个参数”的错误消息。这可能吗?
答案1
在评论中你建议其他输入语法是可以接受的
\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}
给出
! Undefined control sequence.
<argument> \MISSING
l.8 ...e output of my command: \foo{one,two,three}
?
如果需要,您显然可以定义\MISSING
给出更具体的错误。
答案2
抓取嵌套花括号参数的例程需要通过“向前看”标记流中的下一个标记来检测花括号。您可以使用它\futurelet
来获取有关意义下一个标记,但通过检查含义,您无法区分显式字符标记和隐式字符标记,如(定义为)。此外,这种方法在扩展上下文中不起作用,因为表示分配(如果有的话)是在扩展发生后一段时间进行的。{1
\bgroup
\let\bgroup={
\futurelet
\futurelet
如果可扩展性不是问题,并且你确实需要这样的接口,而不是自己实现\futurelet
,你可以尝试使用 xparse 的已弃用 g
- 或G
-type-argument。但您可能对 GitHub 问题的评论感兴趣“xparse 文档主文本中弃用的选项 #1069“。
除此之外,使用g
- 和G
-type-arguments 时,用于检测花括号是否存在的前瞻似乎是基于检查标记流的下一个标记的含义,因此可能会被 所欺骗\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}
控制台输出摘录:
! 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\\
?
答案3
您可以使用\NewDocumentCommand
宏来创建命令(现在它是原生的,在旧文档中您可能需要加载xparse
)。它以宏的名称、描述参数的字符串(m
是强制参数,O{xxx}
是可选参数,其默认值为xxx
...)和宏定义作为输入:
\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.