给出以下语法:
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文档中找到任何可以说它对所有语法都不起作用的注释。
任何想法如何使这个工作?
答案 0 :(得分:2)
我认为你会得到该语法的转换/减少冲突报告,尽管该错误消息显示可能很大程度上取决于BNFC用于生成解析器的工具。据我所知,所有后端工具都有相同的方法来处理转移/减少冲突,这是(1)警告用户冲突,然后(2)解决冲突,转而支持转移。
有问题的制作是这样的:(我省去了类型注释以减少混乱)
Stm ::= "{" [Decl] [Stm] "}" ;
此处,[Decl]
和[Stm]
是宏,它们会自动为具有这些名称的非终端(或后端工具可接受的等价物)生成定义。具体来说,自动制作的作品是:
[Decl] ::= /* empty */
| Decl ';' [Decl]
[Stm] ::= /* empty */
| Stm [Stm]
(第一条规则中的;
是&#34;终结符&#34;声明的结果。我不知道为什么BNFC会生成正确的递归规则,但是这样做了我如何解读参考手册 - 经过一瞥 - 我确信他们有理由。出于这个问题的目的,这并不重要。
重要的是,Decl
和Stm
都可以Ident
开头。因此,我们假设我们正在解析{ id ...
,可能是{ id : ...
或{ id = ...
,但我们只读过{
和先行标记id
。所以有两种可能性:
id
是Decl
的开头。我们应该移动Ident
并转到包含Decl → Ident • ':' Type
id
是Stm
的开头。在这种情况下,我们需要在将[Decl] → •
转换为Ident
作品之前减少制作Stm
。
所以我们有一个转移/减少冲突,因为我们看不到第二个下一个标记(:
或=
)。并且,如上所述,在这种情况下,shift通常会获胜,因此LR(1)解析器将承诺期望Decl
。因此,{ a = b ; }
将失败。
LR(2)解析器生成器可以很好地处理这个语法,但是那些很难找到。 (现代野牛可以生产GLR解析器,它比LR(2)更强大,代价是额外的计算时间,但不是BNFC工具所需的版本。)
允许声明与语句混合。这是我的偏好。它很简单,许多程序员希望能够在首次使用时声明变量,而不是在封闭块的开头声明。
通过首先放置类型(如在C中)或通过添加var
之类的关键字(如在Javascript中),使第一个令牌可以识别声明:
修改语法以推迟前瞻。总是可以为任何LR(k)语言找到LR(1)语法(假设k是有限的),但它可能是乏味的。一个丑陋但有效的替代方法是继续词法扫描,直到找到:
或其他非空白字符,以便id :
被标记为IdentDefine
或其他类似物。 (这是bison
使用的解决方案。它意味着您无法在标识符和以下:
之间添加注释,但很少有(如果有的话)有充分的理由在这种背景下发表评论。