下面的代码
\def\foo#1#2\baz{\#1: -#1- \quad \#2: -#2-}
1) \foo hello world\baz
2) \foo supercalifragilistiexpialidocious\baz
3) \foo a \baz
4) \foo a\baz
%5) \foo \baz % error!
\bye
产量
对我来说,情况 1) 到 3) 完全可以理解。令人惊讶的是,甚至 4) 也能正常工作:第二个参数#2
变为空(两个连字符呈现为短划线)。但情况 5) 却不成立,Runaway argument? ! Paragraph ended before \foo was complete
而是将两个参数都识别为空。
我不会介意还情况 4) 失败了:情况 4) 和情况 5) 都失败了,在我看来似乎“合乎逻辑”。或者我希望它们都能奏效。唉,我的逻辑很少值得信赖:-)
答案1
从\def\foo#1#2\baz{..}
,位#1#2
意味着#1
不是分隔符,因此它要么是单个标记,要么是带括号的参数,因此命令会抓取单个标记或带括号的组(其中一个参数为空的唯一方法是使用{}
);之后,该#2\baz
部分表示#2
的确分隔符,因此它抓取直到\baz
(删除可能的外括号,只需放入下一个即可将其留空\baz
)。在案例 4 中,它抓取第一个参数,然后直到\baz
第二个参数;在案例 5 中,它抓取第一个参数(即\baz
),然后抓取直到\baz
但没有\baz
剩余(它已被抓取为第一个参数)因此它无法抓取第二个参数,因此出现错误消息。
也许从嵌套命令的角度来思考会有所帮助,每个命令都抓取一个参数。\def\foo#1#2\baz{(1:#1) and (2:#2)}
可以这样做
\def\foo#1{(1:#1) and \footwo}
\def\footwo#1\baz{(2:#1)}
答案2
主题介绍
TeXbook 的第 24 章:垂直模式摘要,此章中 TeX 的词性以 Backus-Naur 表示法的修改形式定义,表明通过原语 、 、 定义宏的\def
模式\gdef
是:\edef
\xdef
⟨前缀⟩ \def
| \gdef
| \edef
| \xdef
⟨控制序列⟩⟨参数文本⟩⟨左括号⟩⟨平衡文本⟩⟨右括号⟩
⟨前缀⟩ 要么为空,要么是部分或全部原语\global
/\outer
/\long
/ 的组合\protected
。
⟨控制序列⟩ 是控制字标记或控制符号标记或活动字符标记。
⟨左括号⟩ 是类别 1(开始组)的明确字符标记
。⟨右括号⟩ 是类别 2(结束组)的明确字符标记。
在 TeXbook 的第 20 章:定义(也称为宏)中 ⟨平衡文本⟩ 也称为 ⟨替换文本⟩.
⟨参数文本⟩ 不包含 ⟨左括号⟩ 和不 ⟨右括号⟩ 并且最多可以有 9 个参数,#1
至#9
,并且必须按顺序编号。#
代表第 6 类(参数)的任何显式字符标记。在定义宏时,⟨ 中的参数参数文本⟩ 可以通过在后面放置标记来以相当通用的方式进行界定。
仅在⟨中引入参数参数文本⟩ 可能发生在 ⟨替换文本⟩.
在定义宏时提供参数意味着在扩展相关宏的实例并为每个参数提供替换文本时,TeX 会从标记流中收集一组标记,并在 ⟨替换文本⟩ 用相应的标记集替换相应的参数。
为替换 ⟨ 的参数而收集的一组标记替换文本⟩ 被称为参数/宏参数。
因此 ⟨参数文本宏定义的⟩ 可以被看作是一种模式,在从标记流中收集构成该实例的参数的标记集时,TeX 在扩展相关宏的实例时要遵循该模式。
在定义宏时,你可以有⟨参数文本⟩ 和参数。
在展开宏实例时,涉及宏参数。
如果在⟨参数文本宏的⟩⟨定义⟩ 一个参数是被界定的,那么在扩展该宏的一个实例时,相应的宏参数(其标记要从标记流中收集)也是由标记集界定的,该标记集在 ⟨参数文本⟩ 构成相应参数的分隔符。
微妙之处:
在这个答案中“⟨替换文本⟩”(带有“⟨”和“⟩”)表示⟨平衡文本⟩ 在定义宏时。⟨替换文本⟩ 可能包含参数#1
, #2
, ...,#9
这些参数在 ⟨ 中引入参数文本⟩。
在此答案中,“替换文本”(不带“⟨”和“⟩”)表示在扩展宏实例时获得的标记。在“替换文本”中,参数被构成相应宏参数的标记替换。
这个答案的重点不是提供⟨参数文本⟩ 定义宏时。
这个答案的重点是在扩展宏实例时 TeX 如何从标记流中收集要形成宏参数的标记集。
代币是否可以\outer
出现在⟨参数文本⟩?标记
可以\outer
作为宏参数的组成部分吗?
\outer
不能成为 ⟨ 一部分的代币参数文本⟩.
\outer
标记也不能成为宏参数的组成部分。
\outer
代币可以进入⟨替换文本\edef
⟩ 使用...技巧进行宏定义\noexpand
:
\outer\def\foo{This is foo and foo is outer.}
\edef\bar{%
This is bar and it calls
{\noexpand\tt\noexpand\string\noexpand\foo:}
\noexpand\foo
}
\foo
\bar
%This would yield an error:
% ! Forbidden control sequence found while scanning definition of ...
%\def\bar{This is bar and it calls {\tt\string\foo:} \foo}
\bye
关于\long
和简短
如果宏是根据 定义的\long
,例如\long\def\macro#1#2{...}
,则该宏的所有实例的参数都可以包含标记\par
。通俗地说
,根据 定义的宏\long
称为“长宏”。
如果宏不是定义的术语\long
,例如\def\macro#1#2{...}
,如果其一个实例中的参数包含令牌\par
,则会在控制台/终端上触发一条错误消息,提示“参数失控?”
例如,类似于以下控制台输出:
*\def\macro#1#2{Argument 1: #1; Argument 2: #2}
*\macro{\par argument 1}{argument 2}
Runaway argument?
{
! Paragraph ended before \macro was complete.
<to be read again>
\par
<*> \macro{\par
argument 1}{argument 2}
?
与 -前缀不同\long
,不 \short
-前缀。尽管如此,宏不是定义的术语\long
有时通俗地称为“短宏”。
该\long
机制检测标记,\par
无论其是否\par
具有其通常含义(作为“告诉” TeX 已到达段落末尾并且 TeX 应排版该段落的内容)或是否\par
被重新定义。\long
-mechanism 不会不是检测除\par
其含义等于\par
-primitive之外的其他标记。
例如,之后
\let\pur=\par
\def\par{Hello!}
机制\long
仍然会触发\par
失控参数消息,\pur
而不是引发失控争论信息的触发器。
关于 TeX 收集宏参数的时间顺序
扩展宏实例时,TeX 采用 ⟨参数文本⟩ 来自相应的 ⟨定义⟩ 用于逐个收集宏参数的指令来自令牌流。
例如,使用定义
\def\macro#1#2\DelimiterOne#3#4\DelimiterTwo\DelimiterTwo{%
⟨replacement text where parameters #1/#2/#3/#4 may occur⟩%
}
这 ⟨参数文本⟩ 是:#1#2\DelimiterOne#3#4\DelimiterTwo\DelimiterTwo
。
该参数#1
未定界。
该参数#2
由标记 定界\DelmiterOne
。
该参数#3
未定界。
该参数#4
由标记 定界\DelmiterTwo\DelimiterTwo
。
因此,在扩展 TeX 实例时,\macro
首先根据收集未限定参数的规则从标记流中收集一组标记。该标记集是此 实例的第一个参数\macro
。在传递替换文本时,TeX 会替换#1
的参数\macro
⟨替换文本⟩ 由构成 的此 实例的第一个参数的标记集合组成\macro
。
然后 TeX 根据收集分隔参数的规则从标记流中收集一组标记,其中分隔符由标记 组成\DelimiterOne
。该标记集合是 的此 实例的第二个参数\macro
。在传递替换文本时,TeX 会替换#2
的\macro
⟨替换文本⟩ 是构成 的此实例的第二个参数的标记集\macro
。
然后 TeX 根据收集无界参数的规则从标记流中收集一组标记。该标记集是 的此实例的第三个参数\macro
。在传递替换文本时,TeX 会替换#3
的\macro
⟨替换文本⟩ 由构成 的此实例的第三个参数的标记集合组成\macro
。
然后 TeX 根据收集分隔参数的规则从标记流中收集一组标记,其中分隔符由标记 组成\DelimiterTwo\DelimiterTwo
。该标记集合是 的此实例的第四个参数\macro
。在传递替换文本时,TeX 会替换#4
的参数\macro
⟨替换文本⟩ 通过构成此实例的第四个参数的标记集\macro
。
有两种宏参数:
1. 未限定的宏参数
未分隔的宏参数由 ⟨ 中的未分隔参数表示参数文本⟩. 未限定参数是指其后接有 ⟨左括号⟩ 包含 ⟨替换文本⟩ 或者后面跟着另一个参数。
构成无界参数的一组标记可能有两种形式:
单个代币既不是类别 1(开始组)的显式字符标记,也不是类别 2(结束组)的显式字符标记。也就是说,一个既不是左花括号也不是右花括号的单个标记。 从今以后,这种东西被称为
{1
}2
未封闭的未限定参数。一组标记,包含在类别 1(开始组)的前导显式字符标记和类别 2(结束组)的尾随显式字符标记中,并包含类别 1(开始组)的显式字符标记和类别 2(结束组)的显式字符标记,其中类别 1(开始组)的每个显式字符标记恰好有一个匹配的类别 2(结束组)的显式字符标记,反之亦然。也就是说,它是一组用左花括号和右花括号括起来的标记,如果其中有更多花括号,则每个左花括号恰好有一个匹配的右花括号,每个右花括号恰好有一个匹配的左花括号。
从今以后,这样的东西被称为封闭的未限定参数。
使用这个术语,你可以说:封闭的未限定参数由形成封闭的内容和被封闭的内容组成。(形成被封闭内容的标记集可能为空。)
在扩展宏时,将传递构成该宏的替换文本的标记。因此,在 ⟨ 中出现的未限定参数(#1
或或...)#2
替换文本⟩ 替换如下:
如果未限定参数恰好是未封闭的未限定参数,该参数将被构成未封闭的未限定参数的标记替换。
如果未限定参数恰好是封闭的未限定参数,参数被构成封闭内容的标记替换封闭的未限定参数,尽管类别 1(开始组)的显式字符标记和类别 2(结束组)的显式字符标记形成封闭的是丢弃/移除/剥去。
如果一个宏将一个参数传递给另一个宏,则类似
\def\foo#1{\bar#1!}
\def\bar#1{\string\bar's argument is in parentheses: (#1)}
,其中感叹号表示 的参数的最后一个标记的位置\foo
,括号中的内容被处理为 的参数\bar
,那么每个宏可能会删除一层周围的花括号:
\foo{{Argument}}
收益率\bar{Argument}!
,进而收益率\string\bar's argument is in parentheses: (Argument)!
。\foo{{A}rgument}
收益率\bar{A}rgument!
,进而收益率\string\bar's argument is in parentheses: (A)rgument!
。\foo{Argument}
收益率\bar Argument!
,进而收益率\string\bar's argument is in parentheses: (A)rgument!
。
当 TeX 开始从标记流中收集未限定的宏参数时,它会默默地丢弃显式空格标记(即类别 10(空格)和字符代码 32 的明确字符标记),直到找到不是明确空格标记的标记。
该标记可能不是类别 1(开始组)或 2(结束组)的显式字符标记,因此形成未封闭的未限定的宏参数。
该标记也可能是类别 1(开始组)的显式字符标记,从而形成封闭的未限定宏参数的左侧。
丢弃显式空格标记是为什么在未限定的宏参数之间/之前有空格与在未限定的宏参数之间/之前没有空格没有区别的原因:
\def\ProcessTwoNonDelimitedArgs#1#2{Arg1:(#1), Arg2:(#2)}
% A <space token> between {Argument1} and {Argument2}:
\ProcessTwoNonDelimitedArgs{Argument1} {Argument2}
% No <space token> between {Argument1} and {Argument2}:
\ProcessTwoNonDelimitedArgs{Argument1}{Argument2}
\bye
2. 分隔宏参数
分隔宏参数由 ⟨ 中的分隔参数表示参数文本⟩. 分隔参数是指 ⟨参数文本⟩ 接下来的一组标记既不包含第 1 类(开始组)的显式字符标记,也不包含第 2 类(结束组)的显式字符标记,也不包含第 6 类(参数)的显式字符标记。这样的一组标记称为参数的定界符。
分隔宏参数是一个宏参数,它由 ⟨ 中的一组标记的实例分隔。参数文本⟩ 的 ⟨定义宏的⟩构成相应参数的分隔符。
即,分隔宏参数由一组不包含左花括号、右花括号或显式井号的标记分隔。
(,哈希,是#6
明确的类别 6(参数)的字符标记。示例隐式类别 6(参数)的字符标记是类别 6(参数)的隐式哈希字符标记,\hash
并U
带有\let\hash=#
或\catcode`\U=13 \letU=#
。#
未展开书写等时的 -doubling 仅发生在类别 6(参数)的显式字符标记中。)
对于分隔参数,分隔标记集始终位于参数的正后方。对于带分隔符的宏参数,分隔符集始终位于宏参数的正后方。
如果构成类别 6(参数)隐式字符标记的标记在定义时还不是类别 6(参数)隐式字符标记,但之后通过重新定义将它们转变为类别 6(参数)隐式字符标记,则参数分隔符可以包含类别 6(参数)隐式字符标记。
\let\ImplicitHash=# %
\def\Macro#1\ImplicitHash\ImplicitHash#2\ImplicitHash{Arg1:(#1), Arg2:(#2)}
\Macro{A1}\ImplicitHash\ImplicitHash{A2}\ImplicitHash
\bye
不起作用,而
\def\Macro#1\ImplicitHash\ImplicitHash#2\ImplicitHash{Arg1:(#1), Arg2:(#2)}
\let\ImplicitHash=# %
\Macro{A1}\ImplicitHash\ImplicitHash{A2}\ImplicitHash
\bye
确实有效。
当 TeX 为宏收集分隔的宏参数时,会从标记流中收集标记,直到
- 找到构成匹配参数分隔符的标记,
- 或者很明显需要引发错误消息。
在收集分隔宏参数的标记时,一系列的 token 看起来像⟨ 中的那些 token参数文本对应宏定义形式的⟩参数分隔符,仅当它与宏标记出现在同一个括号组中时才被视为参数分隔符所讨论的分隔参数所属的位置。
即:假设在标记流中有一系列标记,乍一看它们像是在 ⟨ 中构成参数分隔符的标记参数文本宏定义的 ⟩。但是,这些标记与收集宏参数的宏标记不在同一个花括号组中。因此,TeX 不会将此标记序列用作匹配的参数分隔符。
采用这样的序列作为匹配的参数分隔符意味着分隔宏参数的可能性,其中类别 1(开始组)或 2(结束组)的显式字符标记没有匹配的对应项(即,花括号没有匹配的对应项)。
所以,正确的花括号嵌套/适当平衡第 1 类(开始组)的明确字符标记与第 2 类(结束组)的匹配明确字符标记在搜索参数分隔符时被考虑。
例如,定义之后
\def\macro#1\deli\miter{This is the argument: (#1)}
调用\macro
如下
\macro PartOfArgument{\deli\miter}OtherPartOfArgument\deli\miter
从#1
⟨参数文本此调用的定义中的 ⟩ 被视为占位符
PartOfArgument{\deli\miter}OtherPartOfArgument
。
如果找到匹配的参数分隔符,则丢弃构成参数分隔符的标记。找到并丢弃匹配的参数分隔符后,TeX 会检查到目前为止为宏参数收集的整个标记集(如果立即找到分隔符,则该集合可能为空)是否包含在类别 1(开始组)的前导显式字符标记和类别 2(结束组)的尾随显式字符标记中。即,在找到并丢弃匹配的参数分隔符后,TeX 会检查到目前为止为宏参数收集的整个标记集是否都包含在花括号标记中。如果是,则删除这两个标记。(如果 TeX 在查找分隔符时仅收集了第 1 类(开始组)的显式字符标记,然后是第 2 类(结束组)的显式字符标记,则这可能导致空值 - 也就是说,如果 TeX 在找到分隔符之前仅收集了一对匹配的花括号。)
在某些情况下,您希望避免从分隔宏参数中删除最外层的花括号。这可以通过在实际参数前面放置一个标记(该标记不会意外/错误地与参数分隔符匹配)并在扩展链的末尾删除此标记来轻松实现。
例如:(
正在\detokenize
使用,因此这次需要 eTeX 扩展 ;-))
\def\DoNothing{}%
\def\RemoveDot.{}%
\def\macro#1#2\delimited{\detokenize\expandafter{#1#2}}%
\tt
% Stripping of curly braces with the delimited argument will take place.
% #2 will be: 'Delimited Argument'
\macro{\DoNothing}{Delimited Argument}\delimited
% Due to the leading dot, stripping of curly braces with the delimited argument will not take place.
% #2 will be: '.{Delimited Argument}'
\macro{\RemoveDot}.{Delimited Argument}\delimited
\bye
当 TeX 开始收集分隔的宏参数时,它不会丢弃任何标记。它不会丢弃空格标记。当 TeX 开始收集分隔的宏参数时,任何可能被用作前一个空格标记的东西都是构成宏参数的标记集的一部分。
让我们展示一下这些微妙之处:
假设␣
表示一个空格,\macro
在使用带有 eTeX 扩展的 LaTeX2e 时定义如下:
\def\macro#1#2\delimited{%
Undelimited argument: \expandafter\verb\scantokens{*|#1|};
Delimited argument: \expandafter\verb\scantokens{*|#2|}%
}%
调用时\macro{Undelimited}Delimited\delimited
,在分隔参数处没有 ⟨太空代币⟩ 并且没有周围的花括号,因此得到:
未限定参数:Undelimited
;限定参数:Delimited
调用时\macro{Undelimited}␣Delimited\delimited
,在分隔参数处有一个⟨太空代币⟩ 并且没有周围的花括号,因此得到:
未限定参数:Undelimited
;限定参数:␣Delimited
调用时\macro{Undelimited}{Delimited}\delimited
,在分隔参数处没有 ⟨太空代币⟩ 并且有周围的花括号——周围的花括号被删除,因此得到:
未限定参数:Undelimited
;限定参数:Delimited
调用时\macro{Undelimited}␣{Delimited}\delimited
,在分隔参数处有一个⟨太空代币⟩ 并且有花括号。这次它们没有包围整个分隔参数,因为 ⟨太空代币⟩ 也在那里。因此这次它们不会被移除:
未限定参数:Undelimited
;限定参数:␣{Delimited}
#{
TeX:符号中有一个微妙之处:
当通过\def
给出宏的定义时,⟨替换文本⟩ 必须包含在类别 1(开始组)的显式字符标记和类别 2(结束组)的显式字符标记中。即,当通过\def
给出宏定义时,⟨替换文本⟩ 必须用花括号括起来。
因此你可以写:
\def\macro{replacement text}
{\tt \meaning\macro}
\bye
当通过\def
给出宏的定义时,作为特殊情况,⟨ 的最后一个标记参数文本⟩ 可以是类别 6(参数)的单个字符标记,可以是显式的,例如哈希 ( ),也可以是隐式的。如果是这种情况,则类别 1(开始组)的显式字符标记构成 ⟨ 的左侧#6
替换文本⟩ 也被视为最后一个宏参数的分隔符的最后一个标记。通常,在收集要形成分隔宏参数的标记时,形成参数分隔符的标记会被丢弃。但在这个特殊情况下,最后一个宏参数的参数分隔符的最后一个标记不仅会被丢弃,而且会被丢弃并重新插入,这就像将其留在原处一样,并且在扩展相关宏后,该标记会出现在替换文本的正后方。
TeXbook 中解释了您可以使用此#{
功能使某些宏的最后一个宏参数由留在原处的左花括号分隔,以便它可以作为左花括号/作为后续封闭的未限定宏参数的左侧,而该宏参数又将在扩展级联过程中产生的另一个宏标记时被收集。
例子:
\tolerance 9999
\emergencystretch 3em
\hfuzz 0pt \vfuzz \hfuzz
\parindent=0ex
%-------------------------------------------------------------------------------------------------
\let\ImplicitHash=#
\def\macroA#1#2\relax#{Argument1:(#1), Argument2:(#2) The meaning of the next token: \meaning}
\def\macroB#1#2\relax\ImplicitHash{Argument1:(#1), Argument2:(#2) The meaning of the next token: \meaning}
\def\Weird{Whatsoever}
\tt\frenchspacing
\meaning\macroA
\noindent\hrulefill\null
\macroA{Arg 1}{Arg 2}\relax{ The meaning of the next token: \meaning}
\noindent\hrulefill\null
\meaning\macroB
\noindent\hrulefill\null
\macroB{Arg 1}{Arg 2}\relax{ The meaning of the next token: \meaning}
\bye
直到 2021 年 1 月,TeX 版本 3.141592653 才出现边缘问题
(2009 年,usenet 新闻组 comp.text.tex 中的其他人引起了我的注意。例如,请参阅 usenet-discussion“{ } 和 bgroup 和 egroup 之间有什么区别吗?”)
当通过\def
给出宏的定义时,作为特殊情况,⟨ 的最后一个标记参数文本⟩ 可以是类别 6(参数)的单个字符标记,可以是显式的,例如哈希(#6
),也可以是隐式的。在这种特殊情况下,您不必局限于 ⟨ 的左侧替换文本⟩ 是类别 1(开始组)的显式字符标记。在这种特殊情况下,⟨ 的左侧替换文本⟩ 也可能是类别 1(开始组)的隐式字符标记。展开相关宏后,表示类别 1(开始组)的隐式字符标记的标记将出现在替换文本的正后方,就像如果宏的替换文本的左侧是类别 1(开始组)的显式字符标记,则类别 1(开始组)的显式字符标记将出现在替换文本的正后方一样。
即使在定义相关宏时表示类别 1(开始组)的隐式字符标记的标记在定义相关宏之后被重新定义,情况也是如此。
例子:
\let\Weird={
\let\ImplicitHash=#
\def\macroA#1#2\relax#\Weird Argument1:(#1), Argument2:(#2)\string}
\def\macroB#1#2\relax\ImplicitHash\Weird Argument1:(#1), Argument2:(#2)\string}
\def\Weird{Whatsoever}
\tt
\meaning\macroA
\macroA{Arg 1}{Arg 2}\relax\Weird
\meaning\macroB
\macroB{Arg 1}{Arg 2}\relax\Weird
\bye
关于 TeX 收集宏参数,需要牢记的最重要的事情是:
TeX 从标记流中逐个收集宏参数。
如果收集分隔宏参数,TeX 会从标记流中收集标记,直到找到分隔符。这意味着,如果 TeX 在找到分隔符时尚未收集任何标记,则分隔宏参数可以为空。
当收集未限定的宏参数时,TeX 会丢弃前面的显式空格标记。
在收集分隔宏参数时,TeX 不会丢弃显式空格标记。对于分隔宏参数,任何可能被用作前一个显式空格标记的内容都是构成宏参数的标记集的一部分。
如果存在,则将丢弃/删除/剥离整个宏参数周围的一层花括号,无论是分隔的宏参数还是未分隔的宏参数。
按照你的定义
\def\foo#1#2\baz{\#1: -#1- \quad \#2: -#2-}
\foo
无论如何,TeX 在扩展时首先收集一个未分隔的宏参数,然后收集一个分隔的宏参数,其中\baz
分隔符是:
\foo hello world\baz
在扩展时,
\foo
TeX 会收集未分隔的宏参数的标记,丢弃前面的空格标记(如果存在),然后丢弃围绕整个宏参数的花括号(如果存在):
h
然后 TeX 通过收集标记直到找到分隔符来获取分隔的宏参数
\baz
,然后(如果存在)丢弃迄今为止收集的整个标记集周围的花括号:
ello world
\foo supercalifragilistiexpialidocious\baz
在扩展时,
\foo
TeX 会收集未分隔的宏参数的标记,丢弃前面的空格标记(如果存在),然后丢弃围绕整个宏参数的花括号(如果存在):
s
然后 TeX 通过收集标记直到找到分隔符来获取分隔的宏参数
\baz
,然后(如果存在)丢弃迄今为止收集的整个标记集周围的花括号:
upercalifragilistiexpialidocious
\foo a \baz
在扩展时,
\foo
TeX 会收集未分隔的宏参数的标记,丢弃前面的空格标记(如果存在),然后丢弃围绕整个宏参数的花括号(如果存在):
a
然后 TeX 通过收集标记直到找到分隔符来获取分隔的宏参数
\baz
,然后(如果存在)丢弃迄今为止收集的整个标记集周围的花括号:由于空格标记只有当它们位于未分隔的宏参数之前时才会被删除,而这是一个分隔的宏参数,因此 ⟨太空代币a
和-delimiter之间的⟩\baz
将被收集为宏参数。\foo a\baz
在扩展时,
\foo
TeX 会收集未分隔的宏参数的标记,丢弃前面的空格标记(如果存在),然后丢弃围绕整个宏参数的花括号(如果存在):
a
然后 TeX 通过收集标记直到找到分隔符来获取分隔的宏参数
\baz
,然后(如果存在)丢弃迄今为止收集的整个标记集周围的花括号:在这种情况下,立即找到分隔符,但尚未收集任何标记。因此,在这种情况下,宏参数为空。