随笔-341  评论-2670  文章-0  trackbacks-0
    其实Vczh Library++3.0提供的parser combinator并不能大量减少语法分析器的代码量,其实真正降低的是语法分析器的复杂程度。当你想比较快速的完成一个功能的时候,有两种代码量差不多的设计,一种实现起来比较难并且调试起来很惨,一种实现起来比较简单而且基本不用怎么调试,那相对来说肯定会选择后一种方法了。除非你纯粹是想获得锻炼。

    使用parser combinator开发语法分析器的时候,你可以直接往C++里面写EBNF语法,当然语法的具体形式因为受到C++语言本身的限制我做了一点点修改,譬如说A*和A+只好写成*A和+A,A B只好写成A + B、A>>B或者A<<B了。空明流产跟我抱怨说boost::spirit编译速度奇慢(据说要一个多小时,不知道是不是他机器太烂……)而且容易出现C1060 compiler is out of heap space的错误,相比之下我在用我自己开发的parser combinator的时候,我一个充满语法的cpp文件只需要一秒多一点(Thinkpad R61i, Vista Home Basic, 3G内存),而且不会出现C1060这种离谱的错误。至少从这个程度上来说,开发boost::spirit的人应该是有很大的C++洁癖症,才会把好好地一个parser combinator折腾成那个样子。

    我是用的文法模型是带类型修饰的文法,从文法的类型只能看出文法最终给你什么数据,而不是文法他本身是怎么写的。Vczh Library++2.0的parser combinator采用了后面一中的做法,据说boost::spirit也是这么做的,不过奇怪的是旧的parser combinator也没出现那两种错误,取而代之是VC++经常抱怨我一个表达式的类型签名超过了4000个字符(囧)。于是Vczh Library++3.0的parser combinator做了一点修改。

    假设你一条文法A的结果是node<input, type>,第二条文法B的结果是node<input, string>,那么A+B的结果就是node<input, pair<type, string>>。这是什么意义呢?我们看表达文法type name semicolon的意思,大概可以理解为他可以接受“int a;”的这种语句。首先由于C++的限制我们替换成type + name + semicolon,其次由于那个semicolon,也就是分号,其实仅仅是语法的要求而不是语法树的一个必须成分,因此改成type + name << semicolon。这样的话,这个文法依旧会要求输入的字符串分别是一个类型、一个名字和一个分号,但是返回的结果就自动把分号给忽略掉了。那么我们如何表示一个同时包含type和name的类型呢?因为文法不可能替你创建一个struct,所以就定义了一个泛型的pair来表达。于是type + name << semicolon的结果类型就是node<input, pair<type, string>>了。这里input代表输入的记号列表的类型。

    上面是新的parser combinator的做法,旧的parser combinator(据说也是boost::spirit的做法)的类型表示方法比较BT:当你有文法type : node<input, type>,string : node<input, string>和semicolon : node<input, token>的话,那么type + name << semicolon的类型会变成:
1 discard_right<input, sequence<input, node<input, type>, node<input, string>>, node<input, token>>
    写成这样大概就可以理解什么是“文法他本身是怎么写的”了吧。

    旧的parser combinator的好处是C++为每一个文法生成了一个类型,虽然代码会膨胀一点但是执行过程会很快,只不过缺点比较多。第一个当然是类型太多VC++编译器会崩溃(C1060 compiler is out of heap space),第二个是编译时间过长,第三个是当你的文法比较长的时候,类型签名可能会超过VC++给你的限制,然后就会出现奇怪的问题。所以我在Vczh Library++3.0的parser combinator就是用了一个新的做法,也就是仅保留文法的结果类型,所以也就不得不引入虚函数了。因为一个文法node<input, type>有非常多种组合可能,在结构上没办法表现出来,所以必须使用虚函数。

    在听了空明流产的抱怨之后,我去搜了一下使用boost::spirit的人的反应,好像都是遇到了那两个严重的问题。幸好我喜欢造车轮,不然的话也许也会深陷地狱了。不过boost::spirit还是提供了解决办法的,就是把你的长的文法拆开成短的。写过编译器的人都会知道,这么做的严重后果就是你的分析器变成一团乱麻,根本不知道自己在写什么,不仅不可能有我上一篇文章描写的优美结果,更不可能把NativeX的分析器写成下面这个样子了:
 1                     primitive        = TRUE[ToTrue] | FALSE[ToFalse]
 2                                     | ACHAR[ToAChar] | WCHAR[ToWChar]
 3                                     | ASTRING[ToAString] | WSTRING[ToWString]
 4                                     | FLOAT[ToFloat] | DOUBLE[ToDouble]
 5                                     | NULL_VALUE[ToNull]
 6                                     | INTEGER[ToInteger]
 7                                     ;
 8                     reference        = ID[ToReference];
 9 
10                     exp0            = primitive
11                                     | reference
12                                     | RESULT[ToResult]
13                                     | (CAST + (LT >> type << GT) + (OPEN_BRACE >> exp << CLOSE_BRACE))[ToCastExpression]
14                                     ;
15                     exp1            = lrec(exp0 +  *(
16                                                     (OPEN_ARRAY + exp0 << CLOSE_ARRAY)
17                                                     | (OPEN_BRACE + list(opt(exp + *(COMMA >> exp)))[UpgradeArguments] << CLOSE_BRACE)
18                                                     | ((DOT | POINTER) + reference)
19                                                     | (INCREASE | DECREASE)[UpgradePostfix]
20                                                     ), ToPostUnary);
21                     exp2            = exp1 | ((INCREASE | DECREASE | BIT_AND | MUL | SUB | BIT_NOT | NOT) + exp1)[ToPreUnary];
22                     exp3            = lrec(exp2 + *((MUL | DIV | MOD) + exp2), ToBinary);
23                     exp4            = lrec(exp3 + *((ADD | SUB) + exp3), ToBinary);
24                     exp5            = lrec(exp4 + *((SHL | SHR) + exp4), ToBinary);
25                     exp6            = lrec(exp5 + *((LT | GT | LE | GE) + exp5), ToBinary);
26                     exp7            = lrec(exp6 + *((EQ | NE) + exp6), ToBinary);
27                     exp8            = lrec(exp7 + *(BIT_AND + exp7), ToBinary);
28                     exp9            = lrec(exp8 + *(XOR + exp8), ToBinary);
29                     exp10            = lrec(exp9 + *(BIT_OR + exp9), ToBinary);
30                     exp11            = lrec(exp10 + *(AND + exp10), ToBinary);
31                     exp12            = lrec(exp11 + *(OR + exp11), ToBinary);
32                     exp                = lrec(exp12 + *((OP_ASSIGN | ASSIGN) + exp12), ToBinary);
33 
34                     primType        = (FUNCTION + type + (OPEN_BRACE >> list(opt(type + *(COMMA >> type))) << CLOSE_BRACE))[ToFunctionType]
35                                     | (PRIM_TYPE | ID)[ToNamedType]
36                                     ;
37                     type            = lrec(primType + *(MUL | (OPEN_ARRAY >> INTEGER << CLOSE_ARRAY)), ToDecoratedType);
38 
39                     statement        = SEMICOLON[ToEmptyStat]
40                                     | (exp + SEMICOLON)[ToExprStat]
41                                     | (VARIABLE + type + ID + opt(ASSIGN >> exp) << SEMICOLON)[ToVarStat]
42                                     | (IF + (OPEN_BRACE >> exp << CLOSE_BRACE) + statement + opt(ELSE >> statement))[ToIfStat]
43                                     | (BREAK << SEMICOLON)[ToBreakStat]
44                                     | (CONTINUE << SEMICOLON)[ToContinueStat]
45                                     | (EXIT << SEMICOLON)[ToReturnStat]
46                                     | (OPEN_STAT + list(*statement) << CLOSE_STAT)[ToCompositeStat]
47                                     | (DO + statement + (WHILE >> OPEN_BRACE >> exp << CLOSE_BRACE << SEMICOLON))[ToDoWhileStat]
48                                     | (LOOP + statement)[ToLoopStat]
49                                     | (WHILE + (OPEN_BRACE >> exp << CLOSE_BRACE) + statement + opt(WHEN >> OPEN_BRACE >> exp << CLOSE_BRACE << SEMICOLON))[ToWhileStat]
50                                     | (FOR + list(*statement) + (WHEN >> OPEN_BRACE >> exp << CLOSE_BRACE) + (WITH >> list(*statement)) + (DO >> statement))[ToForStat]
51                                     ;
52 
53                     declaration        = (VARIABLE + type + ID + opt(ASSIGN >> exp) << SEMICOLON)[ToVarDecl]
54                                     | (TYPE + ID + (ASSIGN >> type) << SEMICOLON)[ToTypedefDecl]
55                                     | (STRUCTURE + ID << SEMICOLON)[ToStructPreDecl]
56                                     | (STRUCTURE + ID + (OPEN_STAT >> *(type + ID << SEMICOLON) << CLOSE_STAT))[ToStructDecl]
57                                     | (FUNCTION + type + ID + (OPEN_BRACE >> plist(opt((type + ID) + *(COMMA >> (type + ID)))) << CLOSE_BRACE) + statement)[ToFuncDecl]
58                                     ;
59 
60                     unit            = ((UNIT >> ID << SEMICOLON) + list(opt(USES >> (ID + *(COMMA >> ID)) << SEMICOLON)) + list(*declaration))[ToUnit];

    啊,简直就跟EBNF没什么区别啊。

    当前的进度可以在Vczh Library++3.0的页面上看到。
posted on 2010-03-20 23:49 陈梓瀚(vczh) 阅读(4548) 评论(2)  编辑 收藏 引用 所属分类: VL++3.0开发纪事

评论:
# re: Vczh Library++3.0之我的语法分析器和boost::spirit 2010-03-20 23:58 | 空明流转

啊,简直就跟EBNF没什么区别啊。

啊,简直就跟YY没什么区别啊。  回复  更多评论
  
# re: Vczh Library++3.0之我的语法分析器和boost::spirit 2010-03-20 23:59 | 陈梓瀚(vczh)
@空明流转
相比起来,boost::spirit写出来的编译器简直就是石器时代的产品啊,啊哈哈  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理