%nonassoc NO_ELSE
%nonassoc ELSE
stmt_conditional
: IF '(' expr { show_if_begin($3); } ')' stmt_compound { show_if_end(); } %prec NO_ELSE
| IF '(' expr { show_if_begin($3); } ')' stmt_compound { show_if_else(); } ELSE stmt_compound { show_if_end(); }
由于stmt_compound
会生成IR,show_if_begin()
应位于stmt_compound
之前。但是,这会导致yacc中的减少/减少冲突。如何解决这个问题?
EDITED
这是我尝试过的,但它不起作用。
stmt_conditional
: stmt_cond_if { show_if_end(); } %prec NO_ELSE
| stmt_cond_if { show_if_else(); } ELSE stmt_compound { show_if_end(); }
;
stmt_cond_if
: IF '(' expr { show_if_begin($3); } ')' stmt_compound
答案 0 :(得分:3)
你的变体不起作用的原因是你需要无限的lookakead来区分两种if语句。当您的解析器查看IF令牌时,它不知道它是否会看到ELSE或NO_ELSE,或者何时会看到一个,但它必须决定在哪里转移 now 以便处理表达
解决方案是分解更常见的东西,以便在需要时做出决定。
stmt_conditional
: stmt_cond_if { ... } else_part
;
else_part
: %prec NO_ELSE
| ELSE stmt_compound { ... }
;
理论上,解析器生成器应该能够自动重构这些规则,但代码块在规则中间会阻止yacc
这样做。即使是其他相同规则中的空代码块也会阻止它们被展平。那是因为yacc
将代码块的内容视为黑盒子。它不知道这些行为是什么意思并且必须假设它们可能意味着不同的东西,即使文本是相同的。
您可以验证删除操作时,冲突会消失。当然,没有动作的yacc
语法就不那么有用了。
当然,这个因素将你的行为统一起来,而这些行为最初是针对其他人而非其他变体的。没有办法解决这个问题。你必须写一个对两种情况都有好处的动作。
编辑实际上,yacc
通过为每个中间规则操作创建新的不可见规则和新的非终端来处理此类代码块。在yacc页面上:
不终止规则的操作实际上是由Yacc通过制造新的非终结符号名称以及将此名称与空字符串匹配的新规则来处理的。内部动作是通过识别此添加的规则触发的动作。
这些看不见的规则实际上会导致你的语法冲突。因此,另一种解决方案是将规则保持原样,但将动作移至各自规则的末尾。我个人仍然想手动分解常用部件。 - 结束修改。
如果没有%prec
,就会出现转移/减少警告,因为
if (x)
if (y)
do_something
else
do_something_else
是不明确的:当解析器看到else
时,它不知道它是属于内部if
(移位)还是属于外部({reduce})。默认行为是移位,这是我们通常想要的。 %prec
使其明确并消除警告。
请注意,这仍可能会干扰您的其他语法。如果你可以改变你的语法,我建议你从基于C语言的复合语句语法切换到基于ENDIF的语法,例如在阿达。