我不太明白教科书练习 20.5 后面的特殊规则:
允许对这些规则进行特殊扩展:如果 < 参数文本 > 的最后一个字符是
#
,因此#
紧接着是{
,则 TEX 将表现得好像{
已插入到参数文本和替换文本的右端。例如,如果您说\def\a#1#{\hbox to #1}
,则后续文本\a3pt{x}
将扩展为\hbox to 3pt{x}
,因为 的参数\a
由左括号分隔。
如果我理解正确(但事实并非如此),如果{
替换文本的左括号前面紧跟着一个井号,#
那么#{
这个左括号同时也是一个分隔符,标记了替换文本的开始,并表明这个括号也是参数文本的一部分(一般情况下它永远不可能是)。
但是,我不明白为什么这个\def\a#1#{\hbox to #1}
适用于例子的定义\a3pt{x}
会扩展到\hbox to 3pt{x}
。
为什么 里边有个“x” 3pt{x}
?
我不明白给出的解释:
因为 \a 的参数由左括号分隔
使用 www.DeepL.com/Translator 翻译(免费版)
答案1
使用\def\a#1#{\hbox to #1}
如下调用
\a3pt{x}
使 TeX find3pt
作为(分隔)参数。这将触发替换,然后你会得到
\hbox to 3pt{x}
左括号尚未从输入流中删除。 的参数是和第一个标记\a
之间的任何内容。\a
{
宏的参数有两种类型:未限定和分隔。在参数文本中,未定界参数的形式为#<n>
(使用<n>
1 到 9 之间的整数)后面直接跟下一个#<n+1>
参数或以{
1(左括号)开头的替换文本。否则,参数为分隔。
在调用宏时确定参数时,TeX 会区分带分隔符和不带分隔符的参数。如果要查找不带分隔符的参数,则有两种情况:如果后面跟着{
1 个标记,则参数是直到匹配的}
2 个标记为止的整个标记序列;否则下一个标记就是参数。
如果 TeX 正在寻找分隔参数,它将吸收标记,直到找到指定为分隔符的标记的精确序列。所有吸收的标记(不包括分隔标记)构成参数;分隔标记将被丢弃。在此过程中,TeX 不会对吸收的标记进行任何解释。
异常,与练习 20.5 直接相关:如果参数文本是#1#
(但可能更复杂),则参数的分隔符#1
(通常是最后一个参数)是{
1,即不是就像使用标准分隔参数一样被丢弃。
答案2
你可以这样想:
\def\foo#1;{}
将读取之后的所有内容,\foo
直到出现;
,因此也\foo 123;
将读取123
并从输入流中#1
删除。;
相似地
\def\foo#1#{}
将创建一个具有右分隔参数的宏,但;
右分隔符不是{
,因此它会读取到下一个左括号之前的所有内容,但与第一种情况相反,在这种情况下{
不会从输入流中删除(实际上它将被删除,但会由\foo
的替换文本重新插入)。因此\def\foo#1#{}
行为类似\def\foo#1;{;}
,但使用{
而不是 作为右分隔符;
。
通过查看\meaning
(或\show
定义),您可以看到左括号实际上已被删除,然后重新插入:
\def\foo#1#{}\show\foo
\def\foo#1#{foo}\show\foo
将打印
> \foo=macro: #1{->{. > \foo=macro: #1{->foo{.
到终端。
编辑:这试图回答评论
据我了解,参数文本是一个正则表达式。在此正则表达式中,左括号
{
是替换文本的分隔符,因此在替换时会被删除。当它紧接着之前时,情况并非如此#
。这条规则的目的是什么?
如果 TeX 也删除该左括号,则剩下的将是一个不平衡的标记列表(不匹配的右括号)。因此,如果以下宏创建得不够仔细,这将引发错误,并且创建具有相同逻辑的宏将更加困难。因此,此规则的唯一目的是您可以创建由类别代码 1 的标记({
正常类别代码中的 a)右分隔的宏,而无需大量宏来清理现在不平衡的输入流。
想象一下以下情况:
\def\foo#1#{}
\foo 123{abc}
在定义\foo
和扩展之后,如果没有{
重新插入,输入流中剩下的将是
abc}
我们必须以某种方式清理不匹配的右括号。假设我们想要创建一个宏,读取直到左括号和下一个组的所有内容,我们现在要做的是创建一个宏,抓取每个标记,直到它遇到右括号,但也会抛出一个错误,那么我们应该如何创建它呢?我们需要做的是像下面这样(请注意,我通过扩展下面的\def\bar#1}{}
一个来创建不平衡的文本):\iffalse{\fi
\documentclass[]{article}
\makeatletter
\long\def\grabuntilclosingbrace@fi@firstoftwo\fi\@secondoftwo#1#2{\fi#1}
\def\grabuntilclosingbrace
{%
\begingroup
\aftergroup\grabuntilclosingbrace@done
\grabuntilclosingbrace@a
}
\def\grabuntilclosingbrace@a
{%
\futurelet\grabuntilclosingbrace@tok\grabuntilclosingbrace@b
}
\def\grabuntilclosingbrace@b
{%
\ifx\grabuntilclosingbrace@tok\egroup
\grabuntilclosingbrace@fi@firstoftwo
\fi
\@secondoftwo
{%
\afterassignment\grabuntilclosingbrace@final
\let\afterassignment@tok=%
}
{%
\grabuntilclosingbrace@c
}%
}
\def\grabuntilclosingbrace@final
{%
\aftergroup\grabuntilclosingbrace@end
\endgroup
}
\long\def\grabuntilclosingbrace@done#1\grabuntilclosingbrace@end
{Argument was: \texttt{#1}}
\long\def\grabuntilclosingbrace@c#1{\aftergroup#1\grabuntilclosingbrace@a}
\makeatother
\begin{document}
\expandafter\grabuntilclosingbrace\iffalse{\fi abc}
\end{document}
而且这个宏不能处理空格或嵌套组。看看如果 TeX 没有给我们这个超级方便的\def\foo#1#{}
规则,生活会变得多么复杂?
如果你想知道这条规则有什么用途:
假设我们有一些宏无法处理其参数中的嵌套组,因此我们必须测试该参数是否有组,毕竟我们希望给出有用的错误消息,而不是让宏失败。因此我们需要创建一个测试嵌套组的测试。根据逻辑,我们\def\foo#1#{}
可以将其简化为测试参数是否为空(这重用了https://tex.stackexchange.com/a/517265/117050)。
\documentclass[]{article}
\makeatletter
\long\def\ifgroupin#1%
{%
\ifgroupin@a#1{}\ifgroupin@tokB\ifgroupin@false
\ifgroupin@tokA\ifgroupin@tokB\@firstoftwo
}
\long\def\ifgroupin@a#1#{\ifgroupin@b}
\long\def\ifgroupin@b#1{\ifgroupin@c\ifgroupin@tokA}
\long\def\ifgroupin@c#1\ifgroupin@tokA\ifgroupin@tokB{}
\long\def\ifgroupin@false\ifgroupin@tokA\ifgroupin@tokB\@firstoftwo#1#2{#2}
\makeatother
\begin{document}
\ifgroupin{abc}{true}{false}
\ifgroupin{a{b}c}{true}{false}
\end{document}
另一个版本使用了经典的\if\relax\detokenize{#1}\relax
空测试,因为这可能更容易理解(但花费的时间是以前实现的 160% 左右):
\makeatletter
\long\def\ifgroupin#1%
{%
\if\relax
\detokenize\expandafter\expandafter\expandafter{\ifgroupin@a#1{}}\relax
\expandafter\@secondoftwo
\else
\expandafter\@firstoftwo
\fi
}
\long\def\ifgroupin@a#1#{\@gobble}
\makeatother