每个 TeX-based 引擎的根源都是 Knuth 的原始 TeX 及其严格的超集 e-TeX。本页介绍了“TeX 程序”实际上是什么、plain TeX 是什么、为什么今天几乎没有人直接编写它但它仍然很重要,以及 e-TeX 扩展(在 1990 年代添加)现在是每个现代引擎构建的基线。
什么是“TeX 计划”
令人困惑的是,TeX 这个词命名了两件事。一是排版程序本身,由Donald Knuth编写。另一个是程序解释的命令系统。程序本身是一个无情的低级引擎:它将字符和框打包成行和段落,优化换行符,并将结果写入输出文件 - 没有其他任何东西。
由于对其书 *The Art of Computer Programming* 的排版质量不满意,Knuth 在 1978 年开始编写第一个 TeX。它于 1982 年被完全重写,该重写被称为 TeX82。 “TeX”今天实际上意味着这个 TeX82 血统。 Knuth 内置的最小命令集称为原语 — \def、\hbox、\vbox 等。
用裸基元编写文档是不切实际的,因此您通常使用格式——构建在它们之上的一层宏。加载 plain.tex 会得到 plain TeX;加载更大的宏集会给你LaTeX。格式在构建时预先扩展并保存为 .fmt 文件,因此引擎无需在每次运行时从头开始重新读取它们。
Plain TeX
Plain TeX 是 Knuth 在 *The TeXbook* 中提出的标准格式。它将一个最小的工具包(字体设置、基本数学符号、方便的宏(例如 \bye))收集到 plain.tex 中,该工具包随 TeX 本身一起提供。在 LaTeX 之前,这是使用 TeX 的唯一实用方法。
该符号与 LaTeX 明显不同。内联数学为 $...$(与 LaTeX 相同)。水平框是\hbox{...},垂直框是\vbox{...},表格对齐是用\halign完成的,命令是用\def定义的,文本宽度是\hsize,文档以\bye结束。没有 \documentclass 和 \begin{document} ——这些都是 LaTeX 用宏构建的东西。
% plain TeX — tex hello.tex で処理 / process with: tex hello.tex
\hsize=10cm
\font\big=cmr10 at 17pt
{\big Hello, plain \TeX!}
\medskip
This paragraph is set in the default font.
Inline math works too: $E = mc^2$.
\bye使用 tex hello.tex 处理示例会生成 DVI 文件(不是 PDF — 见下文)。 \TeX 等徽标宏和 \medskip(中等垂直跳跃)等间距宏也由 plain TeX 定义。放在 LaTeX \documentclass 文档旁边,它显示了 plain TeX 的皮肤在裸机上有多薄。
识字编程和 WEB
TeX 本身是用 WEB 编写的,这是 Knuth 自己的系统,也是 文学编程 的创始实践。 WEB 源代码是一个单一文档,其中面向人的解释和 Pascal 代码交织在一起;然后,有两个工具从中提取导数 - tangle 生成可编译的 Pascal,weave 生成排版注释(书 *TeX: The Program*)。
原来的目标语言是Pascal;在现代发行版中,有一个名为 web2c 的工具将 WEB(通过 Pascal)转换为 C 进行构建。因此,今天在您的计算机上运行的 pdfTeX 或 LuaTeX 最终仍然可以追溯到那个单一的文字源。
版本号收敛于 π
TeX 的版本编号很特殊。从版本 3 开始,每次更新都会多附加一位数字,因此数字 渐近逼近 π。当前版本是 3.141592653,由当年 2 月的 2021 年调整设置,此后一直未受影响 - 这标志着该程序已基本完成并且异常稳定。
高德纳 (Knuth) 宣称,死后进行的“最终更改”将把版本设置为精确的 π——此时任何剩余的错误都将成为永久功能。事实上,TeX 多年来没有获得任何新功能,报告的问题也只是微小的修复。这种冻结的稳定性正是几十年前的 .tex 文件今天仍然产生相同输出的原因。
METAFONT 和 DVI
TeX 有一个孪生伴侣,METAFONT (mf):一个系统,它不是将字体描述为固定形状,而是描述为*绘制*字形的程序。 Knuth 用它为 TeX 设计了整个 Computer Modern 字体系列。分工是TeX决定角色去哪里,而METAFONT则产生角色本身的形状。
Knuth 的 TeX 直接发出的不是 PDF 而是 DVI (Device Independent) 文件。 DVI 仅包含与设备无关的指令——“将此字符放在这个位置”——然后通过 dvips 转换为 PostScript 或通过 dvipdfmx 转换为 PDF。当今广泛使用的 pdfTeX、XeTeX 和 LuaTeX 的一个主要区别是它们跳过了这一步,直接生成 PDF。
为什么它仍然很重要
在实践中,几乎没有人为新工作编写原始的 plain TeX:实用的机制——编号、交叉引用、类文件——都存在于顶层的 LaTeX 层中,日常写作就是在这种舒适感中进行的。然而,了解这里描述的原语具有真正的价值。
- 每种格式的基础。 LaTeX 和 ConTeXt 等最终都是基于这些原语构建的。
- 您可以看到错误的底部。 一旦您了解了
\hbox和\vbox的词汇,混乱的错误和低级调整就变得可读。 - 盒子和胶水的世界观。 TeX 的从 *盒子* 和可拉伸的 *胶水 * 构建页面的想法是深度使用 LaTeX 的共享语言。
- 通用性和可重复性。 由于引擎被冻结,过去的文档在未来不断产生相同的结果。
什么是 e-TeX
由于 Knuth 冻结了 TeX,因此新功能必须来自其他人。因此,在 20 世纪 90 年代,欧洲的 NTS (New Typesetting System) 项目诞生了 e-TeX,其中 Peter Breitenlohner 负责主要开发工作。 e-TeX 不会取代 TeX;它被设计为它的严格超集 - 现有输入运行不变,具有相同的输出。
e-TeX 有两种模式。在兼容模式下,它的行为与经典的 TeX 完全相同;仅在扩展模式下(通过以 * 启动它来进入)添加的原语才可用。当一种格式以扩展模式构建时,用户根本不需要考虑它。
现在是基线
今天,简单地假设 e-TeX 扩展存在。 自 2003 年以来,TeX Live 默认为 e-TeX,而 LaTeX 自 2017 年起正式要求 e-TeX 原语。更重要的是,当前的每个引擎都捆绑了 e-TeX:pdfTeX、XeTeX 和 LuaTeX 都包含 e-TeX 扩展并在顶部添加自己的功能。
换句话说,当你使用 pdfLaTeX 或 LuaLaTeX 进行排版时,你就在不知不觉中运行在 e-TeX 上。许多现代包(从 expl3 开始,LaTeX3 编程层)如果没有 e-TeX 原语就根本无法存在。
原语 e-TeX 添加
e-TeX 的添加对于编写宏的人来说最重要。最大的是整数、维度和粘合的算术。在裸露的 TeX 中,您必须对暂存寄存器进行洗牌才能进行计算; e-TeX 提供 \numexpr、\dimexpr 和 \glueexpr,它们以可扩展的形式现场计算 (a+b)*c/d 等表达式。
% 拡張モードの e-TeX 系エンジンで / on any e-TeX engine in extended mode
\count0=\numexpr (3+4)*2/7 \relax % 2 が入る / yields 2
% プリミティブが「定義済みか」を安全に分岐 / branch safely on whether a name is defined
\ifdefined\foo \message{foo exists}\else \message{no foo}\fi
% 制御綴を作らずに名前の存在を確かめる / test a control sequence without creating it
\ifcsname chapter\endcsname \message{chapter is defined}\fi
% \unless で「if の否定」を直接書く / negate a conditional directly
\unless\ifnum\count0>10 \message{count0 is not greater than 10}\fi第二个支柱是条件和令牌操作。 \ifdefined 测试是否定义了控制序列,\ifcsname...\endcsname 测试从名称组装的控制序列是否存在 - 两者无副作用(裸 TeX 中的旧 \ifx 技巧可以默默地将未定义的序列转换为 \relax)。 \unless 反转任何条件,\detokenize 将标记列表转换为其字符串形式(类别代码 12 的字符)。
扩张控制也得到加强。 \unexpanded 将其内容保留在适当的位置而不扩展它们,并且 \protected 定义了一个定义的宏,该宏不会在 \edef 和 \write 等扩展上下文中自行扩展 - 这是在低级别正确实现 LaTeX 的 \protect 的关键。
| 原始 | 它的作用 |
|---|---|
\numexpr / \dimexpr / \glueexpr | 就地评估整数/维度/粘合表达式(可扩展) |
\ifdefined / \ifcsname | 测试控制序列是否已定义/存在,没有副作用 |
\unless | 反转下列条件句的含义 |
\protected | 定义一个不会在扩展上下文中扩展的宏 |
\detokenize / \unexpanded | 将令牌转换为字符串/保持令牌不展开 |
\scantokens / \readline | 将字符串重新读取为标记/逐字读取输入行 |
\middle | 在 \left … \right 的中间放置一个弹性分隔符 |
\currentgrouplevel / \interactionmode | 检查当前组深度/获取并设置交互模式 |
除此之外,还有 \middle 用于数学中间的伸缩分隔符,如 \left( … \middle| … \right); \scantokens 将字符串重新读取为标记; \readline 逐字读取输入行; \currentgrouplevel 返回当前组深度;和 \interactionmode 读取和更改交互模式。 e-TeX 还引入了用于双向(从右到左)排版的钩子,这些钩子被引入到 XeTeX、LuaTeX 和日语处理的后续工作中。
一个安静但重要的扩展是寄存器的大量增加。裸露的 TeX 只有 256 个 \count、\dimen、\skip、\toks 等; e-TeX 将其提高到 32768 (分配为稀疏数组)。即使加载许多大型类和包,寄存器也不再那么容易耗尽——这是当今重量级 LaTeX 设置下的无名支持。
这对 LaTeX 文档有何帮助
您不需要在plain TeX中撰写论文或技术文档。不过,了解词汇 \hbox、\vbox、\count、\dimen 和 \def 可以让您阅读底层的错误消息。例如,Overfull \hbox 表示材料从线盒中伸出; Missing number, treated as zero 说 TeX 期望一些可读的数字或尺寸,但没有找到它。
理解这一层与直接在其中写散文是不同的。让 LaTeX 的结构命令处理标题、列表、交叉引用和定理环境;保留低级命令来读取某些内容损坏的原因或狭隘地修复一个类。即使在定义新命令时,\newcommand 或 \NewDocumentCommand 也比原始 \def 更好地服务于普通文档,因为 LaTeX 可以检查参数计数以及与现有命令的冲突。
- 编写散文时,使用 LaTeX 结构命令并保持低级基元很少。
- 修复类或包时,e-TeX 工具(例如
\ifdefined和\numexpr)比旧的 TeX 技巧更安全。 - 阅读日志时,将警告翻译成TeX的盒子、胶水和寄存器语言;原因变得可见。