> 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/geng-duo-zhu-ti/18-how-overleaf-created-the-tex-primitive-reference-data.md).

# Overleaf 如何创建 TeX 原语参考数据

本文介绍用于生成 TeX 原语命令两个交叉引用表的方法和技术：

* [按 TeX 引擎列出的 TeX 原语](/latex/zh-cn/geng-duo-zhu-ti/46-tex-primitives-listed-by-tex-engine.md) 以及；
* [按 CJK TeX 引擎列出的 TeX 原语](/latex/zh-cn/geng-duo-zhu-ti/45-tex-primitives-listed-by-cjk-tex-engine.md).

这类信息是为对更细节感兴趣的读者提供的，但并不是使用交叉引用表本身的前提。为了满足不同读者的需求，我们提供了一个非常简短的摘要版本，以及一个更长的说明，供希望更深入探究这些问题的人阅读。

## 简短摘要/概览版本

为了构建交叉引用表，Overleaf 处理了 9 个 TeX 引擎的源代码，以提取每个引擎所支持的原语列表：该过程生成了 9 个文本文件（每个 TeX 引擎 1 个文件）。这 9 组原语被合并成一个“主列表”，实际上就是各个原语集合的并集：总共得到大约 1000 个不同的原语，分布在各个引擎中。对于每个引擎，都将其自身的原语列表与主文件（所有命令的集合）进行交叉引用，以确定它支持这约 1000 个命令中的哪些：这些比较结果整理在下面的两个表中：

* [TeX 原语交叉引用数据](/latex/zh-cn/geng-duo-zhu-ti/46-tex-primitives-listed-by-tex-engine.md)
* [TeX 原语交叉引用数据（适用于 CJK 引擎）](/latex/zh-cn/geng-duo-zhu-ti/45-tex-primitives-listed-by-cjk-tex-engine.md)

## “软件构建”101：这是什么意思？

在本文余下部分，我们会提到“构建 TeX 引擎”这一概念；如果你不是程序员，或者不编写 C、C++ 等编译型语言程序，这个概念可能比较陌生。就我们的目的而言，构建软件——即 TeX 引擎——是指从组成部分中创建一个可执行的 TeX 程序的过程；这些组成部分就是用开发该程序所使用的编程语言编写的源代码文件。

## 完整版：想了解细节？继续阅读……

每一种基于 TeX 的排版引擎都支持 TeX 语言的一种“方言”：即一组特定的原语命令，它们控制着各个引擎的排版功能，并为创建/定义宏提供基本构件：也就是用户定义的命令序列。无论是为 LaTeX、plain TeX 还是任何其他宏包编写的，每一个宏最终都是由原语命令构成的——不过，在到达 TeX 原语这个“基岩层”之前，你可能需要向下钻探很长一段距离，穿过多层额外的宏。用于生成原语命令参考数据的这 9 个 TeX 引擎当然有许多共同命令，但每个 TeX 引擎也都有其开发者添加的专有原语命令，以支持该 TeX“版本”特有的功能。

TeX 引擎的原语命令内置于可执行的 TeX 软件中：原语不是用户构造的宏，而是用于控制各引擎排版行为的基本、不可再分/原子的指令。因此，要为任何 TeX 引擎创建一份权威的原语命令列表，最可靠的方法就是检查生成可执行 TeX 程序的实际源代码（即编译这些程序的源代码），并提取源代码中定义的原语列表。听起来应该很容易，对吧？然而，由于 TeX 长达 40 年的发展历史，探索/检查 TeX 引擎（LuaTeX 除外）的源代码文件并不特别简单。造成这些复杂性的原因在于 Knuth 用来编写最初 TeX 源代码的工具、编程语言（Pascal）以及方法论（文学化编程）——而所有其他引擎归根结底都源自那份代码。

我们要特别说明 LuaTeX，因为它的核心引擎代码被重写为 C，以去除 Pascal 以及下面详细介绍的其他遗留复杂性（Web2C）；因此，尽管 LuaTeX 的源代码规模很大，但与其他 TeX 引擎相比，它的“打包”和分发方式要容易理解得多。因此，并基于从源代码构建它们所使用的工作流/流程，将 TeX 引擎分为两类是很方便的：

1. LuaTeX：定制（更现代）的构建流程
2. 其他所有引擎：遗留的（Web2C）构建流程

## 遗留代码的背景：为什么构建（大多数）TeX 引擎很复杂

正如我们将在下面看到的，Knuth 将他最初的 TeX 源代码发布为一个单一的、巨大的文件，名为 `tex.web` ，Knuth 每隔 7 年继续更新它，以修复任何剩余的漏洞——从不添加新功能，这纯粹是一项修 bug 的工作。

TeX 源代码的文件扩展名（`.web`）不太可能让你熟悉，你可能会想：Knuth 是用哪种语言编写 TeX 的？答案是 Pascal，但 `.web` 这个扩展名还需要稍作解释。Knuth 开发了一种编程方法论，他称之为 [文学化编程](https://en.wikipedia.org/wiki/Literate_programming) 在这种方法中，程序的源代码和文档被结合在一起，并以一个单一的复合文件（代码加文档）的形式发布，扩展名为 `.web`：这种文件类型被称为 WEB 文件。下面我们会更详细地解释 WEB 文件。

### 创建新的 TeX 引擎：Knuth 的限制条件

尽管 Knuth 早已将他的 TeX 源代码（`tex.web`）免费提供给所有人，他仍然——这完全是他的权利——设定了一条关键限制：他的（`tex.web`）源代码不得被直接编辑/修改后再以“TeX”这一程序名重新分发。在源代码中他写道：

```
% 本程序版权 (C) 1982 归 D. E. Knuth 所有；保留所有权利。
% 仅在以下情况下才授权复制本文件：(1) 你是 D. E. Knuth，或者
% (2) 你对副本完全不做任何修改。（WEB 系统提供
% 通过辅助文件进行改动；主文件应保持完整。）
```

以及：

```
如果这个程序被修改，所得系统不应被称为
`\\TeX'；官方名称 `\\TeX' 本身是保留的
，只留给彼此完全兼容的软件系统。
有一个称为“\\.{TRIP} 测试”的特殊测试套件可用于
帮助判断某个实现是否配得上被
称为 `\\TeX' [参见斯坦福计算机科学报告 CS1027，
1984 年 11 月]。
```

本质上：不要通过编辑并分发修改过的主 TeX 源代码版本来做改动，却仍然继续把它称为 `tex.web`. 如果你确实想做改动，例如添加新原语等，那么你必须通过“借助辅助文件进行改动”来应用这些变化，并且要给你的“TeX 的衍生版本”起一个名称，以将其与“TeX”区分开来；而“TeX”的排版形式（$$\mathrm\TeX$$）是美国数学学会的商标。

### 遗留代码的传承

尽管有人尝试用现代编程语言和方法论——例如两个基于 Java 的项目——完全重写 TeX， [New Typesetting System](https://en.wikipedia.org/wiki/New_Typesetting_System) 和 [εχTEX](http://www.extex.org/) 以及其他如 [一个基于 Clojure 的项目](https://www.infoq.com/news/2015/01/implementing-tex-in-clojure)，但都没有完全成功。旨在进一步发展 TeX 的项目和倡议的历史是一个有趣的话题，读者也许会想要 [访问 UK TeX FAQ](https://texfaq.org/FAQ-enginedev) 以获取更多信息。

那些非 LuaTeX 的倡议中也有一些取得了成功，例如 e-TeX、pdfTeX、XeTeX 以及其他引擎，它们都是在 *直接建立在* Knuth 原始代码之上的：取用他的源代码并“应用修改”，以衍生出具备附加能力的新引擎——例如添加新原语、生成 PDF 输出、支持 UTF-8 文本输入等等。虽然这条路线带来了显著成功，但也意味着这些衍生引擎继承了 Knuth 40 年前创建的遗留代码和开发技术。

这里的关键结论是，除了 LuaTeX 之外，大多数源自 Knuth 原始源代码的 TeX 引擎，都是通过取一个单一的巨型文件（通常是 `tex.web`）并应用修改而创建出来的，这些修改会生成另一个单一的巨型文件，其中包含该新引擎的核心源代码。高级读者也许希望跳到关于 [pdfTeX 和 XeTeX 的说明](#aside-xetex-and-pdftex).

### 一些额外的 TeX 历史/背景

TeX 诞生的时刻是 [Knuth 的日记中记录为 1977 年 3 月 30 日](/latex/zh-cn/shen-ru-wen-zhang/55-what-s-in-a-name-a-guide-to-the-many-flavours-of-tex.md#the-genesis-of-tex-a-brief-history)，距今已超过 40 年。TeX 在内部是一个极其复杂的程序，Knuth 为其源代码 [花费了极大的心血进行异常详尽的文档编写](https://www.amazon.co.uk/Computers-Typesetting-TeX-Program-TEX/dp/0201134373)。为此，Knuth 开发了一种编程风格，他称之为 [文学化编程](https://en.wikipedia.org/wiki/Literate_programming) 在这种风格中，程序的源代码和文档被合并在一起，并作为扩展名为 `.web` 的复合文件发布（称为 WEB 文件）。Knuth 选择 Pascal 作为编写 TeX 软件的编程语言，而毫不意外地，他又使用 TeX 排版语言来编写最终文档。因此，Knuth 的 TeX 主源代码以一个单一的、巨大的文件形式发布，名为 `tex.web`：Pascal 源代码与用于文档的 TeX 排版代码的混合体。

如果一个程序使用 Knuth 的文学化编程风格/方法论编写（TeX、MetaFont、BibTeX 等都是如此），你就需要预处理 WEB 文件，以提取文档或源代码。要访问程序文档，你要用一个名为 `tex.web`的工具来处理 WEB 文件（例如， [WEAVE](http://tug.org/texinfohtml/web2c.html#weave-invocation) ），它会把文档生成成一个 `.tex` 文件，供你排版。要提取 Pascal 源代码，你要使用另一个名为 [TANGLE](http://tug.org/texinfohtml/web2c.html#tangle-invocation) 的工具，它会输出一个扩展名为 `.p` 的文件，其中包含 Pascal 源代码。

在撰写本文时（2019 年初），Knuth 的 TeX 最新版本是 3.14159265，日期为 2014 年 1 月。再次提醒，Knuth 的 TeX 源代码只包含在一个单一文件中，而该文件大约有 25,000 行 TeX/Pascal 代码！

### 从 Pascal 到 C

在 TeX 诞生后的 40 多年里，Pascal 已经过时，如今几乎没有人会考虑根据最初的 Pascal 源代码来构建 TeX。为了绕开 Knuth 对 Pascal 的使用，设计出了一种名为 [Web2C](http://tug.org/texinfohtml/web2c.html) 的工作流程（约在 1987 年），其中将 TeX 的 Pascal 源代码通过机械方式（即借助软件）转换为等价的 C 代码，然后用它来编译 TeX 并构建可执行程序。它运作良好，但唯一的缺点是机械生成的 C 源代码并不适合随意供人查看：它 *极其* 冗长，几乎难以理解，它是为编译器而不是为人准备的——这里有一张截图，展示了从 TeX 的 Pascal 源代码生成的 C 代码中的一小段片段：

![](/files/d77115cba12f7b22540bce0058ea473eab1506ca)

### 另一个 Knuth 主义：WEB 变更文件

如上所述，要在 Knuth 的原始源代码之上进行构建，你要“应用修改”，或者用 Knuth 的话说，通过“借助辅助文件进行改动”：但这究竟是什么意思？这就引出了 *变更文件机制*.

### 变更文件：创建新 TeX 引擎的机制

希望以某种方式扩展 Knuth 的 TeX 的开发者，也就是希望在 Knuth 的原始工作基础上继续开发，通常希望创建一个全新的 TeX“版本”，或者提供一个 *扩展* ，可添加到任何 TeX 引擎中。扩展示例包括 [SyncTeX](https://github.com/jlaurens/synctex) 和 [EncTeX](https://ctan.org/pkg/enctex?lang=en)——例如，SyncTeX 是一个非常有用的扩展，现在已包含在所有 TeX 引擎中。EncTeX 的需求在很大程度上已被支持 Unicode 的 TeX 引擎的发展所取代——但请注意，EncTeX 已内置于 pdfTeX 中。

无论目标是生成 TeX 的新“版本”（即 Knuth 原始 TeX 的一个衍生版本），还是创建一个扩展，其开发者都从 Knuth 的原始源代码出发，应用必要的修改，以创建一个新的 TeX 引擎（或一个附加扩展）。然而，如上所述，任何希望修改 TeX 行为的人都需要通过“借助辅助文件进行改动”来完成，因为这些变更/修改不得通过 *直接* 编辑 Knuth 的原始源代码来应用：开发者必须使用所谓的 WEB *变更文件机制*。用于修改 Knuth 的 TeX 的代码是用 WEB“语言”编写的，并保存为一个或多个代码文件（称为 *变更文件*），随后这些文件会与 *合并* 到 Knuth 原封不动的原始主源代码中。这个合并过程会创建一个新的复合 WEB 文件，其中现在包含了新/修改后的基于 TeX 软件的 *核心* 源代码。 *变更文件* 通常扩展名为 `.ch` ，但在实际中，它们可以具有开发者想要的任何扩展名。

#### 如何使用/应用变更文件？

如今，应用变更文件并修改一个“主”WEB 文件最简单的方法，是使用一个名为 [TIE](https://ctan.org/pkg/tie)的工具程序。例如，假设你想通过添加一些新原语来修改 Knuth 的 TeX，或者你想改变某个现有（标准）TeX 原语的行为。你会用 WEB 系统的文学化编程方式编写代码（用 Pascal！），并将其保存到一个文件中，例如 `myprim.ch`。下一步是将你的代码（在 `myprim.ch`中）与 Knuth 的主源文件 `tex.web` 合并，并生成一个新的、复合的 WEB 文件，表示我们称之为 `mytex.web`。要做到这一点，你只需像这样执行 TIE 程序：

```
tie -m mytex.web tex.web myprim.ch
```

假设合并成功，这将生成一个新的 WEB 文件， `mytex.web`，同时使 Knuth 的主源文件 `tex.web` 完全保持不变，符合要求。

现在假设有人很喜欢你所做的改动，并且想在你的工作基础上继续修改，加入他们自己的改动。与其分发你修改过的 TeX 版本（`mytex.web`），你决定只发布/共享变更文件， `myprim.ch`。任何想在你的工作基础上继续开发的人，现在都可以创建并共享他们的变更文件，例如叫 `moreprim.ch` ，它会在某种程度上扩展你的代码。任何其他想同时利用这两个变更文件的人，现在都可以通过将 *这两个* 变更文件合并到 Knuth 的原始文件中，来生成另一个 TeX 程序，例如叫 `newmytex.web`:

```
tie -m newmytex.web tex.web myprim.ch moreprim.ch
```

### 真实的 TeX 系统：多个变更文件

上面对 TIE 的描述实际上与许多 TeX 引擎在实践中的构建方式非常接近：它们从 Knuth 的 `tex.web` 开始，并添加一连串变更文件，以生成该引擎的 WEB 源文件。每个 TeX 引擎都需要自己特定的一组变更文件，这些文件必须按严格顺序应用/处理（合并）：顺序出错，合并过程就会失败，因为序列中的每个变更文件都依赖于链条中更早出现的变更文件所引入的修改。

下面是一次 TIE 运行示例，它对 Knuth 的 `tex.web` 依次应用多个变更文件，以生成 `ktex.web`——这个复合 WEB 文件对 Knuth 的 TeX 进行了修改，使其可以准备好（适合）通过 Web2C 过程转换为 C。另请注意以下几点：

* `tex.ch` 是一个非常大的变更文件，其中在许多方面修改了 TeX，使其使用 Kpathsea；
* SyncTeX 扩展是通过多个变更文件添加的。

```
tie -m ktex.web tex.web tex.ch enctex.ch synctex-def.ch0 synctex-mem.ch0 synctex-mem.ch2 synctex-rec.ch0 synctex-rec.ch1 synctex-rec.ch2 tex-binpool.ch
这是 TIE，CWEB 版本 2.4。
版权 (c) 1989,1992 归 THD/ITI 所有。保留所有权利。
(tex.web)
(tex.ch)
(enctex.ch)
(synctex-def.ch0)
(synctex-mem.ch0)
(synctex-mem.ch2)
(synctex-rec.ch0)
(synctex-rec.ch1)
(synctex-rec.ch2)
(tex-binpool.ch)
....500....1000....1500....2000....2500....3000....3500....4000....4500
....5000....5500....6000....6500....7000....7500....8000....8500....9000
....9500....10000....10500....11000....11500....12000....12500....13000
....13500....14000....14500....15000....15500....16000....16500....17000
....17500....18000....18500....19000....19500....20000....20500....21000
....21500....22000....22500....23000....23500....24000....24500....
(未发现错误。)
```

#### 补充：XeTeX 和 pdfTeX

为了完整起见，我们应当指出，pdfTeX 和 XeTeX 的构建过程实际上并不是从 Knuth 的 `tex.web`开始的；它们分别是从名为 `pdftex.web` 和 `xetex.web` 的文件开始的：大概是因为修改已经非常广泛，直接共享/发布已经包含对 Knuth 原始代码所做的那些非常重要的修改的 WEB 文件更合理。

### 一个例子：e-upTeX

日本 TeX 社区开发了许多旨在处理日文排版复杂性的 TeX 引擎：

* **pTeX**：Knuth 的 TeX 引擎经过扩展以支持日文排版；
* **e-pTeX**：e-TeX 与 pTeX 的组合（再加上一些由 pdfTeX 引入的原语）；
* **upTeX**：支持 Unicode 的 pTeX 版本，并增加了更好处理 CJK（中文、日文和韩文）的扩展；
* **e-upTeX**：e-TeX 与 upTeX 的组合（合并）。

#### 生成 e-upTeX 的复合源文件

要创建 e-upTeX 的复合 WEB 源文件（带 SyncTeX），你首先从 Knuth 的 `tex.web` 出发，但需要按以下顺序应用 **26** 单独的变更文件，才能得到一个可从中提取原语命令列表的单一复合文件：

```
etex.ch, tex.ch0, tex.ch, tex.ech, etex.ch0,
ptex-base.ch, uptex-m.ch, euptex.ch0, eptex.ech,
etex.ch1, euptex.ch1, synctex-def.ch0, synctex-ep-mem.ch0,
synctex-mem.ch0, synctex-e-mem.ch0, synctex-ep-mem.ch1,
synctex-p-rec.ch0, synctex-rec.ch0, synctex-rec.ch1,
synctex-e-rec.ch0, synctex-p-rec.ch1, fam256.ch,
pdfstrcmp-eup-pre.ch, pdfutils.ch, pdfstrcmp-eup-post.ch,
tex-binpool.ch
```

#### 应用变更文件：哪些文件，以及按什么顺序？

如前所述，必须按严格顺序应用/处理变更文件，这一点至关重要——但你如何找出需要哪些文件，以及处理顺序是什么呢？幸运的是，这些关键信息记录在 TeX Live 发行版中的文件里，对 TeX Live 源代码的探索揭示了支配每个 TeX 引擎构建需求的规则。遵循这些规则，Overleaf 得以重建每个 TeX 引擎的复合 WEB 源代码文件，并提取出原语列表以供后续数据处理。

## 最后：如何提取原语列表？

一旦复合 WEB 文件构建完成，使用正则表达式提取原语列表这项任务就很直接了，因为所有原语命令都是通过一个名为 `primitive(...)`的单个 Pascal 函数来定义（“注册”）的。下面是一些从 Knuth 的 `tex.web` 源代码中取出的真实示例：

```
primitive("lineskip",assign_glue,glue_base+line_skip_code)
primitive("baselineskip",assign_glue,glue_base+baseline_skip_code)
primitive("parskip",assign_glue,glue_base+par_skip_code)
primitive("abovedisplayskip",assign_glue,glue_base+above_display_skip_code)
primitive("belowdisplayskip",assign_glue,glue_base+below_display_skip_code)
primitive("abovedisplayshortskip",assign_glue,glue_base+above_display_short_skip_code)
...
...
```

如你所见， `primitive(...)` 这个函数非常适合用正则表达式进行文本处理：被注册的原语名称位于引号中（`"..."`），同时还附带用于分类每个原语行为的额外数据（我们不会在此深入讨论）。在提取出每个引擎的原语列表后，这些数据又经过一些 Lua 脚本处理，以生成包含表格化结果的 HTML。

### 回到 LuaTeX

我们已经指出，LuaTeX 的构建过程与其他 8 个 TeX 引擎并不完全相同。简要了解了 Web2C 过程、Pascal 到 C 的转换以及变更文件机制之后，我们现在可以解释 LuaTeX 的不同之处：LuaTeX 的开发者决定放弃将 Pascal 转换为 C 这一繁琐过程——正如在 [LuaTeX 参考手册](http://www.pragma-ade.com/general/manuals/luatex):

> 中所说的：

…编译框架是 web2c，我们继续使用它，但不再执行 Pascal 到 C 这一步。

LuaTeX 的核心引擎被重写为 C，这意味着它的构建过程更为标准，当然也方便得多。一个有益的结果是，LuaTeX 所支持的原语被很好地拆分到一个单独的 C 源代码文件中，这大大简化了访问/列出它们的任务。 [CWEB](https://en.wikipedia.org/wiki/CWEB)，它基于 C 而不是 Pascal。

### 不只是 WEB 文件：还需要其他源代码

在为任何 TeX 引擎（LuaTeX 除外）生成复合 WEB 源文件之后，还必须提取 Pascal 源代码并将其转换为 C 代码，但这并不是完整的解决方案。除了从 WEB 源（Pascal⮕C）生成的 C 代码之外，大多数 TeX 引擎还依赖（需要）若干额外的辅助源代码文件（库），这些文件通常用 C 编写，例如 [Kpathsea](https://www.tug.org/kpathsea/)。辅助源文件（库）实现的功能，不需要或无法用 WEB（Pascal）来编写。任何用 WEB“语言”为 TeX 编写的内容都必须使用 Pascal 语言，随后再被提取并转换为机器生成的 C：如果你不需要这样做，为什么一开始不直接用 C 或 C++ 编写呢。


---

# 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/geng-duo-zhu-ti/18-how-overleaf-created-the-tex-primitive-reference-data.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.
