设为首页 加入收藏

TOP

Vczhl Library++3.0之Parser Combinator为常见的语法结构做优化(一)
2014-11-23 22:08:19 来源: 作者: 【 】 浏览:0
Tags:Vczhl Library 3.0 Parser Combinator 常见 语法 结构 优化

前曾经为Parser Combinator写过一篇教程。这次为了处理Vczh Library++新设计的ManagedX托管语言,我为Parser Combinator新增了三个组合子。

第一个是def,第二个是let。它们组合使用。def(pattern, defaultValue)的意思是,如果pattern成功了那么返回pattern的分析结构,否则返回defaultValue。let(pattern, value)的意思是,如果pattern成功了则返回value,否则失败。因此他们可以一起使用。举个例子,ManagedX跟C#一样具有5种type accessor:public, protected, protected internal, private, internal。其中四种accessor的文法类型是token,剩下的protected internal则是tuple。因此我们无法很方便地为它写一个记号到语法树的转换函数。而且对于缺省情况要返回private的这种行为,在EBNF+handler上直接表达出来也比较困难。当def和let还不存在的时候,我们需要这么写:

accessor = (PUBLIC[ToAccessor] | PROTECTED[ToAccessor] | PRIVATE[ToAccessor] | INTERNAL[ToAccessor] | (PROTECTED + INTERNAL)[ToProtectedInternal])[ToAccessorWithDefault];

这个时候我们需要创建三个函数,分别是ToAccessor、ToProtectedInternal和ToAccessorWithDefault。因为accessor本身不是一个重要的语法元素,所以我们不需要为accessor记录一些源代码的位置信息。表达式则需要位置信息,这可以在我们产生错误信息的时候知道错误发生在源代码中的位置。而accessor总是直接属于某一个重要的语法元素的,所以不需要保存。如果不需要保存位置信息的话,那么一个ToXXX的函数其实就是没有必要的。这个时候可以让def和let来简化操作:

accessor = def(let(PUBLIC, acc::Public) | let(PROTECTED, acc::Protected) | let(PRIVATE, acc::Private) | let(INTERNAL, acc::Internal) | let(PROTECTED+INTERNAL, acc::ProtectedInternal), acc::Private);

看起来好像差不多,但实际上我们已经减少了那三个不需要存在的函数。

============================无耻的分割线====================================

第三个是binop。做这个主要是因为那个通用的lrec(左递归组合子)在对付带大量括号的表达式的时候性能表现不好。这里稍微解释一下原因。假设我们的语言有>、+、*和()四种操作符,那文法一般都写成:

exp0 = NUMBER | ( exp3 )
exp1 = exp1 * exp0 | exp0
exp2 = exp2 + exp1 | exp1
exp3 = exp3 > exp2 | exp2

因此可以很容易的知道,当我们分析1*2*3的时候,走的是下面的路子:
exp3
= exp2
= exp1
= exp1 * exp0
= exp1 * exp1 * exp0
= 1 * 2 * 3

现在我们做一个简单的变换,把1*2*3变成((1*2)*3)。意义不变,但是分析的路径却完全改变了:
exp3
= exp2
= exp1
= exp0
= ( exp3 )
= ( exp2 )
= ( exp1 )
= ( exp1 * exp0 )
= ( exo0 * exp0 )
= ( ( exp3 ) * exp0 )
= ( ( exp2 ) * exp0 )
= ( ( exp1 ) * exp0 )
= ( ( exp1 * exp0 ) * exp0 )
= ( ( exp0 * exp0 ) * exp0 )
= ( ( 1 * 2 ) * 3 )

咋一看好像没什么区别,但是对于ManagedX这种有十几个优先级的操作符的语言来说,如果给一个复杂的表达式的每一个节点都加上括号,等于一下子增加了上千层文法的递归分析。由于Parser Combinator是递归向下分析器,因此路径有这么长,那么递归的层次也会有这么长。而且为了避免boost::Spirit那个天杀的超慢编译速度的问题,这里牺牲了一点点性能,将组合字的Parse函数做成了虚函数,所以编译速度提高了超多。一般来说一个需要编译一个半小时的boost::Spirit语法分析器用我的库只需要几秒钟就可以编译完了。不过现在却带来了问题。括号一多,性能下降的比较明显。但是我们显然不能因噎废食,因此我决定往Parser Combinator提供一个手写的带优先级的左右结合一二元操作符语法分析器。为了将这个手写的分析器插入框架并变得通用,我决定采用下面的结构。下面的代码是从ManagedX的语法分析器中截取出来的: 1 expression = binop(exp0)
2 .pre(ADD_SUB, ToPreUnary).pre(NOT_BITNOT, ToPreUnary).pre(INC_DEC, ToPreUnary).precedence()
3 .lbin(MUL_DIV_MOD, ToBinary).precedence()
4 .lbin(ADD_SUB, ToBinary).precedence()
5 .lbin(LT << LT, ToBinaryShift).lbin(GT >> GT, ToBinaryShift).precedence()
6 .lbin(LT, ToBinary).lbin(LE, ToBinary).lbin(GT, ToBinary).lbin(GE, ToBinary).precedence()
7 .post(AS + type, ToCasting).post(IS + type, ToIsType).precedence()
8 .lbin(EE, ToBinary).lbin(NE, ToBinary).precedence()
9 .lbin(BITAND, ToBinary).precedence()
10 .lbin(XOR, ToBinary).precedence()
11 .lbin(BITOR, ToBinary).precedence()
12 .lbin(AND, ToBinary).precedence()
13 .lbin(OR, ToBinary).precedence()
14 .lbin(QQ, ToNullChoice).precedence()
15 .lbin(Q

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇pthread_exit pthread_join 下一篇代码意识流――花朵数问题(七)

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: