> For the complete documentation index, see [llms.txt](https://ayakaleaf-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://ayakaleaf-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)

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

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

## 完整版本：想了解细节？继续往下读……

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

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

我们特别指出 LuaTeX 是个例外，因为其核心引擎代码已用 C 语言重写，以去除 Pascal 以及下面所述的其他遗留复杂性（Web2C）；因此，尽管 LuaTeX 的源代码相当庞大，但与其他 TeX 引擎相比，它的“打包”和分发方式要容易理解得多。正因如此，并且基于从源代码构建它们所使用的工作流程/过程，将 TeX 引擎分成两类更为方便：

1. LuaTeX：自定义（更现代）的构建流程
2. 其他所有引擎：传统（Web2C）构建流程

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

正如我们下面将要探讨的，Knuth 将其最初的 TeX 源代码作为一个单一的、整体式的文件发布，名为 `tex.web` 他每 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”这一程序名重新分发。在源代码中他写道：

```
% This program is copyright (C) 1982 by D. E. Knuth; all rights are reserved.
% Copying of this file is authorized only if (1) you are D. E. Knuth, or if
% (2) you make absolutely no changes to your copy. (The WEB system provides
% for alterations via an auxiliary file; the master file should stay intact.)
```

以及：

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

其要义是：不要通过编辑并分发修改后的主 TeX 源代码来做更改，然后继续把它称为 `tex.web`。如果你确实想做更改，例如添加新的原始命令等，那么你必须通过“借助辅助文件进行修改”来应用这些更改，并为你的“TeX 的衍生物”程序取一个与“TeX”区别开的名称，而“TeX”的排版形式（$$\mathrm\TeX$$）是美国数学学会的商标。

### 遗留传统的延续

尽管曾有人尝试使用现代编程语言和方法论彻底重写 TeX——例如两个基于 Java 的项目 [新排版系统](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 的项目和倡议的历史是一个有趣的话题，读者也许会想 [访问英国 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-du-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 文件来提取文档或源代码。要访问程序文档，你需要处理 WEB 文件（例如， `tex.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 引擎中。随着支持 Unicode 的 TeX 引擎不断发展，对 EncTeX 的需求在很大程度上已被取代——但请注意，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`——即对 Knuth 的 TeX 进行修改后的复合 WEB 文件，使其可以通过 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 版。
Copyright (c) 1989,1992 by THD/ITI. All rights reserved.
(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（含 SyncTeX）的复合 WEB 源文件，你从 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 文件构建完成，使用正则表达式提取原始命令列表的任务就很直接了，因为所有原始命令都是通过一个名为的单个 Pascal 函数来定义（“注册”）的 `primitive(...)`。以下是取自 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 源代码文件中，这大大简化了访问/列出它们的任务。

严格来说，我们还应指出，LuaTeX 的某些源代码文件使用了 Knuth 文学编程方法论的一个变体，称为 [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）编写的功能。为 TeX 用 WEB“语言”编写的任何内容都必须使用 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://ayakaleaf-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.
