有趣的 M4
最近在 GNU Autotools 中潜水,发现只要摒弃对 shell 脚本和 M4 宏语言的恐慌,autoconf、automake、libtool 之类的工具用起来也挺有趣。
Autotools 起源于一个名为 configure 的 shell 脚本。可能一开始 GNU 的程序猿们感觉为软件包手工写一份 configure 脚本很有趣。后来,当他们发现要为每个 GNU 项目都手写一份 configure 脚本就很没趣了,因为各个软件包的 configure 脚本大同小异。于是,一些懒惰的 GNU 程序猿使用了 M4 语言将 configure 脚本中的 shell 代码封装为 M4 宏。例如,一份简单的 M4 宏文件(configure.ac 文件):
AC_INIT(hello, 0.1)
autoconf 工具可将它展开为一份 1000 多行的 shell 脚本(即 configure 脚本)。
若心存怀疑,可将上述代码保存为 configure.ac 文件,然后在这个文件所在的目录中执行 `autoconf` 命令,即可得到 configure 脚本,打开它数数行数。当 autoconf 获得成功后,GNU 的程序猿们情绪高涨,于是像化学反应链一样搞出来 aclocal、autoheader、automake 之类的工具。
宏语言似乎在计算机文明的石器时代繁荣过一段时间,不过一直也是非主流。如果熟悉 TeX 的话,应当也能领会出 TeX 也是一种宏语言,MetaPost 也是。宏语言可能比较适合艺术家使用,用来写出诗歌一般优美的代码,例如 Knuth 为 Dancing Link 算法写的一份 MetaPost 代码,这份代码所绘制的图形可从他的论文看到。
扯远了,这篇文章是要讲点与 M4 有关的东西,现在回归正题。
确切的说,本文所讲的 M4 是 GNU M4。顾名思义,这自然是 GNU 组织对 Unix 软件的又一个复制品。不过 GNU M4 要比 Unix 的 M4 强大许多。这里不准备对它们进行优劣对比,毕竟知道 Unix M4 的观众似乎也不是很多。
本文也不打算变成一篇 GNU M4 指南,因为已经有了一份很好的指南,见 http://mbreen.com/m4.html。如果想知道更多的 M4 知识,可以使用 `info m4` 命令查询 GNU M4 手册。
下面来看这样一份 m4 文件(是我学习 GNU M4 大概 20 分钟后写出来的):
define(`upcase', `translit(`$*', `a-z', `A-Z')')dnl define(`lowcase', `translit(`$*', `A-Z', `a-z')')dnl define(`define_an_object', ``'dnl `#ifndef' upcase(`$1')_`'upcase(`$2')_H `#define' upcase(`$1')_`'upcase(`$2')_H `#define' upcase(`$1')_TYPE_`'upcase(`$2')`'dnl (lowcase(`$1')_`'lowcase(`$2')_get_type ()) `#define' upcase(`$1')_`'upcase(`$2')(object)`'dnl (G_TYPE_CHECK_INSTANCE_CAST((object), `'dnl upcase(`$1')_TYPE_`'upcase(`$2'), `$1'`$2')) `#define' upcase(`$1')_IS_`'upcase(`$2')(object)`'dnl (G_TYPE_CHECK_INSTANCE_TYPE((object), `'dnl upcase(`$1')_TYPE_`'upcase(`$2')) `#define' upcase(`$1')_`'upcase(`$2')_CLASS(klass)`'dnl (G_TYPE_CHECK_CLASS_CAST((klass), `'dnl upcase(`$1')_TYPE_`'upcase(`$2'), `$1'`$2'Class)) `#define' upcase(`$1')_IS_`'upcase(`$2')_CLASS(klass)`'dnl (G_TYPE_CHECK_CLASS_TYPE((klass), `'dnl upcase(`$1')_TYPE_`'upcase(`$2'))) `#define' upcase(`$1')_`'upcase(`$2')_GET_CLASS(object)`'dnl (G_TYPE_INSTANCE_GET_CLASS((object), `'dnl upcase(`$1')_TYPE_`'upcase(`$2'), `$1'`$2'Class)) typedef struct _`$1'`$2' `$1'`$2'; typedef struct _`$1'`$2'Class `$1'`$2'Class; struct _`$1'`$2'{ GObject parent; }; struct _`$1'`$2'Class{ GObjectClass parent_class; }; GType lowcase(`$1')_`'lowcase(`$2')_get_type (void); `#endif' /* upcase(`$1')_`'upcase(`$2')_H */')dnl
看不懂这份 M4 文件并不要紧,继续向下看。
假设上述 M4 文件名为 gobject.m4。然后在 gobject.m4 所在目录再写一份 M4 文件 test.m4,内容如下:
include(gobject.m4)dnl define_an_object(Foo, Object)
然后使用下面的命令对 test.m4 进行展开,并将展开结果重定向输出至 foo-object.h 文件中。
m4 test.m4 > foo-object.h
打开 foo-object.h 文件,可以看到类似以下内容:
#ifndef FOO_OBJECT_H #define FOO_OBJECT_H #define FOO_TYPE_OBJECT (foo_object_get_type ()) #define FOO_OBJECT(object) \ (G_TYPE_CHECK_INSTANCE_CAST((object), FOO_TYPE_OBJECT, FooObject)) #define FOO_IS_OBJECT(object) \ (G_TYPE_CHECK_INSTANCE_TYPE((object), FOO_TYPE_OBJECT) #define FOO_OBJECT_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST((klass), FOO_TYPE_OBJECT, FooObjectClass)) #define FOO_IS_OBJECT_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE((klass), FOO_TYPE_OBJECT)) #define FOO_OBJECT_GET_CLASS(object) \ (G_TYPE_INSTANCE_GET_CLASS((object), FOO_TYPE_OBJECT, FooObjectClass)) typedef struct _FooObject FooObject; typedef struct _FooObjectClass FooObjectClass; struct _FooObject{ GObject parent; }; struct _FooObjectClass{ GObjectClass parent_class; }; GType foo_object_get_type (void); #endif /* FOO_OBJECT_H */
如果你熟悉 GObject 的话,很容易看出 foo-object.h 文件定义了一个 GObject 的子类。
现在总结一下上面所做的工作。
首先,我们在 gobject.m4 文件中所编写的那些像杂草一样的代码,主要是为了定义一个名为 define_an_object 的宏。
然后,我们在 test.m4 文件中使用了这个宏。
最后,我么使用 m4 工具将 test.m4 文件展开为一份 C 程序的头文件,它是 GObject 子类化模板。这就是 M4 的有趣之处。
转载时,希望不要链接文中图片,另外请保留本文原始出处:http://garfileo.is-programmer.com
2011年4月23日 12:13
哈哈,这种功要集成进某ide(包括vim等)才好,
比如像某个模板的自动完成,在编写C代码时能显示的补全,而不需通过间接的文件
如果非要要通过间接的代码生成,用户体验就大打折扣了
就像很多喜欢Qt的人一直觉得那个moc是个遗憾
2011年6月04日 19:48
嗯,一个好思路啊!我一直觉得GObject生命贼麻烦,于是想写一个向导,现在封装一下这个m4宏即可了~
2011年6月11日 16:02
@Ekd123: 好吧有错字。。其实有了gob2和vala就够了。。。
2013年10月13日 20:56
不错,学习了,另外
第三行多了一个'`'字符
14行末少一个')'字符
2015年12月12日 02:32
看了一下m4的指南,突然发现这个货不就是lisp宏的翻版吗?(逃...