我有以下lex定义:
[a-zA-Z][a-zA-Z0-9_]* return NAME;
\, return COMMA;
\: return COLON;
\; return SEMICOLON;
\( return OPAREN;
\) return CPAREN;
\+ return PLUS;
以下yacc制作规则:
program:
| program statement;
arglist:
OPAREN CPAREN
| OPAREN expressionlist CPAREN;
trailed:
NAME
| trailed arglist;
expression:
trailed
| expression PLUS trailed;
expressionlist:
expression
| expressionlist COMMA expression;
statement:
expression SEMICOLON
|NAME arglist COLON expression SEMICOLON;
如果我注释掉最后一条规则,那么一切都会好好编译。根据最后一条规则,我遇到了冲突:
yacc: 1 shift/reduce conflict.
所以我猜,yacc无法决定是将下一个符号移到堆栈上还是使用给定规则来减少堆栈。
我的语法模棱两可吗?
规则“trailed:trailed arglist”和“statement:NAME arglist COLON expression SEMICOLON”之间的决定不应该没有冲突,因为前者从来没有冒号,而后者总是有?
这与先行缓冲区的大小有关吗?
如何修复此语法以解析“a(b)();”和“a(b,c):b + c;”作为有效的陈述?
如何以更详细的方式回溯冲突?
----编辑
关于MichaelMoser的回答:
更改
arglist:
OPAREN CPAREN
| OPAREN expressionlist CPAREN;
expressionlist:
expression
| expressionlist COMMA expression;
到
arglist: OPAREN expressionlist CPAREN;
expressionlist:
| expressionlist COMMA expression; //this now allows for expression lists like ,a,b but NVM
如建议没有帮助。 statement
活跃的第二条规则仍然存在冲突,一旦评论该规则,就不会发生冲突。
答案 0 :(得分:2)
正如其他人所指出的那样,问题是您需要多个前瞻标记来区分函数定义和函数调用。编写语法的问题在于,当前瞻为trailed: NAME
时,需要在看到statement: NAME arglist COLON expression SEMICOLON
之后决定减少规则NAME
和转移以匹配规则OPAREN
。但是在它看到arglist之后才能决定是否有COLON
之后(这是两个案例的区别)。
要解决此问题,您需要重构语法,以便在到达COLON
之前不需要减少仅存在于一个替代方案上的任何内容。使用此语法,您可以通过重构trailed
规则以始终至少需要一个arglist,并使NAME
没有arglist
单独的expression
规则来实现此目的:< / p>
trailed:
NAME arglist
| trailed arglist;
expression:
NAME
| trailed
| expression PLUS NAME
| expression PLUS trailed;
现在,当您获得输入NAME OPAREN ...
时,无需减少任何内容 - 您只需切换到与arglist
匹配的规则,并在匹配arglist
之后,您可以看到下一个标记,并确定这是函数调用还是函数定义。
答案 1 :(得分:1)
如果你想调试shift / reduce错误,那么将--report = all --report-file = pars.report添加到bison命令行,生成的pars.report文件会让你清楚。
缩小的语法文件
%token OPAREN CPAREN NAME PLUS COMMA SEMICOLON COLON %% program: | program statement; arglist: OPAREN CPAREN | OPAREN expressionlist CPAREN; trailed: NAME | trailed arglist; expression: trailed | expression PLUS trailed; expressionlist: expression | expressionlist COMMA expression; statement: expression SEMICOLON |NAME arglist COLON expression SEMICOLON;
给出以下报告:
State 3 conflicts: 1 shift/reduce .... state 3 3 arglist: . OPAREN CPAREN 4 | . OPAREN expressionlist CPAREN 5 trailed: NAME . [OPAREN, PLUS, SEMICOLON] 12 statement: NAME . arglist COLON expression SEMICOLON OPAREN shift, and go to state 7 OPAREN [reduce using rule 5 (trailed)] $default reduce using rule 5 (trailed) arglist go to state 8
在报告的开头有一个导致错误的解析器状态,然后在以后你可以看到解析状态的细节。这个文件也很有趣,因为它实际上解释了解析器在每一步都在做什么。
冲突发生在
之间statement : NAME arglist COLON expression SEMICOLON;
和
statement : expression; expression : trailed; trailed : NAME | tailed arglist;
答案 2 :(得分:0)
鉴于源语法:
%token COLON COMMA CPAREN NAME OPAREN PLUS SEMICOLON
%%
program:
| program statement
;
arglist:
OPAREN CPAREN
| OPAREN expressionlist CPAREN
;
trailed:
NAME
| trailed arglist
;
expression:
trailed
| expression PLUS trailed
;
expressionlist:
expression
| expressionlist COMMA expression
;
statement:
expression SEMICOLON
| NAME arglist COLON expression SEMICOLON
;
我从bison -v xyz.y
获得的报告提供了xyz.y: conflicts: 1 shift/reduce
,但xyz.output
中的说明与MichaelMoser中的answer报告略有不同。
State 3 conflicts: 1 shift/reduce
…
state 3
5 trailed: NAME .
12 statement: NAME . arglist COLON expression SEMICOLON
OPAREN shift, and go to state 7
OPAREN [reduce using rule 5 (trailed)]
$default reduce using rule 5 (trailed)
arglist go to state 8
这意味着当语法读取了NAME并获得OPAREN时,无法推断出它是否在trailed
后面跟arglist
或是statement
使用NAME后跟arglist
。在它到达COLON之前,它将无法确定差异,因为它太远而无法通过一个前瞻标记来确定。
这使得语法无法使用普通Yacc进行解析,因为Yacc只能向前看一个标记。 (我不确定这是否会使其变得含糊不清 - “在Yacc中不起作用”涵盖了这种情况.Bison提供了可能有用的GLR语法。)