如果你想知道这条规则有什么用途:

如果你想知道这条规则有什么用途:

我不太明白教科书练习 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

相关内容