expl3 / LaTeX3 层

expl3(“expl-three”)是从内部支撑现代 LaTeX 的编程语言。它源自曾被称为“LaTeX3”的项目,如今已默认载入 LaTeX 内核。它远远超出 \newcommand 能写的范围,提供变量、数据结构、整数运算,甚至浮点数,是 xparse、siunitx、fontspec 等宏包赖以构建的正式编程层。本页会介绍如何切换到它的语法、它独特的命名规则,以及核心模块。

expl3 是什么

TeX 本质上是宏处理器:用 \def\newcommand 定义命令(宏)。但编写大型宏包时,TeX 原始原语的一致性不足,展开控制和变量处理常常变成手艺活。expl3 给 TeX 与 e-TeX 原语重新命名,系统化地区分函数和变量,并让每个函数的参数类型显式可见。它是 LaTeX Project 多年整理出的,某种意义上既是 LaTeX 的标准库,也是它的编程语言。

实践中有一个重要事实:现代 LaTeX 内核默认载入 expl3。官方 interface3 手册明确写道:“在最新的 LaTeX 2ε 内核中,这些材料作为格式的一部分载入。” 因此已不再需要写 \usepackage{expl3}。多数用户并不直接编写 expl3,而是通过 xparse\NewDocumentCommand)以及 siunitx、fontspec、l3keys2e 等建立在它之上的宏包间接使用。

切换语法 — \ExplSyntaxOn

expl3 代码写在 \ExplSyntaxOn\ExplSyntaxOff 包围的区域中。在这一区域里,字符的读法(类别码)有两点变化。第一,空格和换行会被忽略,因此可以缩进代码,在记号之间留空,让代码更易读。第二,_(下划线)和 :(冒号)会被当作“字母”,可以出现在命令名中。这是普通 LaTeX 不允许的,也正是 expl3 喜欢使用长而描述性命令名的基础。

因为空格会被忽略,所以 要在输出中产生真正的空格时,需要另一种写法。在 expl3 区域中,半角空格写作 ~(波浪号)。例如想排出“Item: apple”,应写成 Item:~#1,因为裸空格会直接消失。只要掌握这一点,其余部分读写起来就很像普通编程语言。

document.tex
% expl3 はカーネルに含まれるので \usepackage{expl3} は不要
\ExplSyntaxOn
  % ここでは空白・改行が無視され、 _ と : が命令名に使える
  \tl_new:N  \l_greeting_tl
  \tl_set:Nn \l_greeting_tl { Hello,~world! }
  \tl_use:N  \l_greeting_tl
\ExplSyntaxOff

命名规则与参数签名

expl3 函数名的形式是 \⟨module⟩_⟨description⟩:⟨arg-signature⟩。第一个 _ 之前是 模块名(数据类型或功能领域),: 之前是 描述性名称: 之后是 参数签名(arg-spec)。例如 \seq_put_right:Nn 中,模块是 seq(序列),描述是 put_right(追加到右端),签名是 Nn。签名中的每个字母表示 在传入该位置的参数之前要如何处理它

主要参数指定符如下(依据官方 interface3 手册)。Nn 最基本,仅凭它们就能做很多事。涉及展开的 xeof,以及取出变量值的 Vv,可以等熟悉后再逐步掌握。

指定符含义
N不做处理;单个记号(通常是一个控制序列)。
n不做处理;用花括号包住的记号列
c先把内容通过 \csname 转换成控制序列 再使用。
V / v取出并传递 变量的值V 来自单个记号,v 先构造名称)。
o展开一次 再传递。
x / e完全展开x 类似 \edef,不可展开;e 使用 \expanded)。
f从左到右展开到 第一个不可展开记号 为止。
pTeX 的 参数文本#1#2…);用于定义函数。

变量的命名也类似,但开头有 表示作用域的一个字母l_ 表示 局部(只在当前 TeX 分组内改变),g_ 表示 全局c_ 表示 常量(值不应改变)。变量名末尾有类型标识,如 _tl(记号列表)、_int(整数)、_seq(序列)、_prop(属性列表)、_clist(逗号列表)、_fp(浮点数)等。因此 \l_my_name_tl 一眼可读为“局部记号列表变量”,\g_counter_int 则是“全局整数变量”。各模块还提供 scratch 变量,即一次性临时变量,例如 \l_tmpa_tl\l_tmpb_int

核心模块

expl3 按数据类型分成多个模块,它们都遵循同样的节奏:创建、设置、使用。先看 函数定义\cs_new:Npn 定义新函数,若同名函数已存在则报错(cs 表示 control sequence)。\cs_set:Npn 也定义函数,但只限当前 TeX 分组内,重新定义也不会报错。二者都使用 :NpnN 是要定义的函数名,p 是参数文本(#1#2…),n 是函数体(替换文本)。

接着是 记号列表(tl。这是最基本的变量,可像字符串一样使用。用 \tl_new:N 声明,用 \tl_set:Nn 替换内容(旧内容会被丢弃),用 \tl_use:N 输出到正文。整数(int 也类似:用 \int_new:N 声明,用 \int_set:Nn 赋值,\int_eval:n 会计算其中的整数表达式并返回结果(例如 \int_eval:n { 2 + 3 * 4 } 得到 14)。

序列(seq 是可从两端存取的列表,也可用作栈。用 \seq_new:N 创建,用 \seq_put_right:Nn 在右端追加元素,用 \seq_map_inline:Nn 依次处理所有元素;后者会把每个元素作为 #1 传给你的代码。属性列表(prop 则是字典,也就是键值映射。用 \prop_new:N 创建,用 \prop_put:Nnn 存入键和值(Nnn 表示“变量、键、值”三个参数)。

逗号列表(clist 顾名思义,用来处理逗号分隔的值序列,所有命令都以 \clist_ 开头(如 \clist_new:N\clist_set:Nn)。最后是 浮点数(fp\fp_eval:n 会计算包含小数的表达式,并支持 sinsqrtpi 等多种科学函数(例如 \fp_eval:n { sqrt(2) }\fp_eval:n { 2 * pi })。重点是区分它和只能处理整数的 \int_eval:n

小例子 — 函数与序列

把以上内容组合起来看看。下面的例子用 \cs_new:Npn 定义函数 \example_add:n,它向序列中添加一个项目;调用几次后,再用 \seq_map_inline:Nn 把所有项目按列表形式输出。请注意,因为位于 \ExplSyntaxOn 区域内,命令名中包含 _: 也可以正常书写。

document.tex
\documentclass{article}
\begin{document}
\ExplSyntaxOn
  % シーケンスを 1 個用意する
  \seq_new:N \l_example_fruits_seq

  % 「右端に 1 項目追加する」関数を定義する
  \cs_new:Npn \example_add:n #1
    { \seq_put_right:Nn \l_example_fruits_seq {#1} }

  % 何度か呼ぶ
  \example_add:n { apple }
  \example_add:n { banana }
  \example_add:n { cherry }

  % 全項目を出力する(各項目が #1 に入る。 ~ は空白)
  \seq_map_inline:Nn \l_example_fruits_seq
    { Fruit:~#1 \par }
\ExplSyntaxOff
\end{document}

编译后会分别输出三行:“Fruit: apple”、“Fruit: banana”、“Fruit: cherry”。\cs_new:Npn 中的 #1 是被定义函数的参数,而 \seq_map_inline:Nn 中的 #1 是正在遍历的每个元素;二者都按 n 型(花括号内容)处理。注意 Fruit:~#1 中的 ~:若换成普通空格,空格会被忽略,结果会粘成 “Fruit:apple”。

实际使用方式

普通文档写作几乎不需要直接写 expl3。但一旦开始 编写宏包或类,expl3 如今已是事实标准。常见搭配是用 \NewDocumentCommand(xparse)接收面向用户的命令,再用 expl3 实现内部处理。需要区分两件事:xparse 的参数指定(mO{...}s 等“文档层”参数)与 expl3 的参数签名(Nn 等“编程层”处理指定)不是同一个概念。前者在 xparse 页面详细说明。

学会 expl3 后,许多只用 \newcommand 难以清楚表达的处理就能写得明晰。需要记住的要点如下:

  • 代码必须包在 \ExplSyntaxOn\ExplSyntaxOff 中。区间内空格会被忽略;需要输出空格时使用 ~
  • 它已包含在内核中,因此不需要 \usepackage{expl3};在 .sty 中可在宏包声明后直接书写。
  • 函数名为 \⟨module⟩_⟨description⟩:⟨signature⟩;变量名为 \⟨scope⟩_⟨name⟩_⟨type⟩,作用域为 l_ / g_ / c_
  • 不要凭空发明命令名。命名非常严格,官方 interface3 手册(texdoc interface3)是权威资料。