7
1
2016
0

autotools 教程:从 m4 说起

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')

从鸭子编程(我生造的词laugh)的角度来看,这确实是定义变量,虽然官方声称它是宏。

调用宏

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 是不可能有这种问题的。

Category: 技术 | Tags: autotools-tut | Read Count: 2423

登录 *


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

Powered by Chito | Hosted at is-Programmer | Theme: Aeros 2.0 by TheBuckmaker.com