我根据Tiger Book(附录A,Tiger手册)编写了一个yacc文件。
但仍有一些转变/减少冲突。我不知道如何解决这些冲突。
% yacc --version
bison (GNU Bison) 3.0.2
您可以使用此cmd重现问题:
% yacc -dvt tiger.y
tiger.y: warning: 37 shift/reduce conflicts [-Wconflicts-sr]
% cat tiger.y
:
%{
#include <stdio.h>
//#include "util.h"
//#include "errormsg.h"
int yylex(void); /* function prototype */
void yyerror(char *s)
{
EM_error(EM_tokPos, "%s", s);
}
%}
%union {
int pos;
int ival;
string sval;
}
%token <sval> ID STRING
%token <ival> INT
%token
COMMA COLON SEMICOLON LPAREN RPAREN LBRACK RBRACK
LBRACE RBRACE DOT
PLUS MINUS TIMES DIVIDE EQ NEQ LT LE GT GE
AND OR ASSIGN
ARRAY IF THEN ELSE WHILE FOR TO DO LET IN END OF
BREAK NIL
FUNCTION VAR TYPE
%right ASSIGN
%left OR
%left AND
%nonassoc EQ NEQ LT LE GT GE
%left PLUS MINUS
%left TIMES DIVIDE
%left UNARYMINUS
%precedence THEN
%precedence ELSE
%start program
%%
program: exp { }
;
exp:lvalue { }
|NIL { }
|LPAREN explist RPAREN { }
|LPAREN RPAREN {}
|INT {}
|STRING {}
|MINUS exp %prec UNARYMINUS {}
|ID LPAREN RPAREN {}
|ID LPAREN arglist RPAREN {}
|exp PLUS exp {}
|exp MINUS exp {}
|exp TIMES exp {}
|exp DIVIDE exp {}
|exp EQ exp {}
|exp NEQ exp {}
|exp LT exp {}
|exp LE exp {}
|exp GT exp {}
|exp GE exp {}
|exp AND exp {}
|exp OR exp {}
|ID LBRACE RBRACE {}
|ID LBRACE idlist RBRACE {}
|ID LBRACK exp RBRACK OF exp {}
|lvalue ASSIGN exp {}
|IF exp THEN exp ELSE exp {}
|IF exp THEN exp {}
|WHILE exp DO exp {}
|FOR ID ASSIGN exp TO exp DO exp {}
|BREAK {}
|LET decs IN END {}
|LET decs IN explist END {}
;
lvalue: ID {}
| lvalue DOT ID {}
| lvalue LBRACK exp RBRACK {}
;
explist: exp {}
| explist SEMICOLON exp {}
;
arglist:exp {}
|exp COMMA arglist {}
;
idlist:ID EQ exp {}
|ID EQ exp COMMA idlist {}
;
decs:dec {}
|decs dec {}
;
dec:tydec {}
|vardec {}
|fundec {}
;
tydec:TYPE ID EQ ty {}
;
ty:ID {}
|LBRACK tyfields RBRACK {}
|ARRAY OF ID {}
;
tyfields:/* NULL */
|notnulltyfields {}
;
notnulltyfields:ID COLON ID {}
|ID COLON ID COMMA notnulltyfields {}
;
vardec:VAR ID ASSIGN exp {}
|VAR ID COLON ID ASSIGN exp {}
;
fundec:FUNCTION ID LPAREN tyfields RPAREN EQ exp {}
|FUNCTION ID LPAREN tyfields RPAREN COLON ID EQ exp {}
;
答案 0 :(得分:6)
通过查看使用tiger.output
标志生成的-v
文件,可以轻松发现shift-reduce冲突。
这是一个例子(我编辑了重复):
State 88
11 exp: exp . PLUS exp
12 | exp . MINUS exp
# ...
29 | WHILE exp DO exp .
PLUS shift, and go to state 34
MINUS shift, and go to state 35
# ...
PLUS [reduce using rule 29 (exp)]
MINUS [reduce using rule 29 (exp)]
# ...
$default reduce using rule 29 (exp)
我们可以看到状态88出现在WHILE
表达式可以减少时(从状态描述中.
的位置可以看出这一点:
29 | WHILE exp DO exp .
如果此时的前瞻标记是二元运算符,则解析器不知道是否移动运算符,使exp
表达式中的尾随WHILE
更长,或者立即减少WHILE
。显然(对我们而言,不是bison
),解决方案就是转变。 bison
不知道,因为生产exp: WHILE exp DO exp
没有优先权。该生产的优先级将是其最后一个终端的优先级,即DO
,因此简单的解决方案是为DO
定义优先级。不出所料,它应该与ELSE
的优先级相同,正如IF exp THEN exp ELSE exp .
类似的问题发生在状态112和129中。
状态1中的转换/减少冲突也从output
文件中清除:
State 1
9 exp: ID . LPAREN RPAREN
10 | ID . LPAREN arglist RPAREN
23 | ID . LBRACE RBRACE
24 | ID . LBRACE idlist RBRACE
25 | ID . LBRACK exp RBRACK OF exp
34 lvalue: ID .
LPAREN shift, and go to state 15
LBRACK shift, and go to state 16
LBRACE shift, and go to state 17
LBRACK [reduce using rule 34 (lvalue)]
$default reduce using rule 34 (lvalue)
在这里,解析器刚刚在ID
可能会减少的上下文中找到exp
,它面临两种可能:
<强>移即可。 exp
为ID [exp] OF exp
,因此最终结果为:
ID '[' exp ']' OF exp --> exp (rule 25)
<强>减少即可。 exp
是左值ID[exp]
,使用以下作品:
ID --> lvalue (rule 34)
lvalue '[' exp ']' --> lvalue (rule 36)
lvalue --> exp (rule 2)
为了使用第二种选择,解析器必须立即将ID
缩减为lvalue
,其中存在问题:解析器在看到{之前无法知道这两种可能性中的哪一种是正确的{1}}跟随匹配的] ,但这在未来很远 - 事实上,它可能是任意数量的令牌。
这里的解决方案是避免强制解析器在此时做出决定。有几种可能性。
由于表达式只能是OF
(而不是更复杂),我们可以将ID [ exp ] OF
从冲突中分解出来:
ID
在此更改之后将当前状态机与状态机进行比较应该清楚它是如何工作的(并且是学习自下而上解析的有用练习)。
如果您不想完成所有这些工作,您可以简单地添加一个“显然多余”的作品,正如Appel在他的教科书中所建议的那样:
exp : ID
| lvalue_not_id
| ...
lvalue: ID
| lvalue_not_id
lvalue_not_ID
: lvalue DOT ID
| ID LBRACK exp RBRACK
| lvalue_not_ID LBRACK exp RBRACK
lvalue: ID
| lvalue DOT ID
| lvalue LBRACK exp RBRACK
| ID LBRACK exp RBRACK
增加的产量显然会产生转移 - 减少冲突;实际上,它与原始语法中的移位 - 减少冲突完全相同。但这一次,冲突发生在lvalue
的两个不同作品之间,默认的移动动作绝对是你想要在裸lvalue
后跟 [< / KBD>。在班次之后,ID
制作和lvalue
制作仍然可用,因此解析器在] 之后找到令牌之前不必做出决定
这个解决方案的缺点是解析器生成器将继续报告shift-reduce冲突,因为显然存在一个。由于移位减少冲突通常被认为是语法可能不明确的标志,因此在代码中留下移位减少冲突将是一个长期维护问题:在每次语法更改之后,有必要验证移位 - 减少冲突是良性的。
另一个解决方案,也不幸地留下了警告,就是使用bison的exp
指令来生成GLR解析器。 GLR算法能够通过有效地同时维护两个(或更多个)不同的可能解析器堆栈来延迟减少决策。对于明确的语法,在输入的长度上仍然是O(n),但它稍慢。 (此外,此选项在许多其他yacc衍生产品中不可用。)
最后,您只需将其作品添加到%glr-parser
即可摆脱lvalue
。然后,您需要将exp
概括为lvalue [ exp ]
,这意味着语法将识别原始语言的超集:它现在将接受某些无效的输入。但是,很容易检查相关作品的语义操作,以查看exp [ exp ]
是否具有exp
的形式;如果不是,则可以在语义操作中生成语法错误。