在 X11 中实现 GTK+ 3 的 OpenGL 支持
基于 ASCIIMathML.js 的 is-programmer 博客数学公式书写及显示

有趣的 M4

Garfileo posted @ 2011年4月21日 15:32 in 业余程序猿的足迹 with tags GNU M4 , 8457 阅读

最近在 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

  • 无匹配
pingf 说:
2011年4月23日 12:13

哈哈,这种功要集成进某ide(包括vim等)才好,
比如像某个模板的自动完成,在编写C代码时能显示的补全,而不需通过间接的文件
如果非要要通过间接的代码生成,用户体验就大打折扣了
就像很多喜欢Qt的人一直觉得那个moc是个遗憾

Ekd123 说:
2011年6月04日 19:48

嗯,一个好思路啊!我一直觉得GObject生命贼麻烦,于是想写一个向导,现在封装一下这个m4宏即可了~

Avatar_small
Mike Ma 说:
2011年6月11日 16:02

@Ekd123: 好吧有错字。。其实有了gob2和vala就够了。。。

alu 说:
2013年10月13日 20:56

不错,学习了,另外
第三行多了一个'`'字符
14行末少一个')'字符

creamidea 说:
2015年12月12日 02:32

看了一下m4的指南,突然发现这个货不就是lisp宏的翻版吗?(逃...


登录 *


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