为什么在讨论 \newif 的定义时,下列说法是正确的

为什么在讨论 \newif 的定义时,下列说法是正确的

这是源于以下问题的答案理解 \newif 定义时遇到问题经过@egreg

@egreg 说

让我们回顾一下
\expandafter\expandafter\expandafter
\def\@if\iffoo{true}{\let\iffoo=\iftrue}%
的定义\@if
\def\@if#1#2{\csname\expandafter\if@\string#1#2\endcsname}
第一个\expandafter展开第三个,这又导致 的展开\@if,它有两个参数;在这种情况下,它们是
#1<-\iffoo
#2<-true

但据我了解,其工作方式\def是,它对命令名称后的非参数字符(即,除 、 、... 之外的字符)很敏感#1#2因此#9(取自 TeXbook 第 202 页)如果您有,\def\cs #1. #2\par{…}\cs需要以格式给出的参数#1. #2,您需要将.␣两者分开,例如

\cs You owe \$5.00. Pay it.\par  

给出

#1 <- You owe \$5.00 
#2 <- Pay it. 

哪里(引用 TeXbook)

\5.00 中的句号不会停止#1,因为 TeX 会继续运行,直到找到紧接着空格的句号。

而上面\def\@if#1#2不是\def\@if#1{#2},而且我可以想象后者不会起作用,因为它会解释为只接受单一参数的思考#2的定义。\@if#1\@if#1

我的第二个困难是理解以下部分

因此我们只剩下
\expandafter\def\csname\expandafter\if@\string\iffoo true\endcsname
现在剩下的部分\expandafter触发了扩展\csname,我记得,它会进行详尽的扩展,直到找到匹配的\endcsname。因此,为了知道将生成什么控制序列名称,我们必须跟踪 之后所有标记的扩展\csname
第一个标记是\expandafter,这会导致 的扩展\string;因为\escapechar-1,我们剩下
\if@ iffootrue\endcsname

但我不明白为什么\string无论是在重新分配的情况下\escapechar=-1还是在\escapechar尚未重新分配的情况下都会产生这种影响。

参照 TeXbook

\string<token>TeX 首先读取<token>不带扩展的。如果出现控制序列标记,则其\string扩展由控制序列名称组成(\escapechar如果控制序列不仅仅是一个活动字符,则包括转义字符)。

所以我只能猜测,如果它取代了\之前iffoo\iffoo

  1. 为什么\string当 是控制序列时会产生这种效果\iffoo,因此(根据我对 TeXbook 的误解)应该将其转换为其名称,即\iffoo(或者是iffoo,在这种情况下为什么需要\escapechar更改)。或者,
  2. 如果\被替换,因为它是一个“控制序列标记”,而不是“控制序列”,那么它去了哪里-1

最后,在下一节中@egreg 说

由于其定义,\if@删除if并留下footrue;所以我们有
\def\footrue{\let\iffoo=\iftrue}

我认为其\if@定义如下:

{\uccode‘1=‘i \uccode‘2=‘f \uppercase{\gdef\if@12{}}} % ‘if’ required

并且我理解第一部分将的大写值设置为和1i但我不明白为什么此部分包含在单独的括号中,也不明白为什么此定义起到删除和的作用,因为如果最初应用它似乎是未定义的,因为不是有效命令,或者将期望和作为固定参数(不是和),它将消除(因为它返回一个空字符串)。2fif\uppercase\GDEF12if

如果这看起来太深奥,我深表歉意,但我真的想理解为什么这些定义会这样起作用,我真的不知道我哪里出错了。我原本打算在原始答案的评论中提出这个问题,但@egreg 很有帮助地指出,我的问题太广泛了,评论不适合讨论这个问题。

答案1

Saying\newif\iffoo生成以下标记列表:

\count@\escapechar \escapechar\m@ne
\expandafter\expandafter\expandafter\def\@if\iffoo{true}{\let\iffoo=\iftrue}
\expandafter\expandafter\expandafter\def\@if\iffoo{false}{\let\iffoo=\iffalse}
\@if\iffoo{false}
\escapechar\count@

接下来,添加空格(或换行符)只是为了分隔标记;定义中没有,因此不生成任何标记。

我还记得辅助宏的定义\@if

\def\@if#1#2{\csname\expandafter\if@\string#1#2\endcsname}

\if@

{\uccode‘1=‘i \uccode‘2=‘f \uppercase{\gdef\if@12{}}} % ‘if’ required

做什么\if@

TeX 打开一个组,其中设置了和\uccode的新值。然后它执行。该操作将12\uppercase字符标记以大写形式显示,而不更改类别代码。生成的标记列表将放回到输入流中,并且

\gdef\if@ if{}

其中if的类别代码为 12(类似12)。空格只是为了清晰起见,没有空格标记。因此,宏\if@希望在它之后看到该对字符标记或发出错误并吞掉它们,而不会产生任何结果。接下来,}恢复和\uccode的值。if

步骤1

执行赋值\count@\escapechar\escapechar\m@ne。具体来说,escapechar 设置为 -1,这意味着\string\cs将扩展为cs(每个类别代码为 12 的字符),因为 -1 不是有效的字符代码。

第2步

\expandafter\expandafter\expandafter\def\@if\iffoo{true}{\let\iffoo=\iftrue}

TeX 跳过一个标记并扩展下一个标记,即\expandafter,因此会跳过另一个标记并扩展下一个标记。它是\@if,它接受两个参数。宏后面没有{,因此第一个参数是以下(非空格)标记,即\iffoo;后面跟着 a {,因此第二个参数是所有标记,直到匹配的 ,包括},因此{true}。回想一下,TeX 会从未分隔的参数中剥离一对括号(如果这不会留下不匹配的括号,也会从分隔的参数中剥离一对括号)。所以参数#2true\if@\iffoo{true}用替换文本替换

\csname\expandafter\if@\string\iffoo true\endcsname

再次强调,这里没有空格,我留它只是为了分隔 token。这个 token 列表中有 10 个 token。

步骤3

两个\expandafter消失了,替换文本已取代\if@及其参数,因此我们有

\expandafter\def\csname\expandafter\if@\string\iffoo true\endcsname{\let\iffoo=\iftrue}

TeX 跳过\def并扩展\csname。这最终将扩展为一个符号标记,该标记又通过扩展匹配的内容而构建\endcsname

步骤4

让我们看看 token 列表是什么样的\endcsname

\expandafter\if@\string\iffoo true

(记住,那里没有空格)。TeX 跳过\if@并扩展\string。这会产生五个字符标记iffoo(因为\escapechar是 -1),所有标记的类别代码均为 12。因此,我们剩下

\if@ iffootrue

\if@被扩展。所需的标记被找到并吞掉,所以我们得到footrue。现在 TeX 完成\csname...\endcsname构造,生成标记\footrue

步骤5

输入流有

\def\footrue{\let\iffoo\iftrue}

已执行并删除。

第 6 步

TeX 现在发现

\expandafter\expandafter\expandafter\def\@if\iffoo{false}{\let\iffoo=\iffalse}

和之前完全一样,但最终的标记列表是

\def\foofalse{\let\iffoo\iffalse}

已执行并删除。

步骤7

现在我们有

\@if\iffoo{false}

和之前一样,只生成

\let\iffoo\iffalse

因此条件一开始就是错误的。

步骤8

最后的代币

\escapechar\count@

重置\escapechar为开始之前的值\newif\iffoo

答案2

逆向思考:

{\uccode‘1=‘i \uccode‘2=‘f \uppercase{\gdef\if@12{}}} % ‘if’ required

\uppercase适用于标记,除字符标记之外的任何标记均保持不变。字符标记会生成新的字符标记,其 catcode 与之前相同,但字符代码是通过查找 uccode 获得的(如果 uccode 非零)

所以\gdef大写\gdef不是\GDEF,你会得到

\gdef\if@ if{}

它定义\if@为不接受任何参数并且扩展为不包含任何内容但会吞噬 catcode 12 i 和 f(或生成错误)。

就像这样:

\catcode`i=12
\catcode`f=12
\def\if@if{}

但这不起作用,因为fin\def需要是 catcode 11,并且ini也是如此f\if@

该构造用括号括起来,因为您不希望在文档的其余部分将 1 的大写字母显示为 i。

我不确定我是否理解了你的\string问题。\string\iffoo使 catcode 序列有 12 个标记\ i f f o o ,但第一个\标记是 指定的任何字符,\escapechar如果该字符代码不是合法字符,则为空,因此-1这里\string\iffoo生成iffoo

答案3

Knuth 的宏\newif非常神秘。我花了整整 402 页在我的“TeXbook 纳鲁比”并对此做出解释。但我们可以问一下,是否没有更直接、更易理解的解决方案来解决此问题并得到相同的结果。我的意思是,它存在。例如:

\def\newif#1{\expandafter\newifA\string#1\relax#1}
\edef\tmp{\string\if}
\expandafter\def\expandafter\newifA\tmp#1\relax#2{%
   \newifB#2{\csname#1true\endcsname}{\csname#1false\endcsname}%
}
\def\newifB#1#2#3{% #1=\iffoo  #2=\footrue  #3=\foofalse
   \let#1=\iffalse
   \expandafter\def#2{\let#1=\iftrue}%
   \expandafter\def#3{\let#1=\iffalse}%
}

相关内容