IUP代码生成器以及re2c补丁一枚

最近虽然写SQLite图形前端的计划还没有什么进展,但是准备工作已经做起来了。在这过程中常常有意外收获。

在计划使用IUP作为图形界面库的时候,因为LED使用的机制过老,而Lua绑定又要增加新的依赖。(我的经验是,一旦用了IUP的Lua绑定,就会不知不觉地把和GUI不相关的逻辑代码写在Lua里。)所以我打算自己写一个新的GUI代码生成器。它的特点是:

  • 使用IUP的新接口,例如IupSetCallback, IupSetAttributeHandle
  • 不使用IupSetHandle给每个IUP对象绑定一个全局名称。
  • 生成纯C代码,不依赖Lua。

目前这个项目已经托管在GitHub,仍在开发中。最新的master分支已经基本能用。

写这个程序其实就是发明一个小型的专用语言,所以免不了和Lex & Yacc这样的元老打交道。最初我打算手写词法分析器和语法分析器,然而只有词法分析器被我手写出来。语法分析器我用Lemon生成。Lemon和Yacc的语法并不兼容,但好在前者是公有领域内的软件,编译起来也很方便。

Lemon生成的是LALR(1)语法分析器,对语法似乎有很大的限制,总之并不能像我想象地那样好地工作。GNU Bison可以生成更加强大的语法分析器而且和Yacc更加兼容,但我没有尝试。

后来我嫌词法分析器手写也太麻烦,索性用Flex改成自动生成。这就是master分支的最新状态。

很多人把Flex & Bison看成是Lex & Yacc的GNU版本。其实这是不对的。Flex不是GNU系列的软件,采用的也不是GPL协议(而是BSD协议)。所以所谓GNU Flex的称法完全是子虚乌有,但网上一查却还能查出一大堆。(但Bison的确是GNU软件。)

Flex对于喜欢瞎搞编译器的我来说应该是非常趁手的工具。我只需要给出词法定义,所有其他的辅助代码(输入输出、内存管理)全都由Flex搞定。最后程序的主体部分就只有:

do {
	token = yylex();
	Parse(parser, token, strdup(yytext));
} while (token);

词法分析每读一个记号,就送给语法分析器,如此循环直到文件结束。

值得注意的细节是,yytext不能直接传送给语法分析器。这是因为LALR语法分析器和词法分析器不是同步工作的,读入一个记号以后往往不会立即进行归约(reduce)。等到归约的时候,相应的动作代码才被执行。这时候yytext的内容恐怕已经发生了变化。所以在传送词法记号的时候,就应该传送一份拷贝。

下面说说re2c。re2c是一个相对小众的工具,文档也不多。它可以把DFA(有限状态自动机,词法分析的核心理论)直接编码成(含有很多goto的)C代码。Flex的做法是把状态和跳转规则保存在静态数组里,而其他部分的代码都是同样的模板。

re2c也可以用来写词法分析器。据称,直接编码的DFA构造的词法分析器和手写的词法分析器的效率是接近的,而比Flex那样表格驱动的快很多倍。不过,Flex名字本身意思就是Fast lex,所以究竟能快多少呢?我很好奇。和Flex不同,re2c不产生辅助代码。为了方便起见,我只好采取把输入文件一次性全部读入内存的办法。这样做其实很实用。(Lemon自身分析语法规则的时候,就采用这样的做法。)

我为GUI代码生成器的项目创建了一个新的分支,用re2c生成词法分析器。

最后说说re2c的补丁。re2c的词法规则是放在形如/*!re2c ... */的注释块中的。据说也可以使用%{ ... %}作为界限符,但是我试了一下,Segmentation fault。

就我个人的审美而言,我觉得%{ %}作为界限符更加美观一点。把有意义的代码写在注释里边是很讨厌的事情。如果要专门为re2c编写语法高亮规则,采用%{ %}这样的界限符也更容易实现一些。

崩溃的原因是re2c自己的词法分析器写的不对,里边有一个硬编码的字符串长度,只适用于前一种形式。补丁已提交到SourceForge。

更新: re2c的维护者者说他看出了问题,但并不能重现re2c崩溃的现象。真是奇怪啊,在我这里凡是含有%{ %}界限符的文件统统都会崩溃。

更新#2: re2c的维护者很快就发布了re2c的新版本修正了这个错误。不过他扩展了我提供的补丁,因为程序中还有其他几处类似的错误。


分享