例如,我定义了一个\test
带有一个参数的宏。Latex 默认将后面的第一个字符作为其参数。如果在没有任何明确参数的情况下调用,\test
我希望 latex 抛出错误。也就是说,我应该始终写,而不是。如何实现?\test
\test{a}
\test a
\documentclass{amsart}
\newcommand{\test}[1]{test #1 test}
\begin{document}
\test abcdefg
\end{document}
答案1
请注意,如果您不提供括号,则抓取单个非空格非括号标记的行为是 TeX 的标准行为,并且 LaTeX 方面不会出现错误。TeX 抓取括号平衡的参数,如果您不以左括号开头,则单个标记是括号平衡的。虽然 LaTeX 官方表示应始终提供参数周围的括号,但对于单个标记,省略括号是很常见的(例如,我从未使用过,\newcommand{\foo}{}
但总是使用\newcommand\foo{}
)。
无论如何,您都可以使用以下左括号进行明确测试\@ifnextchar\bgroup
,如果找不到则抛出错误:
\documentclass{amsart}
\makeatletter
\@ifdefinable\test{\protected\def\test
{%
\@ifnextchar\bgroup
{\@test}%
{\PackageError{gaoqiang}{Missing braced argument}{}}%
}}
\newcommand\@test[1]{test #1 test}
\makeatother
\begin{document}
\test abcdefg
\end{document}
请注意,\@ifnextchar
是不可扩展的,因此我们需要以某种方式定义受保护的宏(要么\DeclareRobustCmd
,\NewDocumentCommand
要么像这里使用一样\protected\def
;我已添加\@ifdefinable
以确保\test
是一个新命令)。
答案2
如果你不想自己指定错误消息,但对一些低级 TeX 错误消息“ ! Use of \test doesn't match its definition.
”感到满意,则可以使用#{
-notation:
\documentclass{amsart}
\makeatletter
\@ifdefinable\test{\long\def\test#{\test@brace}}%
\newcommand{\test@brace}[1]{test #1 test}%
\makeatother
\begin{document}
\test {Nice}
\test Problem
\end{document}
如果您希望自己指定错误消息,事情可能会变得更加棘手。
在 TeX 术语中,在宏编程的上下文中,“参数”和“参数”这两个术语的含义并不相同。
在 TeX 术语中,在宏编程的上下文中,一些术语(例如“参数”一词)关注宏定义的时间,而一些术语(例如“参数”一词)关注宏标记(即当前代表宏实例的控制序列标记)展开的时间。
在 TeX 术语中,在宏编程的上下文中,“参数”是宏的 ⟨定义⟩ 的组成部分。它们出现在 ⟨定义⟩ 的 ⟨参数文本⟩ 和 ⟨定义⟩ 的 ⟨平衡文本⟩ 中。它们表示在扩展当前代表宏实例的控制序列标记时(因为相应的 ⟨定义⟩ 当前已分配给它),需要额外的标记群来形成该控制序列标记/该宏标记的“参数”。
在 TeX 术语中,在宏编程的上下文中,“参数”是一组标记,在扩展宏标记时,会从标记流中抓取这些标记,用于替换当前分配给该宏标记的 ⟨definition⟩ 的 ⟨balanced text⟩ 中出现的参数的所有实例(该参数可能是#1
或可能是或...)。 从标记流中抓取形成参数的一组标记(定界参数的定界符、非定界参数)的规则来自当前分配给宏标记的 ⟨definition⟩ 的 ⟨parameter text⟩。#2
从关注不同方面的角度来理解,在这个答案中
短语“⟨替换文本⟩”(在角度中)指的是宏的⟨定义⟩的⟨平衡文本⟩,即宏的⟨定义⟩的组成部分。⟨替换文本⟩指的是定义一个宏。它可能包含参数
#1
、#2
等,如 ⟨definition⟩ 的 ⟨parameter text⟩ 中所示。短语“替换文本”(不带角度)指的是在扩展一个宏标记,该宏标记和组成它的参数分隔符和参数的标记将被替换,并且当前分配给该宏标记的⟨定义⟩的⟨替换文本⟩的参数
#1
、#2
等将被组成该宏标记的相应参数的标记替换。
\test
正如您在示例中所定义的,#1
在执行过程中产生的 ⟨定义⟩ 的 ⟨参数文本⟩ 和 ⟨替换文本⟩ 中都有一个未限定的参数\newcommand
。
因此,在扩展宏标记时\test
,即在标记流中替换宏标记及其参数分隔符和参数的标记时,将从标记\test
流中抓取/移除要形成所谓未分隔参数的标记,以替换#1
当前分配给宏标记的⟨定义⟩的⟨替换文本⟩中出现的所有实例\test
。
未限定的参数可以用两种方式表示:
要么只有一个非
\outer
标记,它既不是类别 1(开始组)的显式字符标记,也不是类别 2(结束组)的显式字符标记。在这种情况下,该标记是未限定的参数。或者存在一个(可能为空的)非
\outer
标记星座,其中第 1 类(起始组)的显式字符标记和第 2 类(结束组)的显式字符标记(如果存在)是平衡的,并且被第 1 类(起始组)的显式字符标记和匹配的第 2 类(结束组)的显式字符标记包围。在这种情况下,这两个周围的字符标记表示/界定形成所谓无界参数的标记星座,但这两个周围/界定字符标记本身不是参数的组成部分,因为参数不是将其变成形成替换文本的标记集,该替换文本替换参数所属的宏标记和界定/形成参数所属的宏标记的参数的标记。
在扩展参数所属的宏标记的过程中,这两个周围/界定字符标记被剥离,从而从标记流中获取界定/形成参数的标记。
LaTeX 2ε 编程环境“expl3”的参考文档,“LaTeX3 界面”,说道:
区分标记的两个方面很重要:其“形状”(找不到更好的词来表达),它会影响分隔参数的匹配和包含此标记的标记列表的比较;其“含义”,它会影响标记是否扩展或执行什么操作。可以有不同形状但含义相同的标记,但不能有相反的形状。
基本上,您要求检测宏标记的后续未限定参数是否是由类别 1(开始组)的显式字符标记和类别 2(结束组)的匹配显式字符标记所包围的参数。{1}2
我知道一种方法后抓取未限定的宏参数 - 它作为未被类别 1(开始组)的显式字符标记和类别 2(结束组)的匹配显式字符标记包围的单个标记提供,它作为被类别 1(开始组)的显式字符标记和类别 2(结束组)的匹配显式字符标记包围的标记星座提供 - 检测该参数的第一个标记是否是类别 1(开始组)的显式字符标记:
\makeatletter
% Compile with latex and look at the messages on the console/in the .log-file.
%
%%=============================================================================
%% Check whether argument's first token is an explicit character token
%% of category 1 (begin group):
%%.............................................................................
%% \UD@CheckWhetherBrace{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked has a leading
%% explicit catcode-1-character-token>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked does not have a
%% leading explicit catcode-1-character-token>}%
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00 }%
\newcommand\UD@CheckWhetherBrace[1]{%
\romannumeral\expandafter\UD@secondoftwo\expandafter{\expandafter{%
\string#1.}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@firstoftwo}{%
\expandafter\UD@stopromannumeral\UD@secondoftwo}%
}%
\newcommand\macro[1]{%
The first token of the argument of \string\macro\space is
\UD@CheckWhetherBrace{#1}{indeed}{not}
an explicit character token of category 1(begin group).
}%
\makeatother
\message{%
^^J%
\macro{no braces}%
}%
\message{%
^^J%
\macro{braces {in} the middle}%
}%
\message{%
^^J%
\macro{{braces} as leading tokens}%
}%
\message{%
^^J%
\macro{{everything in braces}}%
}%
\stop
控制台和.log文件中的消息:
The first token of the argument of \macro is not an explicit character token of
category 1(begin group).
The first token of the argument of \macro is not an explicit character token of
category 1(begin group).
The first token of the argument of \macro is indeed an explicit character token
of category 1(begin group).
The first token of the argument of \macro is indeed an explicit character token
of category 1(begin group)
但这并不是你想要的,因为这并没有测试形成的标记未限定的参数是封闭式在花括号/中,被一个类别 1(开始组)的显式字符标记和一个类别 2(结束组)的匹配显式字符标记包围。这确实测试了第一个标记是否成型论据是左花括号 / 是第 1 类(开始组)的明确字符标记。
我也不知道有什么完全可靠的方法前抓取未分隔的宏参数,检测该未分隔的宏参数是否作为单个标记提供,而不是由类别 1(开始组)的显式字符标记和类别 2(结束组)的匹配显式字符标记括起来,我也不知道一种完全可靠的方法来前抓取一个未限定的宏参数,检测该未限定的宏参数是否由类别 1(开始组)的显式字符标记和类别 2(结束组)的匹配显式字符标记所包围的标记星座提供,该星座将在收集形成参数的标记的过程中被剥离。
为了检测宏标记后面是否跟着形成无分隔宏参数的标记,这种宏参数属于“由第 1 类(开始组)的显式字符标记和第 2 类(结束组)的匹配显式字符标记所包围的标记序列”,您需要一些功能来允许提前查看标记流中跟在所讨论的宏标记后面的下一个非空间标记的“形状”。
坏消息是:在 TeX 引擎中(LuaTeX 除外),没有任何工具可以提前查看标记流中跟在宏标记之后的下一个标记的“形状”。
好消息是:你可以让 TeX 进行“有根据的猜测”,方法是使用 和 的组合,而不是让 TeX 提前查看标记流中跟随宏标记的下一个标记的“形状”,让 TeX 提前查看标记流中跟随宏标记的下一个标记的含义\futurelet
。\ifx
那些实现 LaTeX 2ε 宏的人\@ifnextchar
用于\futurelet
编写一个有趣的循环,用于提前查看标记流中跟随宏标记的下一个非空间标记的含义:
\documentclass{amsart}
\makeatletter
\newcommand{\test}{\@ifnextchar\bgroup{\test@brace}{\test@nobrace}}%
\newcommand\test@brace[1]{The argument is formed by the following tokens: \texttt{\detokenize{#1}}.}%
\newcommand\test@nobrace{\PackageError{gaoqiang}{Missing braced argument}{}}%
\makeatother
\begin{document}
\hrule\bigskip
\noindent
Here the educates guess works out and you get the expected text:
\smallskip
\noindent
\verb|\test{UNDELIMITED ARGUMENT ENCLOSED BY BRACES}|:\\
\test{UNDELIMITED ARGUMENT ENCLOSED BY BRACES}
\bigskip\hrule\bigskip
\noindent
Here the educates guess works out and you get the expected error message:
\smallskip
\noindent
\verb|\test THIS YIELDS AN ERROR.|:\\
\test THIS YIELDS AN ERROR.
\bigskip\hrule\bigskip
\noindent
Here the educates guess does \textbf{not} work out---although with the following
there is no \verb|{|, you do not get an error-message:
\smallskip
\noindent
\verb|\test\bgroup THERE SHOULD BE AN ERROR MESSAGE BUT THERE IS NONE|:\\
\test\bgroup THERE SHOULD BE AN ERROR MESSAGE BUT THERE IS NONE
\bigskip\hrule\bigskip
\noindent
Besides this, the way in which \verb|\@ifnextchar| is implemented in edge cases
lets you create surprising results\dots
\makeatletter
\smallskip
\noindent
\verb|\test \reserved@d THERE SHOULD BE AN ERROR MESSAGE BUT THERE IS NONE.|:\\
\test \reserved@d THERE SHOULD BE AN ERROR MESSAGE BUT THERE IS NONE.
\smallskip
\noindent
\dots and surprising error-messages:
\smallskip
\noindent
\verb|\test \@sptoken THE ERROR-MESSAGE DIFFERS FROM ``Missing braced argument''.|:\\
\test \@sptoken THE ERROR-MESSAGE DIFFERS FROM ``Missing braced argument''.
\bigskip\hrule
\end{document}
控制台/终端和.log 文件中的消息摘录:
[...]
! Package gaoqiang Error: Missing braced argument.
See the gaoqiang package documentation for explanation.
Type H <return> for immediate help.
...
l.30 \test T
HIS YIELDS AN ERROR.
?
[...]
! Use of \reserved@c doesn't match its definition.
<recently read> \@sptoken
l.66 \test \@sptoken
THE ERROR-MESSAGE DIFFERS FROM ``Missing braced argumen...
?
[...]
除了不是百分之百可靠之外,通过检查含义而不是形状来进行有根据的猜测\@ifnextchar
还有\futurelet
另一个缺点:\futurelet
是赋值。赋值不是在扩展阶段进行的。它们是在处理的后期阶段进行的。因此,让 TeX “提前”查看以下标记的含义在仅进行扩展的上下文中不起作用。例如,这样的上下文是
\edef
在或方面,收集 ⟨定义⟩ 的 ⟨平衡文本⟩ 和 ⟨右括号⟩ 过程中的标记扩展\xdef
。\csname
在收集形成⟨控制序列标记⟩的名称和匹配的明确字符标记序列的过程中,对标记进行扩展\endcsname
。- 扫描⟨
{
一般文本⟩时进行扩展。 \write
或的⟨一般文本⟩的扩展\message
。- 在收集 ⟨number⟩ 的第一个标记以及可能的后续标记时进行扩展。
- 扫描⟨一个可选空格⟩时进行扩展,例如,使用某些类型的⟨number⟩。
- 在扫描 的⟨内部数量⟩ 时进行扩展
\the
。 - ...