TeX 的宏 / 第二集
TeX 宏的参数可被构造为可选的。例如对于宏 \xx,假设它接受一个参数,所谓可选参数,是指 \xx[#1]{...} 与 \xx{...} 这两种宏调用方式都成立。当然,要实现这一目的,需要一些技巧,其中最关键的是掌握 \futurelet 的用法。
首先,采用下面两个宏的形式分别定义具有可选参数的宏与不具有可选参数的宏:
\def\xxWithOpt[#1]#2{...} \def\xxNoOpt#1{...}
然后,定义一个不带任何参数的宏:
\def\xx{\futurelet\xxLookedAtToken\xxDecide}
在此,\futurelet 的作用:让 \xxLookedAtToken 存储一个目前还不存在的 token,然后执行 \xxDecide 宏。\xxDecide 的具体实现如下:
\def\xxDecide{% \ifx\xxLookedAtToken [% \let\next=\xxWithOpt \else \let\next=\xxNoOpt \fi \next }
至此,带有两个参数的宏 \xx 定义完毕,其第一个参数为可选参数,第二个参数为必须参数。
现在,如果以形式 \xx[a]{b} 调用宏 \xx,tex 会按以下步骤进行宏的展开:
- \xx[a]{b} 会被展开为 \futurelet\xxLookedAtToken\xxDecide[a]{b}。在此,字符“[”会被作为 \futurelet 的第三个参数,而 \futurelet 的前两个参数分别为 \xxLookedAtToken 与 \xxDecide。\futurelet 可复制字符“[”(实际上是一个 token),将其存储于 \xxLookedAtToken 的“名下”,然后调用 \xxDecide。
- 展开 \xxDecide,判断 \xxLookedAtToken 所表示的 token 是否为字符 “[”,若是,则调用 \xxWithOpt;若否,则调用 \xxNoOpt。对于 \xx[a]{b} 而言,显然最终是调用 \xxWithOpt。
若以形式 \xx{b} 调用 \xx,则展开过程类似上述过程,不过最终调用的宏是 \xxNoOpt。
有时候,对于 \xx[a]{a} 这样的宏调用,我们也许希望存在这样一种简写方式:\xx{a},让 tex 将其展开为 \xx[a]{a}。也就是说,我们希望定义类似下面这样的宏:
\def\xx{\DblArg{\@xx}}
对于 \xx{a},就将其展开为 \@xx[a]{a},而对于 \xx[a]{b},则将其展开为 \@xx[a]{b}。要实现这一目的,可将 \DblArg 定义为:
\catcode`\@=11 \def\DblArg#1{% \def\@DblArgTemp{#1}% \futurelet\@DblArgTok\@DblArg } \def\@DblArg{% \ifx \@DblArgTok [% \let\@DblArgTempA=\@DblArgTemp \else \let\@DblArgTempA=\@DblArgB \fi \@DblArgTempA \def\@DblArgB#1{\@DblArgTemp[#1]{#1}} \catcode`\@=12
如果理解了第一个示例,那么上例是不难理解的。无非就是判断 \xx 之后的 token 是否为字符“[”,若是,则 \xx 最终被展开为 \@xx,其后紧跟可选参数与一个编组参数;若否,则 \xx 会被展开为 \@DblArgB,其后紧跟一个编组参数,即 \@DblArgB{#1},这个宏会被进一步展开为 \@xx[#1]{#1}。
上例中,要注意这样一个小技巧:为了丰富宏的命名,可临时将“@”的域代码设为字母类别(11),不用时,再将其设为原有类别(12)。
转载时,希望不要链接文中图片,另外请保留本文原始出处:http://garfileo.is-programmer.com