从这次的内容开始,我们就将正式进入 LaTeX3 的世界了。
架构 / 层次设计
上次我们已经介绍过,LaTeX3 的主要成果都凝结在了 expl3
之中。实际上,LaTeX3 是一套非常庞大的框架,集编程和排版为一体。根据我们之前的介绍,LaTeX3 为此做出了一个层次划分:
-
文本标记层。这一层主要提供给文章作者使用。显然,考虑到历史兼容性,这一层次与我们至今仍在使用的 LaTeX 2ε 并没有显著的差异。对于 LaTeX 的一般用户而言,从 LaTeX 2ε 转换到 LaTeX3 的学习成本可以说几乎为零;甚至由于一些新接口、新语法的使用,使用 LaTeX 将变得更加方便。
-
设计接口。传统的 LaTeX 2ε 并没有提供这一个层次。也就是说,用户要么使用 LaTeX 本身或者宏包提供的功能,要么就必须通过底层编程来进行控制,不存在这样一个所谓「设计模板」的存在。编写模板的人,我们暂且可以称之为是「设计师」,他们只需要利用编程框架设计模板,而无需考虑用户(即文章作者)究竟用模板写了怎样的内容。
-
编程接口。这一层次实际上就是
expl3
,它的实现基于 TeX 的原语,提供了丰富的编程工具,也是上面两个层次的实现手段。
这三个层次是紧密联系在一起的。如前所述,expl3
宏包提供了编程接口;xtemplate
宏包给出了实现「文档原型」的方法,也就是提供了上面所说的「设计接口」;最后,xparse
宏包用来定义文档层的命令和环境,即所谓「文本标记」。
LaTeX3 相关宏包
对于具体的用户来说,无论是文章作者、设计师还是程序员,使用 LaTeX3 在目前阶段仍需要通过调用一系列宏包来完成。
目前,在 CTAN 中与 LaTeX3 相关的有五个软件包:
-
l3kernel
:包含了expl3
宏包的各个部分。 -
l3packages
:提供较高层次的接口(设计层和文本标记层),这些宏包的语法接口都较为稳定。主要包括:-
l3keys2e
-
xfp
-
xfrac
-
xparse
-
xtemplate
-
-
l3experimental
:一些实验性的尝试,同样用来构建较高层次的语法接口,但不如l3packages
稳定。目前主要有:-
l3benchmark
-
l3cctab
-
l3color
-
l3draw
-
l3str
-
l3sys-shell
-
xcoffins
-
xgalley
-
-
l3backend
:提供与后端(底层驱动)相交互的代码,处理颜色、绘图、PDF 特性等功能,目前主要支持以下几种驱动: -
dvipdfmx
-
dvips
-
dvisvgm
-
xdvipdfmx
-
PDF 模式(即 pdfTeX 和 LuaTeX)
-
-
l3build:LaTeX3 的构建系统,用来进行单元测试、文档排版、自动化发布等。它利用一系列 Lua 脚本来实现跨平台的功能。
这就是当前 LaTeX3 的主要组成。除此以外,在 LaTeX3 的 GitHub 存储库中,忽略文档、测试文件和辅助文件等,还有以下几个部分:
-
l3trial
-
l3leftovers
-
xpackages
这是一些高度实验性的功能以及一些弃用的模块。对于普通用户和开发者来说,它们不应该直接使用。
命名规范
前面铺垫了很多,现在我们终于可以开始尝试 LaTeX3 了。
和我们熟知的 LaTeX 2ε 不同,LaTeX3 对函数和变量做出了区分。函数可以吃掉一些参数,并进行相应的操作;函数要么是可被展开的,要么就是可被执行的,以后讲到展开控制的时候我们还会详细介绍。变量用来存储数据,它会被函数所调用。一些具有相关功能的函数和命令可以构成一个模块。
LaTeX3 中的命令,无论是函数还是变量,仍然都是以反斜杠 \
开头。所不同的是,我们可以在命令中使用下划线 _
,用以区分不同单词。
函数
按照规范,LaTeX3 中的函数名包括三部分:模块名(module
)、描述(description
)以及参数指定(arg-spec
),形如
\<module>_<description>:<arg-spec>
注意参数指定需要放在冒号 :
后面。不必奇怪,冒号也是命令的一部分。
参数指定
模块名与描述的含义都是显而易见的。「参数指定」,指的是这一函数要吃掉怎样的一些参数,它由一串字母组成(区分大小写)。最基本的参数指定包括:
-
n
:普通(normal)参数,表示一组由大括号{…}
包围的 token(记号,或者叫字元)列表,这其实就是TeX 中的标准宏参数 -
N
:表示单个 token,比如一个控制序列(由\
开头的命令),或者一个单独的字符 -
p
:原始 TeX 的形参(parameter)指定。具体来说,就是我们在用\def
定义新命令时所用的#1
、#1#2
等 -
T
,F
:这两个是n
的特殊情况,用来给出条件分支(True、False)
还有两个特殊的参数指定:
-
D
:表示不要使用(Do not use)。由D
开头的命令是原语的封装,在l3kernel
之外尽量不要直接使用(当然有时候不可避免) -
w
:奇异型(weird)参数,表示不遵循标准参数指定的一些特例
参数指定在 LaTeX3 中发挥着至关重要的作用。LaTeX3 的展开控制机制将会引入更多类型的参数指定,以后我们会详细介绍。
函数的例子
-
\cs_new:Npn
:这一函数属于cs
模块(控制序列,control sequence)。顾名思义,它用来创建新的函数。三个参数分别是:这一函数的行为类似于
\def
:% LaTeX2ε \def\myfunc#1{Hello #1} % LaTeX3 \cs_new:Npn \my_func:n #1 { Hello~ #1 }
注意到开启 LaTeX3 语法后,单词间的空格是不起任何作用的(catcode=9,即可忽略字符)。确实要使用空格时,则用
~
代替。至于需要使用~
的原来意思,即不可断开的空格(俗称「带子」)时,可以用原来的宏\nobreakspace
。-
N
:函数名称,由于是\
开头的控制序列,因而总是单个 token -
p
:新创建的函数的形参指定 -
n
:具体的函数定义
-
-
\int_if_even:nTF
:它属于int
模块,用于处理整数。这一函数的作用是判断一个数字(由n
参数接受)是不是偶数,若是,则执行T
分支,否则执行F
分支。\int_if_even:nTF { 12 } { <true code> } { <false code> }
显然以上这段代码会执行
<true code>
。一般来说,这种条件判断函数在定义时会同时创建多种分支结构,比如
\int_if_even:nT
和\int_if_even:nF
也可以使用。\int_if_even:nT
表示数字为偶数则执行T
分支,否则什么也不做;\int_if_even:nF
也是类似的。
变量
LaTeX3 中,变量的名称包括四个部分:作用域(scope
)、模块名(module
)、描述(description
)以及变量类型(type
),形如
\<scope>_<module>_<description>_<type>
通常来说,变量名中只包含字母和下划线(_
)。
代码风格
我们知道,Google 为 C++ 和 Python 等提供了格式指南(style guide),但传统上 TeX 和 LaTeX 这样的宏语言却并没有类似的代码规范,因此很多时候可读性实在不敢恭维。
通过修改空格、下划线等字符的类别码,LaTeX3 大大提供的代码的可读性。这样,我们也可以相应地给出一些格式规范:
-
每行不超过 80 个字符
-
各元素之间添加空格以增加可读性,除了少数使用简单参数的情况,如
{#1}
、#1#2
等 -
每一层语义应当独占一行,比如 true 和 false 分支就应至少占据两行
-
对不同层次的代码合理使用缩进。缩进可以使用两个空格,但不要用
tab
-
左花括号单独占据一行,并且也需要缩进
以下是一个示例:
\cs_new:Npn \my_foo:nn #1#2
{
\tl_if_empty:nTF {#1}
{ \my_foo_aux:n { X #2 } }
{
\my_foo_aux:nn {#1} {#2}
\my_foo_aux:n { #1 #2 }
}
}
当然,没有一种规范是可以放之四海而皆准,特殊的地方总还是免不了特殊对待。
编程环境
之前我们就已经提到过,LaTeX3 目前为止还没有成为一个独立的格式。使用 LaTeX3,仍然需要在 LaTeX 2ε 中调用 expl3
宏包。
如果只在一个 .tex
文件中使用,可以这样做:
\documentclass{article}
\usepackage{expl3}
\ExplSyntaxOn % 开启 LaTeX3 编程环境
...
\ExplSyntaxOff % 关闭 LaTeX3 编程环境
如果是要编写宏包或文档类,标准做法与在 LaTeX 2ε 中类似:
\RequirePackage{expl3}
% 宏包使用 \ProvidesExplPackage
% 文档类使用 \ProvidesExplClass
% 其他文件使用 \ProvidesExplFile
\ProvidesExplPackage{<package>}{<data>}{<version>}{<description>}
% 之后开启 LaTeX3 语法,文件末尾处则会自动关闭
第二种方法继承并扩展了 LaTeX 2ε 中 \ProvidesPackage
、\ProvidesClass
和 \ProvidesFile
的功能,大致相当于
% Package info
% 文件开头
\makeatletter
\ExplSyntaxOn
...
% 文件结尾
\ExplSyntaxOff
\makeatother
因此在编写宏包或文档类时,@
符号可以被当成字母使用。