手写自上而下的解析器

时间:2013-09-26 15:29:54

标签: parsing grammar context-free-grammar

我在编写自上而下的解析器时遇到了这个答案:Is there an alternative for flex/bison that is usable on 8-bit embedded systems?但我对几点感到困惑。

说我有这个语法:

Expr = Id | Id '(' Expr ')'
Stmt = Id '=' Expr | Expr

我不确定这是否完全必要,但我想我们可以留下语法因素:

Expr = Id ExprRest
ExprRest = ϵ | '(' Expr ')'
Stmt = Id '=' Expr ';' | Expr ';'

我究竟如何编写能够将foo(x);正确解析为Stmt的代码?如果我们编写Stmt解析代码,如:

func ParseStmt() {
  if ParseId() {
    return ParseTerminal('=') && ParseExpr() && ParseTerminal(';');
  } else {
    return ParseExpr() && ParseTerminal(';');
  }
}

我们会看到Id foo,假设我们处于第一种情况,然后失败,因为我们无法在=之后找到foo

这个语法不是LL(1)吗?

2 个答案:

答案 0 :(得分:1)

该语法不是LL(1),因为LL(1)语法需要能够仅在解析堆栈和前瞻令牌的情况下确定要扩展的生产。使用空的解析堆栈,在{1}}前瞻令牌的情况下,您无法分辨两个Stmt生产中的哪一个。你可以留下它,但它很烦人。

Id创建的可执行文件虽然占用了很多C行,但实际上非常紧凑。你的语法由bison编译,产生1559行C,但编译成4K的目标文件,其中(我相信)仅略多于1K对应于实际的代码和数据。 (当然,那是没有行动的。)

如果你真的想手工编写解析器的表达式语法,我建议你使用自下而上的'Shunting Yard'算法或自上而下的'Pratt解析器'(两者都可以随时搜索与谷歌),处理运算符优先级之类的东西比严格的LL语法。但你可能会发现,野牛生成的语法具有相当的大小和速度,以及更好的可读性和可维护性。 (或者你可能不会。口味不同。)

答案 1 :(得分:0)

您的问题是ID(xxx)可以出现在语句和表达式上下文中。

正如另一个答案所说,你必须考虑更多因素。这并不难。 写下你的语法:

Stmt = Id  ( '=' Expr ';' |
             RestOfExpression );

 RestOfExpression = '(' Expr ')' |
                     ϵ );
 Expr =  Id  RestOfExpression;

代码应该是显而易见的。