Bison下标表达式意外错误

时间:2017-01-14 14:50:05

标签: 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 ']';

我希望以下内容有效:

array[5] = 0;

这只是assignment,左侧有subscript_expr。但是,生成的解析器会为该语句提供错误:

syntax error, unexpected '=', expecting ID

生成解析器还会警告有1次移位/减少冲突。删除subscript_expr会使其消失。

为什么会发生这种情况,如何才能将array[5] = 0;解析为assignment subscript_expr

我正在使用Bison 2.3。

2 个答案:

答案 0 :(得分:2)

以下两种陈述均以您的语言有效:

x [ 3 ] = 42;

x [ 3 ] y = 42;

第一个是数组 变量 x的元素赋值,第二个是数组变量{{1}的声明和初始化其元素为 类型 y

但是从解析器的角度来看,xx都只是 ID s;它无法知道y在第一种情况下是变量而在第二种情况下是一种类型。它所能做的就是注意这两个语句分别与作品xassignment相匹配。

不幸的是,在] 之后看到令牌之前,它无法做到这一点。如果该标记是 ID ,则该语句必须是var_decl;否则,它是var_decl(假设该陈述当然有效)。

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

assignment

在这种情况下是expr '=' expr 的结果,而后者又是expr: subsciprt_expr [subscript_expr: expr]`。

所以第一个语句的减少集如下:(注意:我没有写出变换;相反,我通过在每个减少的末尾加上一个来标记解析的进度。转到下一步,只需移动•直到到达手柄的末端。)

expr

必须按如下方式解析第二个语句:

ID • [ NUMBER ] = NUMBER ;             expr: ID
expr [ NUMBER • ] = NUMBER ;           expr: NUMBER
expr [ expr ] • = NUMBER ;             subscript_expr: expr '[' expr ']'
subscript_expr • = NUMBER ;            expr: subscript_expr
expr = NUMBER • ;                      expr: NUMBER
expr = expr ; •                        assignment: expr '=' expr ';'
assignment

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

当然,如果我们能词法区分 ID s变量名 ID s,那么这个问题就不存在了,但是不可能(或者,如果可能的话,它可能不合适,因为它需要从解析器到词法分析器的反馈)。

如上所述,可以使用固定的先行进行移位/减少预测,因为 ID 之后的第四个标记将确定可能性。这使得语法LALR(4),但这没有多大帮助,因为bison只实现了LALR(1)解析器。在任何情况下,可能不太简单的语法也不会被修复 - 例如,如果数组大小允许使用常量表达式,或者数组可以有多个维度。

即便如此,语法也不含糊,因此可以使用GLR解析器来解析它。 Bison确实实现了GLR解析器;只需要插入

type
进入序幕。 (仍将生成shift / reduce警告,但解析器将正确识别这两种语句。)

值得注意的是,C没有准确地解释这个特定的解析问题,因为它将数组大小放在声明的变量的名称之后。我不相信这是为了避免解析问题(虽然谁知道?),而是因为人们认为使用变量的方式编写声明更自然。因此,我们写%glr-parser int a[3],因为在程序中我们将使用char *pa[i]取消引用。

可以为这种语法编写一个LALR(1)语法,但它有点烦人。关键是延迟语法*p的减少,直到我们确定哪个生产将是它的开始。这意味着我们需要包含生产ID [ NUMBER ]。这将导致更多的移位/减少警告(因为它使语法模糊不清),但由于野牛总是喜欢移位,它应该产生一个正确的解析器。

答案 1 :(得分:1)

添加%glr-parser解决了这个问题。