尝试理解 latex.ltx 中的一行(关于 \newif 定义中的 \string、\escapechar 和 \csname)

尝试理解 latex.ltx 中的一行(关于 \newif 定义中的 \string、\escapechar 和 \csname)

我试图了解 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这意味着\gobbletwoif应用于\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 的单个反斜杠字符标记)。

相关内容