其实
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开发纪事