美文网首页
TeX 编程

TeX 编程

作者: 明夷 | 来源:发表于2021-04-24 21:15 被阅读0次

此文大概写于 2018 年 11 月 8 日。我已经忘记了当初为什么没有写下去。以后可能也不会再写下去。不过,有一个好消息,我偶尔发现有人和我有同样的想法,他写了一份比下文更完善的 TeX 编程笔记

TeX 是一种面向文档排版的计算机编程语言,适用于处理科技文献的排版任务,但本文几乎不关心 TeX 的排版功能,仅从一门完备的编程语言应当具备的要素的角度去认识它。

常量

最基本的常量是单个字符,TeX 解释器会照实对其予以解释。例如

Hello world!

TeX 解释器逐字照实输出。输出到何处?现代的 TeX 解释器,诸如 pdftex、xetex、luatex 等,会将其输出至 PDF 格式的文档。

常量之间只有一种运算,即连接。我们在输入文本时,便已经实施了该运算——将一组字符连成文本。若将一组字符合成为一个常量,可以用 {...},例如

{Hello world!}

变量

变量可通过无参数的宏予以构造,例如通过 \def 构造一个变量并赋值:

\def\myvar{Hello world!}

\myvar 便是一个变量,它的值为 Hello world!。在 TeX 中,变量只有一种类型,即文本类型。

函数

函数即有参数的宏。我知道它应该叫作宏,但是不要改正我,在这篇文章里我喜欢叫它函数。

函数与变量并没有本质上的区别。所以,在数学中,变量也会被称为常函数。

函数可以吸收常量或变量,将它们与其他常量或变量进行组合。例如,

\def\myfunc#1{#1 world!}

\myfunc 吸收了常量 Hello

\myfunc{Hello}

之后,就会将 Helloworld! 连接为 Hello world!{...} 可将一组字符常量合并为一个常量。在上例中,若不用 {...},而是直接

\myfunc Hello

结果得到的是「H world!ello」,因此 \myfunc 此时只吸收常量 H,剩下的 ello 只能等待与 \myfunc 的结果连接。

若将 Hello 作为值赋予一个变量,\myfunc 也能吸收这个变量:

\def\hello{Hello}
\myfunc\hello

由于函数与变量并没有本质区别,所以函数也能吸收函数,例如:

\def\foobar#1{#1 {Hello}}
\foobar\myfunc

结果为「Hello world!」。

若一个函数将吸收到的量与这个函数自身进行组合,结果会导致 TeX 解释器陷入到不停地解释这个函数的过程,直至崩溃。例如

\def\foobar#1{#1\foobar{#1}}
\foobar{Hello world!}

在现实世界,类似这种形式的机器叫永动机。与 TeX 世界一样,现实世界也造不出永动机。换言之,若现实世界能造出永动机,那么在 TeX 世界一定也能。

\foobar 不吸收任何量,也不与任何量组合,即

\def\foobar{\foobar}
\foobar

在 TeX 的世界里,它可以永动,然而它却什么都不能做了。像 \foobar 这样的宏,在 TeX 中称为递归宏……不是说好了吗不叫宏的吗?递归函数。

寄存器和条件

永动机虽然造不出来,让一个函数自身与其所吸收的量进行组合,这种形式可以产生循环形式的动力。在现实世界,利用这种动力所取得的上天入地效果,我们都有所见识。在 TeX 世界里也能如此,否则就不会有 LaTeX 和 ConTeXt 的出现。但是,要利用这种动力,就需要通过一些开关对其进行控制,否则这种动力便会摧毁整个 TeX 世界。

最简单的开关是控制循环的次数,即控制一个函数自身与其所吸收的量进行组合的次数。这需要使用 TeX 的计数器。使用 \newcount 可以向 TeX 申请一个计数寄存器作为计数器,例如

\newcount\mycount

若让这个计数器从 0 开始,只需

\mycount=0

若要控制函数自身与其所吸收的量进行组合的次数不大于 10 次,只需在该过程中增加控制语句

\ifnum\mycount=10
\else 函数自身与其所吸收的量的组合\advance\mycount by 1
\fi

例如

\def\foobar#1{
  \ifnum\number\mycount=10
  \else #1\advance\mycount by 1\foobar{#1}
  \fi
}

\newcount\mycount
\mycount=0
\foobar{Hello world!}

可将 Hello world! 分段输出十次。

\newcount 的作用是分配一个未使用的计数寄存器,并赋予它一个名字。通过这个名字便可以使用这个计数寄存器中存储的数值。Knuth 的 TeX 最多支持 256 个计数寄存器,现代的 TeX 对此进行了扩展,例如 LuaTeX 可支持 65536 个。可直接以数字为后缀的 \count 使用计数寄存器,例如

\def\foobar#1{
  \ifnum\number\count65534=10
  \else #1\par\advance\count65535 by 1\foobar{#1}
  \fi
}

\count65535=0
\foobar{Hello world!}

但是这样做,很容易引起混乱。例如,倘若某种 TeX 格式将 \count65535 用于存储某个重要的排版数据,这里使用了这个寄存器,那么这个寄存器中原有的值就会被覆盖,可能会导致排版结果出现难以预测的结果。因此,通常推荐使用 \newcount 申请一个尚未被使用的寄存器。这里需要纠正一下前文中的一个说法,TeX 的变量的类型只有,即文本类型。通过 \newcount 构造的计数寄存器本质上是整数类型的变量。

\advance 用于整型变量的加减运算,例如对一个整型变量加 10,再减 30,再增加 1 倍:

\newcount\abc
\abc=0
\advance\abc by 10
\advance\abc by -30
\advance\abc by\abc
\the\abc

结果为 -40。\the 用于攫取整型变量的值。

事实上,TeX 变量的类型还有更多。除了计数寄存器,还有盒子(box)寄存器、维度(dimen)寄存器、skip 寄存器、musikip 寄存器以及 toks 寄存器,这些变量的值皆能用 \the 获取。

\ifnum 用于比较两个数值的关系,即大于、小于和等于。类似的条件语句还有

  • \iftrue 永远为真,\iffalse 永远为假;
  • \if:测试两个字符是否相同;
  • \ifx:测试两个记号(Token)是否相同;
  • \ifcat:测试两个记号的类别码是否相同;
  • \ifdim:比较两个尺寸的关系;
  • \ifodd:测试一个数值是否为奇数;
  • ……更多的,见《The TeXbook》第 20 章 ……

这些条件语句,待需要使用它们之时再作细究。

尾递归

利用递归函数可以制作通用的循环语句,例如若制作类似于 TeX 的 \loop ...\repeat 的结构,只需

\def\myloop#1\repeat{\def\body{#1}\myiterate}
\def\myiterate{\body\myiterate\else\relax\fi}

\relax 是个什么都不做的控制序列,将其删除,对 \myloop 毫无影响,但是使用它可以让 \myiterate 的定义更清晰。

现在,用 \myloop ...\repeat 结构将 Hello world! 输出 10 次:

\newcount\mycount
\mycount=0
\myloop Hello world!\advance\mycount by 1\ifnum\mycount<9\repeat

现在来看 \myiterate 的定义……

未完……

另附

在 TeX 编程中,类别码(Category Code)和记号(Token)是非常基础的两个概念。可通过 TeX 的作者 Donald Knuth 所写的《The TeXbook》的第 7 章了解它们。

TeX 按行读取文档中的字符。在该过程中,TeX 会对读入的字符进行分类。在 TeX 看来,字符可分为 16 类,类的编号从 0 到 15。经 TeX 分类后的每个字符构成记号。此外,TeX 的控制序列也构成记号。因此,TeX 读取文档的过程便是生成记号序列的过程,记号序列由字符记号和控制序列记号构成。

字符记号所属的类别决定了 TeX 在读入文档如何理解它们。例如,当 TeX 读入字符 { 时,会将它归为类 1,属于这一类别的字符记号,TeX 会将其视为一个编组的开始符号。当 TeX 读入字符 } 时,会将它归为类 2,属于这一类别的字符记号,TeX 会将其视为一个编组的结束符号。因此,当 TeX 读入类似 {天地一指也,万物一马也。} 这样的字符序列之后,会将 天地一指也,万物一马也。 视为一个编组。

对于一个字符,TeX 本身并不知道它应当归于哪个类别。字符所属类别需要由 TeX 的使用者通过控制序列 \catcode 予以设定,这个控制序列是 TeX 的原始控制序列。不过,在使用某种 TeX 格式排版时,该格式会对字符进行归类,用户只需承认这些归类的合理性,然而心安理得地使用这种 TeX 格式完成排版任务。

TeX 的使用者有时也需要临时地修改某些字符的类别。例如,现在有许多网站支持 TeX 数学公式,但是对于行内公式,这些网站往往会将公式文本放入 \(\) 之间,而不是放入 TeX 所沿袭的一对 $ 之间。若让 TeX 也支持这种形式,只需将 () 的类别编码修改为 11(字母类别),然后便可以定义 \(\) 宏`,之后再复原它们的类别编码:

\catcode`(=11
\catcode`)=11
\def\({$}
\def\){$}
\catcode`(=12
\catcode`)=12

之后,在 TeX 文档里便可以像下面这样写数学公式:

行内公式:\(E=mc^2\)

在 Markdown 中,\(\) 需要写成 \\(\\)。不过,在几乎所有的 TeX 格式中,\ 用作控制序列的开始记号,这样就很难定义 \\(。对于这种情况,不妨先以 \(\) 代替 $,在此基础上,利用文本编辑器的替换功能将 \(\) 替换为 \\(\\)。例如

$ sed -i 's/\\(/\\\\(/g; s/\\)/\\\\)/g' foo.tex

若将文档中的 \\(\\) 再复原为 \(\),只需

$ sed -i 's/\\\\(/\\\(/g; s/\\\\)/\\\)/g' foo.tex

相关文章

  • TeX 编程

    此文大概写于 2018 年 11 月 8 日。我已经忘记了当初为什么没有写下去。以后可能也不会再写下去。不过,有一...

  • 关于tex.web以及WEB编程

    今天捣鼓了一下tex.web。有一点挤出的同学可能知道.web文件通过两个命令可以得到两种不同类型,即: tang...

  • TeX

    TeX这种工具最好是直接用,边用边学习和掌握. 这符合学习工具的原则:学会20%最基本的功能就能完成80%最常见的...

  • tex

    #哈 ##哈哈

  • Tex

    texlive tlmgr 安装 将/usr/share/texmf-dist/scripts/texlive/t...

  • MarkDown插入公式测试

    $$\alpha_{\sqrt{tex}}^{\beta}$$ 和 $\alpha_{\sqrt{tex}}^{\...

  • Ubuntu下TeX live的安装与配置

    TeX live是可以作为Tex文件生成的软件系统,提供了在Unix(包括LInux)环境下的完整Tex 系统。 ...

  • GORE-TEX

    1970年GORE申请了专利GORE-TEX®GORE-TEX®的标识众所周知,但究竟什么是GORE-TEX®面料...

  • 在安卓手机上编译LaTeX文档

    关于LaTeX是什么,TeX的起源,TeX与word的对比,TeX的优势,本文不会论述,毕竟看这篇文章的人肯定不会...

  • pycharm LaTeX

    一、安装TeX Live TeX Live下载地址https://mirrors.tuna.tsinghua.ed...

网友评论

      本文标题:TeX 编程

      本文链接:https://www.haomeiwen.com/subject/jtulrltx.html