IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    autotools 教程:从 m4 说起

    Ma Kai发表于 2016-07-01 00:29:44
    love 0

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



沪ICP备19023445号-2号
友情链接