> For the complete documentation index, see [llms.txt](https://overleaf-pro.ayaka.space/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://overleaf-pro.ayaka.space/latex/zh-cn/shen-ru-wen-zhang/53-what-is-a-tex-token.md).

# 什么是“TeX 记号”？

## 关于 TeX token 及相关概念系列文章的动机

本文讨论了撰写一系列关于 TeX token 及相关概念文章的动机和所采用的方法 [新系列文章：TeX token 及相关概念——但为什么（以及如何）？](https://www.overleaf.com/blog/521-a-new-series-of-articles-tex-tokens-and-related-concepts-but-why-and-how) 正如那篇文章所指出的，在整个系列中，我们的讨论和解释都基于通过 Knuth 原始 TeX 程序的定制构建所获得的见解——我们用它来撰写一系列文章，旨在对关键的 TeX 概念提供简明的描述和易于理解的解释。

## 引言：我们的目标是什么？

在本文中，我们将通过追踪从输入文件中的字符到 TeX token 实际生成的处理过程，准确地了解什么是 TeX token。实际情况相当复杂，因此我们将这一过程提炼为其核心要点，力求在保持技术准确性的同时，使其易于跟随和理解。

我们先介绍一些重要的 TeX 内部概念： *原始命令*, *命令代码* 和 *命令修饰符*。然后，我们使用一个非常简单的宏示例来准确看看 TeX 是如何处理命令 `\def` 以及 TeX 为表示该命令而创建的 token。

最后，我们简要看看 TeX 如何创建用于表示字符的 token，以及字符的 `\catcode` 确实会永久附着在字符 token 上——这一点在关于 TeX 的书中常常会提到，但在这里我们会准确地看到它是如何实现的。

下图展示了我们将要概述的过程——从输入文本到 TeX token：

![从 TeX 输入到 TeX token 的过程。](/files/ff2789dc3c98fb880dad923724f23b10bb12db86)

## 但首先：原语和命令代码

每个 TeX 引擎（Knuth 式 TeX、pdfTeX、XeTeX、LuaTeX）都能理解一些内置命令：所谓的 *原始命令*——这些是构成 TeX 可编程性的基本积木式命令。之所以称为“原语”，是因为与用户定义的宏不同，它们不是由其他命令构成的，也不能进一步分解为更简单的指令。对于 Knuth 的 TeX 来说，大约有 320 个原语——不过我们应当注意，其他 TeX 引擎如 pdfTeX、XeTeX 和 LuaTeX 都在 Knuth 的原始程序基础上添加了新命令，并且会包含 Knuth 的 TeX 软件中没有的原语。

内部地，TeX 会为所有命令分配一个数字 *命令码* ，无论它们是用户定义的宏还是内置原语。这些命令代码用户无法直接访问，它们只是 TeX 处理过程内部机制的一部分，不过在后面讨论 TeX token 时了解它们是有用的。

功能相关的一组命令共享相同的命令代码。例如， `\def`, `\gdef`, `\edef` 和 `\xdef` 这些原语都用于定义宏，并且在（Knuth 的 TeX 中）共享 97 的命令代码。显然，这 4 个定义宏的命令各自以稍有不同的方式创建宏；因此，在处理过程中，TeX 需要一种方法来区分它们。

单靠命令代码（例如 97）无法告诉你正在考虑的是哪一个宏创建命令；因此，正如你所预料的，每个 TeX 命令还会分配一个额外的信息，称为其 *命令修饰符* （见下例）。

### 命令修饰符：两种类型

命令修饰符分为两类，我们将称之为“类型 1”和“类型 2”——TeX 并不使用这种术语，这里这样称呼只是为了方便：

* **类型 1**：TeX 可以在需要时使用的简单整数值，用于区分共享相同命令代码的命令。
* **类型 2**：一个整数值，它是 TeX 内存中的一个数值位置，告诉 TeX 需要去哪里查找该命令的信息。例如，这适用于用户定义的命令（宏），其中命令修饰符告诉 TeX 宏定义存储在内存中的位置。

#### 类型 1 命令修饰符（示例）

如前所述，在 Knuth 的 TeX 中，用于定义宏的四个原语命令： `\def`, `\gdef`, `\edef`, `\xdef` 都共享 97 的命令代码：它们通过命令修饰符加以区分，下面的表中列出：

| 命令      | <p>命令<br>代码</p> | <p>命令<br>修饰符</p> |
| ------- | --------------- | ---------------- |
| `\def`  | 97              | 0                |
| `\gdef` | 97              | 1                |
| `\edef` | 97              | 2                |
| `\xdef` | 97              | 3                |

作为第二个例子，Knuth 决定将命令 `\openout`, `\write`, `\closeout`, `\special`, `\immediate` 和 `\setlanguage` 实现为 TeX 的“扩展”，纯粹是为了展示如何向 TeX 添加新的原语。在这种情况下，这些命令其实并没有真正共享“相似功能”，只是 Knuth 决定把它们分组，以解释如何扩展 TeX。那 6 个命令被归类为“扩展”，并以 59 的命令代码值分组，但每一个都有合适的命令修饰符来将其与其他命令区分开来：

| 命令             | <p>命令<br>代码</p> | <p>命令<br>修饰符</p> |
| -------------- | --------------- | ---------------- |
| `\openout`     | 59              | 0                |
| `\write`       | 59              | 1                |
| `\closeout`    | 59              | 2                |
| `\special`     | 59              | 3                |
| `\immediate`   | 59              | 4                |
| `\setlanguage` | 59              | 5                |

#### 类型 2 命令修饰符（简要说明）

虽然所有命令修饰符都是整数，但类型 2 修饰符需要稍加解释。在 TeX 中，这些命令修饰符被称为“指针”，因为它们指向内存中的一个位置，TeX 可以在那里找到该命令的附加信息。这听起来可能有点含糊，但 TeX 使用这些指针查找信息的方式非常多样，若展开说明会偏离本文的核心目标。一个例子就足够说明：宏。当定义一个宏命令时，TeX 需要把替换文本存放在内存中的某处。如下所示，用户定义的宏的命令代码介于 111 和 114 之间，并且其命令修饰符是一个指向内存的指针，告诉 TeX 其替换文本（宏定义）存放在哪里。

### 命令代码：可展开与不可展开

在 Knuth 的 TeX 源代码中，命令代码范围从 0 到 120——注意，该范围内的一些代码仅供专门的内部使用，并不分配给用户可访问的命令。值得注意的是，其他 TeX 引擎如 pdfTeX、XeTeX 和 LuaTeX 都在 Knuth 的原始集合中添加了新命令，因此会包含更多原语以及相应的命令代码；不过，这里概述的原理是所有源自 Knuth 源代码的 TeX 引擎的核心。

命令代码集合分为两个主要部分：

* *不可展开命令*：命令代码小于或等于 100；
* *可展开命令*：命令代码大于 100，最大值为 120。101 到 120 的范围包括用户定义的宏，以及诸如 `\csname`, `\\expandafter` 和 `\the`.

不可展开命令通常执行对内部参数的赋值，或者直接生成可排版的内容。可展开命令通常会向 TeX 当前的处理活动中“注入”一串 token，或者改变 token 处理的顺序。

如上所述，所有宏（用户定义的命令）的命令代码都在 111 到 114 之间：不同的取值反映了宏是被定义为 `\long`, `\outer`，两者兼有，或两者皆非。下面是一个例子：

| 宏类型            | 示例                             | 备注                 |
| -------------- | ------------------------------ | ------------------ |
| 非 long，非 outer | `\def\ohyeah{....}`            | `\ohyeah` 命令代码=111 |
| long，非 outer   | `\long\def\ohyeah{....}`       | `\ohyeah` 命令代码=112 |
| 非 long，outer   | `\outer\def\ohyeah{....}`      | `\ohyeah` 命令代码=113 |
| long outer     | `\long\outer\def\ohyeah{....}` | `\ohyeah` 命令代码=114 |

作为对命令修饰符的提醒，当一个宏被定义时，TeX 会把宏的定义存储在内存中的某个位置：那个位置（一个指针）将成为该宏命令的命令修饰符，而该宏命令会根据其定义方式，以 111 到 114 之间的某个命令值存储。分配给用户定义宏的实际名称其实并不重要：在输入处理之后，它们都会被分配一个介于 111–114 的命令代码，而最终，TeX 从你的输入中读取的所有命令，无论是原语还是用户定义的宏，最终都会转换成一种称为一个 *记号*.

## 从输入文本到 TeX token 的过程

在这一节中，我们将通过一个非常简单的宏示例来准确看看 TeX 是如何处理命令 `\def` 以创建一个表示 `\def` 命令的 token。TeX 的详细处理活动可能极其复杂，所以我们不使用宏参数或分隔符，因为那样会增加复杂度并分散我们对这一路径的关注。

假设你的 TeX 输入文件包含以下一行：

```latex
\def\ohyeah{Overleaf is cool!}
```

当 TeX 开始处理这一行输入时，它会检查每个字符的 `\catcode` 并看到第一个字符是 `\` （第一个字符为 `\def`）。它检测到（在内部表中查找） `\` 具有 `\catcode` 0，这意味着它引入了一个 *控制序列*的开始。当然，你可以重新定义任何字符使其具有 `\catcode` 0，但我们这里假设使用的是 plain TeX 或 LaTeX 的常规定义。

严格来说，术语 *控制序列* 有两个子类别： *控制词* 和 *控制符号*:

* *控制词*：由一串具有 `\catcode` 字母（11）；
* *控制符号*：一个单独的字符，其 `\catcode` 是 *不* 字母（11）。

此时， `\` 字符已经完成了它的工作，现在没它什么事了。检测到转义字符后，TeX 的反应是开始读取输入中后续的所有字符，以便检测控制字或控制符号。

在最初的 `\`之后，TeX 立即检测到 `d`：一个字符，其 `\catcode` 为 11，这告诉 TeX 它找到了一个 *控制词*。它继续扫描后续字符，直到最终检测到一个字符，它 *不会* 必须 `\catcode` 字母（11）。初始的 `\`）之后所有具有 `\catcode` 11（字母）的后续字符都被认为构成一个控制字的名称：也就是一个命令的名称——可能是宏，也可能是原语，但 TeX 此时还不知道它是哪一种命令。此时它只是一串字符。

因此，在我们的例子中，TeX 会一路扫描，检查每个字符，直到它到达最初的 `\` 的一个记号值 `\ohyeah` 其 `\catcode` 0。TeX 识别到它扫描过头了，于是礼貌地把那个 `\` 放回文本流中，使其成为后续再次扫描文本时将看到的下一个字符。此时，TeX 已识别出一个字符串（`def`），它知道这构成了一个由三个字符组成的控制字文本，每个字符都有 `\catcode` 11 (`d`, `e` 和 `f`）。TeX 现在需要做的是弄清楚 `def` 是什么意思：它要做什么？正如你可能已经猜到的，TeX 需要为 `def` 找到命令代码和命令标识符，以便判断应该如何处理这个命令。

## 把它搞成哈希

在检测到一个控制字（`def`）后，TeX 做的第一件事就是使用一种称为哈希函数的东西，把这一串字符（`def` 在我们的例子中）“转换”为一个整数。我们不需要过多关注细节，概述即可。简单来说，TeX 会查看它刚检测到的控制字中的每个字符，并使用每个字符的 ASCII 码值（对于 XeTeX/LuaTeX 则是 Unicode 值）计算出一个称为哈希值的数字：它只是一个简单的整数。

作为哈希计算过程的一部分，TeX 还会检查新检测到的控制字中的字符序列是否已经为它所知。所有命令的人类可读文本，无论是原语还是用户定义的宏，都存放在一个称为 *字符串池*的内部存储区域中。TeX 必须这样做，因为它可能需要输出某个命令的人类可读名称——例如，当 TeX 需要报告错误并提供出错命令的名称时。比如，我们的宏 `\def\ohyeah{Overleaf is cool!}` 正在定义一个名为 `\ohyeah` 的新命令，而 TeX（在后续阶段）不仅需要为 `ohyeah` (*没有* 初始的 `\` 字符）并且还要存储文本字符串（可读形式），以便将来在需要用于错误报告（或其他任务）时使用。

如果你想了解更多关于 TeX 字符串处理过程的细节，我已经在我的 [个人博客网站](http://www.readytext.co.uk/?p=3590).

最终的结果是，表示命令 `def` 被转换为数值 1218（这就是 TeX 实际计算出的值）。此时，单个字符 `d`, `e` 和 `f` 已经不再是主线的一部分——它们已经从输入中被读取并完成了它们的任务：从现在起，讨论的全是整数和 *记号*——我们很快就会看到 token 究竟是什么！在内部，TeX 将这些哈希值数字称为 *当前控制序列* 但在源代码中这个术语被缩写为一个名为 `curcs`。TeX 源代码中充满了非常短、往往相当晦涩的变量名。

但是 TeX *实际上做什么* 面对这个刚刚生成的整数值 1218？TeX 如何弄清楚最初的字符串 `def`，现在由整数 1218 表示，实际上指的是一条定义宏的指令？答案是，TeX 有一种内部“文件柜”，它存储着当前已知的每个命令的当前含义和值——无论该命令是用户定义的宏还是内置原语。TeX 之所以费力把 `def` 转换成哈希值 1218（现在存储在名为 `curcs`的变量中），是为了用它来查找 *含义* 的一个记号值 `def`该命令的当前含义和值。TeX 当然会对输入中检测到的所有控制字重复这个哈希计算过程——当然，不同的控制字通过哈希函数会得到不同的整数值：这正是其目的所在。

TeX 的内部“文件柜”称为 *等值表* ，这是下一节的主题。

### 查阅等值表

先概括一下，到目前为止我们学到了什么：

* `\` 引入一个控制序列的开始（一个 *控制符号* 或一个 *控制词*).
* 如果紧跟在 `\` 具有 `\catcode` 11（字母），那么它就是一个 *控制词*.
* 对于 *控制字* TeX 会扫描并检查后续所有具有 `\catcode` 11 的输入字符，并且一旦找到第一个不具有 *不* 不具有 `\catcode` 11 的值。
* 输入字符串（跟在 `\`之后）其 `\catcode` 11，被认为是用户输入的一个 *控制词* ：即用户在要求 TeX“做某事”的命令。
* 为了开始“做某事”的过程，TeX 会把控制字中的字符序列转换成一个整数。它使用一种所谓的哈希函数来完成这件事，该函数输出一个整数。
* 这个整数（计算得到的哈希值）被称为 *当前控制序列*，但 TeX 给予它更短的名称 `curcs`.
* 在我们的例子中，控制字 `def` 被转换为值 1218——这会存储在一个名为 `curcs`：即， `curcs=1218`.

TeX 现在需要弄清楚刚检测到的 *当前控制序列* 究竟是什么意思——TeX 要如何处理它？

#### 关于分组的说明：需要保存和恢复信息

这里，我们稍作绕行，提醒自己 TeX 具备保存和恢复信息的能力：也就是说，它有某种内置的“记忆”。

哪怕只写过最简单宏的人，都应该了解 TeX 的分组机制——例如，使用 `\def` 在一个组内创建宏。除非你对在组内定义的、由 `\global` 前缀修饰的 `\def`宏应用

```latex
{\def\foo{Hello}}
```

并尝试在组外使用 `\\foo` 在组外

```latex
{\def\foo{Hello}}% \foo 定义在一个组内（注意：未使用 \global）
\foo %<--- 不再定义，现在是未定义的
```

那么我们就会得到那个令人熟悉的错误： `未定义的控制序列`. `\\foo` 只在它被定义的那个组（及其子组）内有意义。此外，当你在一个组内重新定义宏时，新的值可能会在组结束时丢失，而先前的含义（存在于组外）会被恢复。

```latex
\def\foo{Goodbye}
\foo\par% 输出 Goodbye
{\def\foo{Hello}% 在组内重新定义：
{第二层组内：\foo\par}}% 在第二层组内使用：\foo 输出 Hello
组外恢复旧值：\foo\par% 输出 Goodbye
```

这些简单示例的目的，是说明 TeX 具有某种“存储机制”或“记忆”，可以保存/恢复命令的“含义”——当然，它确实如此。我们在上一节已经暗示过这一点：那个“存储机制”或“文件柜”就是一个大型内部表，称为 *等值表*。TeX 就是在那里存储它当前已知的所有命令——内置原语和用户定义的宏——的当前含义或值。

### 等值表：类比说明

为了解释等值表，我们将通过类比来展开。我们继续使用带有成千上万个小抽屉的文件柜这个概念，每个抽屉都用一个唯一的整数标记。在处理的这个阶段，TeX 实际上是在说：

“好吧，我刚刚算出了这个整数值 1218，并把它保存在名为 `curcs`的变量中。现在我需要弄清楚它是什么意思：为此，我会去查看我的文件柜中编号 1218 的抽屉，看看里面写了什么。”

TeX 用 1218 找到正确的抽屉，在那里它找到一张小纸条，里面包含三条信息，这些信息的名称是 TeX 源代码中使用的名称：

* **`eq_level：`** 定义此条目的分组层级（第 1 级 = 全局定义）。我们在上面看到了分组的效果：这里在等值表中存储了该分组层级信息；
* **`eq_type：`** 此条目的命令代码；
* **`equiv：`** 此条目的当前“值”——它可以是一个简单整数，例如上面提到的命令修饰符，或者是一个指向内存某一区域的指针；例如，表示宏定义的 token 集合所在的内存位置。

因此，我们 1218 的哈希值（保存在变量 `curcs`中）实际上被用作 *键* ，以访问一个抽屉，而该抽屉包含了我们最初输入的字母串命令的当前含义和值 `\def`.

在 TeX 程序的源代码中， `eq_type` 对于任何命令都存储在一个名为 `curcmd` 而 `equiv` 的值存储在一个名为 `curchr`.

### def 的等值表里会写什么？

如前所述，为任何命令计算得到的哈希值会保存在一个名为 `curcs`；因此对于 `def` 我们有 `curcs=1218`。在查看等值表中 1218 的位置时，TeX 会找到以下信息：

* `curcmd`=97。这是 `\def`;
* `curchr`=0。这是 `\def`.

`\def` 是一个原语（内置）TeX 命令，除非它在别处被重新定义，否则第三条也是最后一条信息应当是 `eq_level=1` ，表明 `\def` 的含义是在全局范围内定义的，而不局限于某个较低层级的分组。在内部， `eq_level` 附加到命令上的值在 TeX 的分组机制中扮演着极其重要的角色，但我们不再进一步讨论。

下图概括了我们刚刚走过的说明：

![从 TeX 输入到 TeX token 的过程。](/files/ff2789dc3c98fb880dad923724f23b10bb12db86)

## 命令的 TeX token

经过上述解释后，控制序列的 TeX token 的实际计算其实非常简单。TeX 使用 `curcs` （1218）的值，通过哈希函数创建一个称为 *记号*的简单整数。由 `curcs` 的值生成 token 的计算公式是：

```c
curtok = 4095 + curcs
```

TeX 将当前 token（最近计算出的那个）的值存储在名为 `curtok`.

因此，总之，表示 `\def` 命令的一般形式是 `4095 + 1218 = 5313`。这就是表示命令序列的 TeX token：它们只是由哈希表值加上 4095 计算得到的整数。

## TeX 用于字符的 token

当 TeX 需要创建一个表示字符的 token 时，它使用下面同样简单的计算：

```c
curtok = 256*catcode + (ASCII value of character)
```

请注意，对于 LuaTeX 之类支持 Unicode 的引擎，会使用略有不同的计算。

例如，表示一个空格字符的 TeX token，其 `\catcode` 为 10 且 ASCII 值为 32，是：

```c
256*10 + 32 = 2592
```

### 包含字符的 token 列表

当你创建一个简单的 token 列表，例如

```latex
\toks100={Hello}
```

TeX 会创建下面的 token 列表，并将其存入内存以供后续使用：

* H→ 256 × 11 + 72 = 2888
* e→ 256 × 11 + 101 = 2917
* l→ 256 × 11 +108 = 2924
* l→ 256 × 11 +108 = 2924
* o→256 × 11 + 111 = 2927

在 TeX 内部深处，token 寄存器 100 会提供对“Hello”存储位置的访问，它被保存为 5 个 token 值：2888、2917、2924、2924、2927。请注意，这些 token 结合了每个字符的 ASCII 码和其 `\catcode`在那一刻，它们就被转换为 token（被分词）。一旦字符被转换为字符 token， `\catcode` 附加在它们上的值就变成永久性的，并存储在 token 中，以便后续在用户说出例如 `\the\toks100`.

如前所述，一个字符 token 的计算公式是 `256*catcode + (ASCII value)` 而一个控制序列 token 的计算公式是 `4095 + curcs` 其中 `curcs` 是 TeX 在输入中检测到的控制字（用户键入命令的文本字符串）的哈希值。值得注意的是，字符 token 总是小于 4095。因此 TeX 可以轻易判断某个 token 代表的是控制序列（命令）还是字符，然后再确定具体是哪一个控制序列或字符，以及 `\catcode` 该 pair 是否被编码进该 token 中。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://overleaf-pro.ayaka.space/latex/zh-cn/shen-ru-wen-zhang/53-what-is-a-tex-token.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
