在如何创建具有两个或三个 ; 分隔参数的宏?,Herbert 使用宏定义
\def\defpoint#1#{\expandafter\defpoint@i#1;;\@nil}
Joseph Wright 报告称,它有一个“grab to #{
”参数。它是如何工作的?它在哪里有用?有什么限制吗?它是否以与嵌套宏定义相同的方式扩展?例如,使用##{
if 在另一个宏中重新定义一个宏。
答案1
这是什么意思?
重复一下 Martin 给出的事实信息:定义一个宏,例如
\def\macro<other arguments>#{<replacement text>}
实现的功能等同于尝试编写
\def\macro<other arguments>{{<replacement text>{}
除了它可以工作,因为上面的代码破坏了宏定义的两个规则。首先,它不能按预期运行,因为 TeX 在扫描 时\def
会读取到第一个 来{
找到参数,一旦到达该括号,就会假定替换文本已经开始。所以你永远不能{
在参数中真正有 。其次,在扫描替换文本时,它会跳过平衡的括号对,因此{
您尝试插入的开头将阻止它在以下 处停止吸收替换文本}
。(您可以使用 来解决这个问题\bgroup
,但如果没有 ,第一个问题是无法解决的#{
。)所以这种语法实际上涵盖了这些解析规则排除的一种逻辑上的分隔参数情况。
要回答你的最后一个问题,是的,你可以写
\def\bigmacro{
\def\macro##{<stuff>}
<more stuff>
}
因此,在调用 时\bigmacro
,它首先定义\macro
其后有一个“分隔括号”。正如 egreg 和 Martin 所说,将 翻倍#
只是 TeX 语法的一部分。
为什么它会这样起作用?
好吧,规则排除了一种更普遍的情况,即假定的
\def\macro<args>{<more args>{<replacement>}
您希望{
在参数中间匹配一个。您不能用 来做到这一点#{
。我不确定,但这与事物的工作方式一致,这样 TeX 在宏扩展中的参数扫描的任何部分都不会破坏括号对。您会看到,就目前的情况而言,#{
将使用左括号作为边界用于参数抓取,但不会将其包含在抓取中,因为找到后会将其放回原处。这意味着一个组{...}
保持完整。
如果您考虑规则如何工作,就会发现这种情况总是发生在无限制扫描中。您可以使用 来破坏一个组\let
,但那是因为您在说“寻找下一个标记”;不可能写\let\a={<stuff>}
;或者更确切地说,它会导致\let\a={
后面跟着<stuff>}
(因此可能出现错误)。但是对于宏参数扫描,TeX 只会查找直到找到要调用的东西#1
,并且由于规则允许使用{...}
来形成可以捕获的“组”,因此不要跨越括号对的边界很重要。有人可能故意写它们来向参数扫描仪发出信号。
当然,不拆开括号组的另一个原因是,如果你写
{\macro <stuff> {<more stuff>} ...
如果\macro
能以某种方式捕捉到开头{
,那么<stuff>
和<more stuff>
最终会分到同一组,这可能与编写它的人的期望相反。这\aftergroup
至少会给造成一些问题。(我承认这是一个非常技术性的观点,但同样如此#{
。)
这种构造什么时候有用?
消毒
嗯,我发现它很有用我的这个答案。问题是如何将 TeX 代码可扩展地转换为“纯文本”,大致来说,我只需要使用宏扩展来查找该代码中的组,以便我可以删除它们(并且我还可以绕过它们对我自己的代码的影响)。所以我的清理器的一部分是一个宏定义如下
\def\SanitizeGroups#1#{
其结果是,我#1
知道一切之间的外观\SanitizeGroups
和第一的后面的组。换句话说,#1
不包含任何组,紧接着它有一个组。这意味着如果我使用类似
\def\SanitizeTokens#1{...}
解析以下文本的各个“标记” \SanitizeGroups
,我可以确保通过调用\SanitizeTokens
is获得的所有内容实际上一个单独的标记,而不是说里面的某个东西{...}
。
逐字论据
另一种用途:这个答案对于有关构造的问题#{
,Philippe Goutet 给出了来自 Tugboat 文章的一个例子:
\def\bold#{\bgroup\bf\let\next= }
这意味着\bold
只会“插入到\bf
紧随其后的组的开头”;#{
此处的目的是确保当您使用\let
吞掉该组的左括号时,它确实会吞掉一个括号。这迫使用户写\bold{<text>}
而不是可能\bold a
得到一个粗体字母a
,但无论如何您都必须使用 和 的括号,\bf
因此这并不算什么。这样做而不是
\def\bold#1{{\bf #1}}
(“程序员”方法,该文章中七个 TeXpertise 中仅有第三个级别)是如果您不阅读括号中的内容,它的 catcodes 就不是固定的,因此它可以包含逐字文本。
答案2
当宏的参数文本以#
之前的直接结尾时{
, 则将{
被视为参数文本的一部分和也附加到替换文本中。这意味着使用\def\foo#1#{..}
,\foo
将读取到下一个的所有内容{
作为第一个参数,但{
随后将后面插入到输入流中。因此您不能使用这样的宏来删除字符{
。此外,宏必须至少有一个包含在 中的正常(强制)参数{ }
。如果宏定义时没有任何参数,即\def\foo#{..}
,它将在使用后直接等待{
。但是空格将像往常一样被删除。在任何其他情况下,宏的行为与任何其他宏相同。
这在TeXBook在第 215 页,练习 20.5 之后。
此类宏有时用于实现带有可选参数的宏。例如,color
和xcolor
包定义\textcolor
抓取下一个之前的所有内容{
并将其作为第一个参数传递给内部宏,然后检查它是否为空。这是或的可扩展替代方案\futurelet
,\@ifnextchar
但要求对强制参数使用括号。这有时会导致问题,因为有些人喜欢在单个标记参数周围删除括号。
如果这样的宏在另一个宏中(重新)定义,则#
必须像平常一样加倍,即\def\bar{\def\foo##1##{..}}
。据我所知,的加倍#
与的实际本地目的无关#
,因此这里没有区别。