Parsing Bison下标表达式出现意外错误

Parsing Bison下标表达式出现意外错误,parsing,syntax,grammar,bison,shift-reduce-conflict,Parsing,Syntax,Grammar,Bison,Shift Reduce Conflict,使用以下语法: program: /*empty*/ | stmt program; stmt: var_decl | assignment; var_decl: type ID '=' expr ';'; assignment: expr '=' expr ';'; type: ID | ID '[' NUMBER ']'; expr: ID | NUMBER | subscript_expr; subscript_expr: expr '[' expr ']'; 我希望以下内容是有效的:

使用以下语法:

program: /*empty*/ | stmt program;
stmt: var_decl | assignment;
var_decl: type ID '=' expr ';';
assignment: expr '=' expr ';';
type: ID | ID '[' NUMBER ']';
expr: ID | NUMBER | subscript_expr;
subscript_expr: expr '[' expr ']';
我希望以下内容是有效的:

array[5] = 0;
这只是一个
赋值
,左手边有一个
下标expr
。但是,生成的解析器为该语句提供了一个错误:

syntax error, unexpected '=', expecting ID
生成解析器还警告存在1个shift/reduce冲突。删除
subscript\u expr
会使它消失

为什么会发生这种情况,以及如何让它解析
array[5]=0作为带有
下标的
赋值


我使用的是Bison 2.3。

添加
%glr解析器
解决了这个问题。

以下两个语句在您的语言中都是有效的:

x [ 3 ] = 42;

x [ 3 ] y = 42;
第一个是数组变量
x
的元素赋值,第二个是数组变量
y
的声明和初始化,其元素类型为类型
x

但是从解析器的角度来看,
x
y
都只是id;它无法知道
x
在第一种情况下是变量,在第二种情况下是类型。它所能做的就是注意到这两条语句分别匹配productions
assignment
var_decl

不幸的是,它无法做到这一点,直到它看到后面的令牌]。如果该标记是一个ID,那么该语句必须是一个
var_decl
;否则,这是一个
作业
(当然,假设该语句有效)

但是为了将语句作为赋值进行解析,解析器必须能够生成

expr '=' expr
在本例中,这是
expr:subsciprt\u expr
的结果,而这又是
subscript\u expr:expr
[
expr
]

因此,第一条语句的缩减集如下所示:(注意:我没有编写移位;相反,我在每个缩减的末尾添加一个•来标记解析的进度。要进入下一步,只需移动•直到到达句柄的末尾。)

第二条语句必须按如下方式进行分析:

ID [ NUMBER ] • ID = NUMBER ;          type: ID '[' NUMBER ']'
type ID = NUMBER • ;                   expr: NUMBER
type ID = expr ; •                     var_decl: type ID '=' expr ';'
var_decl
这是一个shift/reduce冲突,因为关键决策必须在第一个ID之后立即做出。在第一个语句中,我们需要将标识符减少为
expr
。在第二条语句中,我们必须继续转换,直到我们准备好减少
类型

当然,如果我们能够从词汇上区分类型ID和变量名ID,这个问题就不会存在,但这可能是不可能的(或者,如果可能的话,这可能是不可取的,因为它需要从解析器到词法分析器的反馈)

如前所述,可以使用固定的前向进行移位/减少预测,因为ID之后的第四个令牌将确定可能性。这使得语法成为LALR(4),但这并没有多大帮助,因为bison只实现LALR(1)解析器。在任何情况下,如果数组大小允许常量表达式,或者数组可以有多个维度,则不太简化的语法很可能是固定的

即使如此,语法也不是模棱两可的,因此可以使用GLR解析器对其进行解析。Bison实现了GLR解析器;只需插入

%glr-parser
进入开场白。(仍将生成shift/reduce警告,但解析器将正确识别这两种语句。)

值得注意的是,C并不存在这种特殊的解析问题,因为它将数组大小放在声明的变量名称之后。我不认为这样做是为了避免解析问题(虽然谁知道呢?),而是因为人们认为,以使用变量的方式编写声明更为自然。因此,我们编写
inta[3]
char*p
,因为在程序中我们将使用
a[i]
*p
解除引用


可以为这种语法编写LALR(1)语法,但这有点烦人。关键是延迟语法的缩减,直到我们确定哪一个产品将开始。这意味着我们需要包括生产
expr:ID'['NUMBER']'
。这将导致大量的shift/reduce警告(因为它使语法变得模棱两可),但由于bison总是喜欢shift,因此它应该生成一个正确的解析器。

解决您自己的问题并发布答案总是一件好事。
%glr-parser