TeX 的宏 / 第二集

TeX 的宏 \ 第一集

Garfileo posted @ 2010年5月21日 18:56 in LuaTeX 道阻且长 with tags tex luatex , 5001 阅读

以 ‘\’ 为开头的 TeX 命令被称为“控制序列”。如果一个控制序列仅由字母组成,将其称为“控制字”。如果一个控制序列由非字母构成,将其称为“控制符”。对于 TeX 用户使用 \def 定义的新的控制序列,将其称为“宏”。

把它嚼碎

\def\vec{$A_1,A_2,\ldots,A_n$} 定义了 \vec 宏,这个宏的值是一串“token(记号)”。所谓 token,其为 TeX 对文稿(即 .tex 文件)进行初步处理后所生成的中间数据,提供给 TeX 内部的后续处理过程使用。一个 token,可以是一个字符,可以是一个控制序列,也可以是一个参数。由 TeX 文稿生成 token 的过程,经常被比喻为食物在口中被牙齿嚼碎的过程。TeX 文稿被嚼碎后,还要被传送到食道、胃以及肠道等器官内依序处理。

若一个宏被定义,它的值会被保存在一个表中,以便 TeX 对该宏展开时使用。注意,囊括宏值的括号需要对称出现。

看这样一个例子:\def\abc{1 \x a}。宏 \abc 会被 tex 程序嚼碎为 4 个 token:

  • “1”,其 catecode 为 12,所属类别为其他字符(数字、符号等)
  • “ ”,其 catecode 为 10,所属类别为空格
  • “\x ”,无 catecode,所属类别为控制序列
  • “a”,其 catecode 为 11,所属类别为字母

要使用一个已定义的宏,输入其名称即可。例如输入 \vec,即调用前面所定义的那个宏。tex 程序在处理文稿时,遇到 \vec,就将其展开,即从保存宏值的表中复制其值并替换。

tex 程序在咀嚼 TeX 文稿的过程中,遇到宏的定义语句,只是简单的将宏的定义转化为一系列 token,然后找块内存空间存放起来。只有在宏被调用时,tex 程序方对其进行展开处理,从内存中调出其对应 token 系列,然后对其中的每个 token 作以下处理:如果是字符 token,就排版显示;如果是宏 token,就进一步展开;如果是 TeX 的基本命令,就执行。

来看这样一个另类的例子:\def\test{\catcode`\\=12 \everypar{*}}。在 \test 宏的定义中,首先使用 \catcode`\\=12 将字符 “\” 的 catecode 修改为 12,即将 “\” 作为很普通的字符,然后又使用控制序列 \everypar{*} 实现在每个段落之前添加“*”字符。这个示例的另类之处在于给 TeX 新手造成了一点点字面上的悬念:既然已将字符 “\” 定义为一般字符,那么 \everypar 就应该失效。一 定不要忘记:宏在定义时,其值所包含的控制序列仅仅被 tex 程序转化为 token,并不执行它们。

事实上,对于上例所定义的 \test 宏,即使调用它,也不会出错,\everypar 工作正常。这是因为 tex 程序在处理宏的定义时,已将 \everypar 转化为 token 并保存。在调用 \test 时,即便 \catcode 会篡改 “\” 的本意,但它不会影响 \everypar,只 会影响 \test 宏调用语句之后的控制序列。

藉由参数而变化

宏可以节省 TeX 文稿的输入时间,因为我们可将需要频繁输入的一些文字信息定义为简单的宏并使用。事实上,宏最大的妙用就是赋予其参数后所产生的各种变化,像 LaTeX 与 ConTeXt 格式,皆由 TeX 所变。

上一节所定义的宏 \vec,现在对其重新定义并赋予它两个参数:\def\vec#1#2{$#1_1,#1_2,\ldots,#1_{#2}$}。 这样,如果我们想输入 $a_1,a_2,...,a_n$ 或者 $b_1,b_2,...,b_10$ 这样的向量,只需调用宏 \vec an\vec b{10} 即可。在宏的定义中,我们将“#1” 与“#2”这种形式所表示的参数称为“形参”;而在调用宏时,例如 \vec an,我们将“a” 和“n”称为“实参”。

对于 \def\abc#1{#1},tex 程序将其嚼碎后可得:\def, \abc, #1, {, #1, }

要注意的是,在调用有参数的宏时,如果参数的字符超过 1 个,需要用编组符号(一般为“{”与“}”)将其囊括起来。例如对于 \def\test#1#2#3{#1, #2, #3} 定义的宏,使用 \test abc def ghk 来调用,那么 \test 的参数 #1, #2, #3 分别为 a, b, c。若想将“abc”,“def”和“ghk”作为 \test 的参数,则宏的调用方式为 \test{abc}{def}{ghk}

在定义宏的时候,可为其参数添加一些定界符。例如 \def\test[#1][#2]{#1 and #2},可通过形如 \test[param1][param2] 的方式调用。一般,为参数添加定界符,主要目的是让宏的调用代码看起来优雅一些。一个比较复杂的参数定界示例如下:

\def\ifempty#1\\{\ifemp#1\endA\endB}
\def\ifemp#1#2\endB{\ifx#1\endA}

所定义的宏 \ifempty 用于测试其参数是否为空,它只接受 1 个参数,且以 \\ 作为定界符,也就是说,\ifempty 的调用形式为 \ifempty xxx\\,位于 \ifempty\\ 之间的字符串 xxx\ifempty 的参数。下面通过代入具体的参数用以分析 \ifempty 的展开过程:

  1. 若参数为字符串“abc”,\ifempty 展开可得 \ifemp abc\endA\endB。此时“abc”中,“a” 为 \ifemp 的第一个参数,而“bc\endA” 为其第二个参数。对 \ifemp 进一步展开,可得 \ifx a\endA,此时“a”与“\endA” 定然是不相等的,因此本例中 \ifempty 的测试结果为 false。
  2. 若参数为空值,\ifempty 展开可得 \ifemp \endA\endB。此时\ifemp 的第一个参数为“\endA”,第二个参数为空。对 \ifemp 进一步展开,可得 \ifx \endA\endA,此时“\endA”与“\endA” 定然是相等的,因此本例中 \ifempty 的测试结果为 true。

宏的命名规则

宏的命名必然要遵循 TeX 控制序列的命名规范,即只能使用字母或单个非字母字符进行命名。但是,在了解宏的参数定界符的概念之后,假象上可认为宏的命名之后也可以是字母与非字母字 符的混合物,虽然这样通常很危险。

例如\def\test1{...},看上去是定义了一个名为“\test1”的宏,实际上定义的却是“\test”, 那个数字“1”不过是“\test”的参数定界符而已,尽管“\test”并无参数。如果继续有\def\test2{...}, 会把 tex 程序搞的很郁闷,它可能会提醒你:

    ! Use of \test doesn't match its definition.

转载时,希望不要链接文中图片,另外请保留本文原始出处:http://garfileo.is-programmer.com


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter