当我尝试使用$bison时--output=calcy.c-d calc.y我收到这个消息:
calc.y: conflicts: 2 shift/reduce
这是我的代码
program:DEBUT
corps
FIN
;
corps:liste_declaration
liste_instruction
;
liste_declaration:
| declaration";"liste_declaration
;
declaration: ENTIER IDENTIFICATEUR
;
liste_instruction:
|instruction";"liste_instruction
;
instruction: IDENTIFICATEUR"="expression
| DEBUT liste_instruction FIN
| SI expression
ALORS instruction
SINON instruction
| SI expression ALORS instruction
| TANTQUE expression FAIRE instruction
| ECRIRE liste_argument
| LIRE IDENTIFICATEUR
;
liste_argument:
| expression
| expression ";"liste_argument
;
expression:expression"<"expression_simple
|expression">"expression_simple
|expression"=="expression_simple
|expression"!="expression_simple
|expression"<="expression_simple
|expression">="expression_simple
|expression_simple
;
expression_simple:expression_simple"+"terme
|expression_simple"-"terme
|"("expression_simple")"
|terme
|"-"terme
;
terme:terme"*"facteur
|terme"/"facteur
|terme"^"facteur
|facteur
;
facteur:IDENTIFICATEUR|NOMBRE
;
我搜索了很多,我认为这是由歧义引起的,但我找不到它在哪里。
正如Chris Dodd先生在评论中建议的那样,我使用了-v
选项,并在我的.output file.nv 中得到了这个选项
Grammar
0 $accept: program $end
1 program: DEBUT corps FIN
2 corps: liste_declaration liste_instruction
3 liste_declaration: /* empty */
4 | declaration ";" liste_declaration
5 declaration: ENTIER IDENTIFICATEUR
6 liste_instruction: /* empty */
7 | instruction ";" liste_instruction
8 instruction: IDENTIFICATEUR "=" expression
9 | DEBUT liste_instruction FIN
10 | SI expression ALORS instruction SINON instruction
11 | SI expression ALORS instruction
12 | TANTQUE expression FAIRE instruction
13 | ECRIRE liste_argument
14 | LIRE IDENTIFICATEUR
15 liste_argument: /* empty */
16 | expression
17 | expression ";" liste_argument
18 expression: expression "<" expression_simple
19 | expression ">" expression_simple
20 | expression "==" expression_simple
21 | expression "!=" expression_simple
22 | expression "<=" expression_simple
23 | expression ">=" expression_simple
24 | expression_simple
25 expression_simple: expression_simple "+" terme
26 | expression_simple "-" terme
27 | terme
28 terme: terme "*" facteur
29 | terme "/" facteur
30 | facteur
31 facteur: X "^" facteur
32 | X
33 X: "(" expression_simple ")"
34 | "-" X
35 | IDENTIFICATEUR
36 | NOMBRE
这些是有冲突的州:
state 33
16 liste_argument: expression .
17 | expression . ";" liste_argument
18 expression: expression . "<" expression_simple
19 | expression . ">" expression_simple
20 | expression . "==" expression_simple
21 | expression . "!=" expression_simple
22 | expression . "<=" expression_simple
23 | expression . ">=" expression_simple
";" shift, and go to state 53
"<" shift, and go to state 40
">" shift, and go to state 41
"==" shift, and go to state 42
"!=" shift, and go to state 43
"<=" shift, and go to state 44
">=" shift, and go to state 45
";" [reduce using rule 16 (liste_argument)]
$default reduce using rule 16 (liste_argument)
state 56
10 instruction: SI expression ALORS instruction . SINON instruction
11 | SI expression ALORS instruction .
SINON shift, and go to state 70
SINON [reduce using rule 11 (instruction)]
$default reduce using rule 11 (instruction)
您有两个shift-reduce冲突,它们有完全不同的原因。严格地说,只有一个是模棱两可的;具有讽刺意味的是,它更容易修复。
1.歧义:晃来晃去
一个是经典的"悬挂其他";歧义(维基百科的法语页面建议可以翻译为sinon pendent。这种歧义很容易修复或解决:
-
将
%expect 1
添加到野牛文件中。(Bison允许您将此声明放在文件中的任何位置,因此将其放在instruction
定义之前或之后是合理的。但在其他yacc方言中,您需要将其放于文件中的第一个%%
之前。)所有这些都是告诉解析器生成器,如果正好有一个偏移减少冲突,就取消显示警告消息。冲突将使用默认解决方案来解决,即更倾向于移动
SINON
令牌。在这种情况下,这正是您想要的,这就是为什么它是默认的解决方案。 -
使用优先级声明使移位
SINON
比减少ALORS
产量更可取。(产品以产品中最后一个终端命名,而不是第一个。)这与第一个具有完全相同的效果,但以稍微更稳健的方式,因为它不依赖于减少冲突的总次数。
-
重写语法,使
SI ... ALORS ... SINON
明确无误。这可以说是最好的解决方案,但它需要做更多的工作,因为它迫使您考虑所有可以以语句结尾的复合语句。在您的案例中,只有两个——SI和TANTQUE——但您可能希望稍后添加其他循环构造。你可以在维基百科页面上找到示例代码(目前这个页面非常混乱,所以现在可能不是最好的地方),或者通过搜索";悬挂其他";并运用一些谨慎。
2.勉强无歧义:;
的双重意义
另一个shift-reduce冲突是由于在ECRIRE
语句中使用;
来分隔语句和分隔参数。这意味着,当解析器在分析ECRIRE
语句时到达;
时,它无法知道;
是否终止该语句。如果它是语句的末尾,解析器必须将ECRIRE liste_argument
减少为instruction
;否则需要对;
进行移位,以便将另一个expression
添加到liste_argument
。
严格来说,这并不是模棱两可的,因为您的语言要求所有语句都以关键字或IDENTIFICATEUR "="
开头,而这两者都不能作为表达式的开头。因此,解析器最多只需要查看三个符号(包括;
),就可以确定接下来的是表达式还是语句。
这一事实对您没有多大帮助,因为野牛解析器通常只会查看下一个符号,即;
,并且必须在此基础上做出缩减决策。因此,即使语法并不含糊——每个有效的程序只能用一种方式解析——bison解析器在需要知道的时候也不知道该使用哪个解析。
可以解决这个问题,因为可以为任何可以用有界前瞻分析的语言编写一个单符号前瞻语法。但它很乏味,而且由此产生的语法又大又笨重,而且很难阅读。
此外,将来在您的语言中添加一些内容可能会使ECRIRE
语句实际上不明确。例如,您(当前)不允许函数调用。但在某些情况下,您可能希望将函数添加到您的语言中,当您这样做时,您可能想要在不将函数的返回值分配给任何变量的情况下调用函数。这通常只需将expression
添加到可能的instruction
的列表中即可完成,但一旦这样做,ECRIRE
中的分号就会变得不明确,因为它后面可能是要打印的表达式,或者是要作为下一条语句执行的表达式。
如果你确信这永远不会成为一个问题,并且语法真的会像现在这样毫不含糊,那么你可以通过告诉bison生成%glr-parser
来解决这个问题。尽管这使野牛的工作变得复杂,但它对你没有太大影响:你可以继续以同样的方式使用语法,只要它不含糊。
但是,如果你认为你可能想在某个时候引入表达式语句,那么你最好改变语言的语法。如果您使用,
将参数分隔为ECRIRE
,并保留;
来分隔语句(这是一种非常常见的方法),那么这两个分隔符将不会相互干扰,语法将自动明确。