为什么用BNFC解析这个程序会失败?

时间:2015-05-17 01:50:42

标签: parsing bnf bnfc

给出以下语法:

comment "/*" "*/" ;

TInt.  Type1 ::= "int" ;
TBool. Type1 ::= "bool" ;
coercions Type 1 ;


BTrue.   BExp   ::= "true" ;
BFalse.  BExp   ::= "false" ;

EOr.     Exp    ::= Exp  "||" Exp1 ;
EAnd.    Exp1   ::= Exp1 "&&" Exp2 ;
EEq.     Exp2   ::= Exp2 "==" Exp3 ;
ENeq.    Exp2   ::= Exp2 "!=" Exp3 ;
ELt.     Exp3   ::= Exp3 "<" Exp4 ;
EGt.     Exp3   ::= Exp3 ">" Exp4 ;
ELte.    Exp3   ::= Exp3 "<=" Exp4 ;
EGte.    Exp3   ::= Exp3 ">=" Exp4 ;
EAdd.    Exp4   ::= Exp4 "+" Exp5 ;
ESub.    Exp4   ::= Exp4 "-" Exp5 ;
EMul.    Exp5   ::= Exp5 "*" Exp6 ;
EDiv.    Exp5   ::= Exp5 "/" Exp6 ;
EMod.    Exp5   ::= Exp5 "%" Exp6 ;
ENot.    Exp6   ::= "!" Exp ;
EVar.    Exp8   ::= Ident ;
EInt.    Exp8   ::= Integer ;
EBool.   Exp8   ::= BExp ;
EIver.   Exp8   ::= "[" Exp "]" ;
coercions Exp 8 ;

Decl. Decl ::= Ident ":" Type ;
terminator Decl ";" ;


LIdent.  Lvalue ::= Ident ;

SBlock.  Stm ::= "{" [Decl] [Stm] "}" ;
SExp.    Stm ::= Exp ";" ;
SWhile.  Stm ::= "while" "(" Exp ")" Stm ;
SReturn. Stm ::= "return" Exp ";" ;
SAssign. Stm ::= Lvalue "=" Exp ";" ;
SPrint.  Stm ::= "print" Exp ";" ;
SIf.     Stm ::= "if" "(" Exp ")" "then" Stm "endif" ;
SIfElse. Stm ::= "if" "(" Exp ")" "then" Stm "else" Stm "endif" ;

terminator Stm "" ;

entrypoints Stm;
使用bnfc创建的

解析器无法解析

{ c = a; }

虽然它解析了

c = a;

{ print a; c = a; }

我认为这可能是一个问题,解析器看到了Ident并且不知道它的声明或声明,LR东西等(仍然是lookeahed的一个标记应该足够吗?)。但是,我无法在BNFC文档中找到任何可以说它对所有语法都不起作用的注释。

任何想法如何使这个工作?

1 个答案:

答案 0 :(得分:2)

我认为你会得到该语法的转换/减少冲突报告,尽管该错误消息显示可能很大程度上取决于BNFC用于生成解析器的工具。据我所知,所有后端工具都有相同的方法来处理转移/减少冲突,这是(1)警告用户冲突,然后(2)解决冲突,转而支持转移。

有问题的制作是这样的:(我省去了类型注释以减少混乱)

Stm ::= "{" [Decl] [Stm] "}" ;

此处,[Decl][Stm]是宏,它们会自动为具有这些名称的非终端(或后端工具可接受的等价物)生成定义。具体来说,自动制作的作品是:

[Decl] ::= /* empty */
       |   Decl ';' [Decl]

[Stm]  ::= /* empty */
       |   Stm [Stm]

(第一条规则中的;是&#34;终结符&#34;声明的结果。我不知道为什么BNFC会生成正确的递归规则,但是这样做了我如何解读参考手册 - 经过一瞥 - 我确信他们有理由。出于这个问题的目的,这并不重要。

重要的是,DeclStm都可以Ident开头。因此,我们假设我们正在解析{ id ...,可能是{ id : ...{ id = ...,但我们只读过{和先行标记id。所以有两种可能性:

  1. idDecl的开头。我们应该移动Ident并转到包含Decl → Ident • ':' Type

  2. 的州
  3. idStm的开头。在这种情况下,我们需要在将[Decl] → •转换为Ident作品之前减少制作Stm

  4. 所以我们有一个转移/减少冲突,因为我们看不到第二个下一个标记(:=)。并且,如上所述,在这种情况下,shift通常会获胜,因此LR(1)解析器将承诺期望Decl。因此,{ a = b ; }将失败。

    LR(2)解析器生成器可以很好地处理这个语法,但是那些很难找到。 (现代野牛可以生产GLR解析器,它比LR(2)更强大,代价是额外的计算时间,但不是BNFC工具所需的版本。)

    可能的解决方案

    1. 允许声明与语句混合。这是我的偏好。它很简单,许多程序员希望能够在首次使用时声明变量,而不是在封闭块的开头声明。

    2. 通过首先放置类型(如在C中)或通过添加var之类的关键字(如在Javascript中),使第一个令牌可以识别声明:

    3. 修改语法以推迟前瞻。总是可以为任何LR(k)语言找到LR(1)语法(假设k是有限的),但它可能是乏味的。一个丑陋但有效的替代方法是继续词法扫描,直到找到:或其他非空白字符,以便id :被标记为IdentDefine或其他类似物。 (这是bison使用的解决方案。它意味着您无法在标识符和以下:之间添加注释,但很少有(如果有的话)有充分的理由在这种背景下发表评论。