m4 是什么?
Autoconf 的基础是一个叫做 m4 的东西。那么 m4 是什么呢?说白了,它类似于 C 语言的预处理器,用来处理宏的。
要看看这玩意是怎么工作的?
kai@debian:~$ m4 define(`OS', `operating system') ⇒ OS ⇒operating system
注意,我输入的是 `' (第一个字符不是半角单引号,而是 ~ 键不按 Shift)而不是 ''。
很简单吧?它将 OS 直接展开成了 operating system,而 C 语言 #define 就差不多:
#define OS operating system OS /* 展开成 operating system */
m4 对我们有什么用?
要玩转 autoconf,我们不得不了解一下 m4。虽然 configure.ac 看上去很简单,但是里面一些高端玩法都是和 m4 有关的,要进行深度定制也离不开 m4。但是我们有必要学得很深入么?当然没有必要啦,学学跟我们可能有关的就行了。
m4 玩法举例
引用
前引用符号 ` 和后引用符号 ' 扩起来的,就是一个单纯的引用(不知道怎么解释比较好,文档里称 Quotation,注意不是 Reference)。
m4 在遇到 quotation 时不会进行宏展开,这类似于 Lisp,Lisp 遇到 quote 时不会对变量求值。如果一个关键字可以被宏展开,那么 m4 会反复将其展开到不能再展开为止。
注意,因为 m4 有前缀和后缀,所以一般来说,你在其他编程语言中有关 " " 的经验是失效的。比如 ``abc'' 是嵌套引用,应用上述展开规则,首先展开为 `abc',再展开为`abc'。
定义变量
define(`variable', `字符串') define(`varInt', 100) define(`DO', `define(`varInt', incr(varInt))Hello $1 varInt')
从鸭子编程(我生造的词)的角度来看,这确实是定义变量,虽然官方声称它是宏。
调用宏
define(`COUNT', 0) define(`DO', `define(`COUNT', incr(COUNT))Hello $1 varInt')
你看得懂 DO 吗?展开后很易懂,每次调用 DO 会将 COUNT 重新定义为 COUNT + 1,并输出一段字符串 Hello $1 varInt(这里是varInt的值),$1 代表 DO 的第一个参数,如:
DO(hi) ⇒Hello hi 1 DO(mike) ⇒Hello mike 2
有多个参数,用逗号分隔,如果一个宏没有参数,那么依然要加上括号:
macro(arg1, agr2, ..., argn) macro()
根据你自己的尝试和上面的一些例子,你能否猜出下面的代码是什么意思呢?
define(`foo', `one') define(foo, `two')
嗯,其实第二句就相当于 define(`one', `two'),如果你会 Tcl,应该很好理解:
set foo one set $foo two
注释
dnl 这是注释 dnl 这还是注释 dnl 一共三行注释 dnl 上面是骗你的
一些常见的控制结构
测试是否定义宏:
ifdef(`foo', ``foo' is defined', ``foo' is not defined') ⇒`foo' is not defined define(`foo', `bar') ⇒ ifdef(`foo', ``foo' is defined', ``foo' is not defined') ⇒`foo' is defined
通用的 if 结构:
ifelse(string-1, string-2, equal-1, ..., [not-equal]) [ ] 表示可选参数
什么意思呢?看这个例子:
ifelse(foo, `bar', `$`foo' == bar', `$`foo' != bar') ⇒$foo != bar define(`foo', `bar') ⇒ ifelse(foo, `bar', `$`foo' == bar', `$`foo' != bar') ⇒$foo == bar
也就是 string-1 和 string-2 相等时,该宏展开成 equal-1,以此类推,如果所有判断都是假,就展开成 not-equal。
循环?m4 是没有的。记得 m4 的求值规则吗?我们完全可以使用递归!这里我们利用 shift 函数(把一个列表的起始项移走)。
shift(1,2,3) ⇒2,3 shift(2,3) ⇒3 shift(3) ⇒ shift() ⇒ define(`reverse', `ifelse(`$#', `0', , `$#', `1', ``$1'', `reverse(shift($@)),`$1'')') ⇒ reverse(1,2,3) ⇒3,2,1
这个例子是从文档里面抄出来的,很恶心吧?那就最好别用。
包含一个文件
include(`xxx.m4')
宏的参数
- $1 第一个参数
- $2 第二个参数
- $3 第三个参数
- ……
- $n 第n个参数
- $@ 所有参数
注意,GNU m4 实现支持任意个参数,但这与 POSIX 不兼容,因此不建议那样使用。
我们再看一个有关引用的小问题
define(`active', `ACT, IVE') ⇒ define(`show', `$1 $1') ⇒ show(active) ⇒ACT ACT show(`active') ⇒ACT, IVE ACT, IVE show(``active'') ⇒active active
你能解释上述结果吗?
写在 autoconf 边上
m4 提供了一个叫 changequote 的功能,可以修改 quotation 的前缀后缀,autoconf 就用了这个功能,即前缀后缀分别修改成了 [ ],显得更加清晰简洁。
我们在本章中介绍的 m4 其实已经涵盖了 m4 的核心功能,这也是我们接下来使用 autoconf 的一个坚实基础。如果你对 m4 还有兴趣,可以参考 info m4 阅读官方文档。
其他碎碎念
m4 其实完全可以当作一个编程语言来用,但是其奇妙的用法实在让人难以接受,所以官方将其定位为 macro preprocessor 其实是非常准确的。从编程语言角度来看,相比 Tcl,m4 是一个更为贯彻「一切皆为字符串」信条的,比如上面那个引用小问题……Tcl 是不可能有这种问题的。