我试图了解 TeX 究竟是如何解析文档的,关于这一点, 的定义\newif
让我很困惑(在latex.ltx
,第 929ff 行):
\def\newif#1{%
\count@\escapechar \escapechar\m@ne
\let#1\iffalse
\@if#1\iftrue
\@if#1\iffalse
\escapechar\count@}
\def\@if#1#2{%
\expandafter\def\csname\expandafter\@gobbletwo\string#1%
\expandafter\@gobbletwo\string#2\endcsname
{\let#1#2}}
我理解这个定义做(即\newif\iffoo
引入三个宏\iffoo
,\foofalse
并且\footrue
),但我不明白它为何有效。以下是我对这段代码的理解:
\newif\iffoo
首先将当前转义字符存储在 中\count@
,然后将其临时重新定义为-1
。这可能是问题的关键,但我不清楚这究竟如何影响进一步的解析。- 我们将其定义
\iffoo
为\iffalse
。显然, 的重新定义\escapechar
在这里还没有效果。到目前为止,这是有道理的,因为(我猜) 的“定义”\newif
和参数在-stuff 展开\iffoo
之前已经被标记化了(?)。\escapechar
- 现在,奇怪的事情
\@if
开始发挥作用。我们执行\@if\iffoo\iftrue
,它执行 -stuff\csname
。这就是我不明白它是如何工作的。我将其理解为:\expandafter\def\csname\expandafter\@gobbletwo\string#1\expandafter\@gobbletwo\string#2\endcsname
扩展为\def[\csname\@gobbletwo[\string\iffoo]\@gobbletwo[\string\iftrue]\endcsname]
(方括号里的意思是评估)。- 根据 TeXbook,
\string
将控制序列转换为其组成字符作为单独的标记,包括转义字符,所有标记的类别代码均为 12。因此\string\iffoo
应转换为 $\backslash_{12}i_{12}f_{12}f_{12}o_{12}o_{12}$。但这会导致\csname ffooftrue\endcsname
而不是\csname footrue\endcsname
。
引用 TeX 书:
尽管控制序列被视为单个对象,但 TEX 确实提供了一种将它们分解为字符标记列表的方法:如果您写入
\string\cs
,其中\cs
是任何控制序列,您将获得该控制序列名称的字符列表。例如,\string\TeX
产生四个标记:$\backslash_{12},T_{12},e_{12},X_{12}$。此标记列表中的每个字符自动获得类别代码 12(“其他”),包括\string
插入以表示转义字符的反斜杠。但是,如果空格字符以某种方式潜入控制序列的名称中,则类别 10 将被分配给字符 ' '(空格)。
所以我想我的问题是:上面引用的 TeX 书中的说法是否简化了?那么当前的 值究竟如何\escapechar
影响 的扩展\string
?
答案1
该\escapechar
设置意味着\string\foo
不是(没有反斜杠)foo
,\foo
这意味着\gobbletwo
当if
应用于\i
\string\ifzzz
\escapechar
当教科书引用说将\backslash_{12}
插入时,它假设了默认值。
答案2
该命令\string<token>
有两种不同的工作方式:
如果是字符标记,则传递类别代码为 12 的相同字符(但如果是显式空格标记,则
<token>
使用 10 );<token>
否则
<token>
是一个控制序列,在这种情况下姓名控制序列的前面是代码为 的字符\escapechar
;这样产生的所有字符都具有类别代码 12,空格除外,它仍保留其通常的类别代码 10;但是,如果\escapechar
具有负值或超过 255(0x1FFFFF
对于xetex
或等 Unicode 引擎luatex
),则不会在前面添加任何字符。
因此,当\escapechar
为 −1 时,的结果\string\foo
将为foo
(所有具有类别代码 12 的字符)。
这与当前字符的类别代码为 0 完全无关。因此,如果你有
\catcode`@=0
@string@foo
并且所有其他设置都是标准的,则传递的字符串将是
\foo
因为的默认值\escapechar
是 92,这是 TeX 在启动时设置的。以下交互式会话可以证明这一点
> tex -ini '\catcode`{=1\catcode`}=2\message{\the\escapechar}'
This is TeX, Version 3.14159265 (TeX Live 2019) (INITEX)
92
\escapechar
但是,没有人能够知道调用时的值是多少\newif
。由于预期会执行\newif\iffoo
,因此宏需要删除可能的 \escapechar
从的输出\string\iffoo
以便能够定义\footrue
和\foofalse
。
例如,在读取.fd
文件时,LaTeX 会进入设置为 -1 的状态\escapechar
,因此使用时不会插入反斜杠\string
(它不是唯一的地方)。因此,最安全的策略是暂时设置\escapechar
为已知值,然后恢复其值。因此,当前值存储在\count@
(请注意,这\escapechar
是一个内部整数寄存器)中,以便在作业结束时恢复。
将(临时)值设置为 92 需要使用\@gobblethree
(内核中未定义),因此将其设置为 -1 并使用更简单\@gobbletwo
。
这可以通过不同的方式来完成,而不必将值存储在计数器中,这甚至可能更为可取,因为\count@
不会受到影响。
\def\newif#1{%
\let#1\iffalse
\@if#1\iftrue
\@if#1\iffalse
}
\def\@if#1#2{%
\begingroup\escapechar=\m@ne\expandafter\endgroup
\expandafter\def
\csname\expandafter\@gobbletwo\string#1\expandafter\@gobbletwo\string#2\endcsname
{\let#1#2}%
}
它是如何工作的? 的值\escapechar
在组内改变,并且会在组结束后立即恢复。然而,\expandafter
戳之后\endgroup
前当到 −1时,组结束,因此\csname
形成来自的标记;将恢复先前的值并且将执行。\escapechar
\endgroup
\escapechar
\def
与普通 TeX 中类似宏的一个重要区别是 LaTeX 执行不检查参数是否\newif
是一个名称以 开头的控制序列if
。
答案3
您转载的引文确实经过了简化,但在同一页(40)上,TeXbook 写道:
在到目前为止的示例中,已将控制序列转换为以 \ 12
\string
开头的标记列表。但这个反斜杠标记实际上并不是硬连线到 TeX 中的;有一个名为的参数指定在将控制序列输出为文本时应使用什么字符。的值通常是 TeX 的反斜杠内部代码,但如果需要其他约定,则可以更改它。\escapechar
\escapechar
无论如何,如果你想确定的话,最好也看看后面的章节,比如三...模式摘要书末的章节。在本例中,在第 20 章中,定义(也称为宏):
\string
〈token〉。TeX 首先读取不带扩展的 〈token〉。如果出现控制序列标记,则其\string
扩展由控制序列名称组成(\escapechar
如果控制序列不是简单的活动字符,则包括转义字符)。否则 〈token〉 是字符标记,其字符代码将保留为扩展结果。
\string
此外,根据第 308 页,当且仅当\escapechar
超出 [0,255] 范围时,才会在 Knuth 的 TeX 中使用转义字符时省略转义字符:
但是,还有另一种解决方案:如果 TeX 的
\escapechar
参数(将在下一个危险弯道中解释)为负数或大于 255,则\string\\
有效。
(目的是输出类别代码为 12 的单个反斜杠字符标记)。