如何克服LALR语法中的shift-reduce冲突

时间:2016-08-16 23:10:55

标签: parsing yacc shift-reduce-conflict lemon shift-reduce

我试图解析正负小数。

number(N) ::= pnumber(N1).

number(N) ::= nnumber(N1).

number(N) ::= pnumber(N1) DOT pnumber(N2).

number(N) ::= nnumber(N1) DOT pnumber(N2).

pnumber(N) ::= NUMBER(N1).

nnumber(N) ::= MINUS NUMBER(N1).

包含前两个规则会产生转换/减少冲突,但我不知道如何编写语法以便不会发生冲突。 我正在使用Lemon解析器。

编辑:来自.out文件的冲突

State 79:
     (56) number ::= nnumber *
          number ::= nnumber * DOT pnumber

                           DOT shift        39
                           DOT reduce       56      ** Parsing conflict **
                     {default} reduce       56     number ::= nnumber

State 80:
     (55) number ::= pnumber *
          number ::= pnumber * DOT pnumber

                           DOT shift        40
                           DOT reduce       55      ** Parsing conflict **
                     {default} reduce       55     number ::= pnumber
State 39:
          number ::= nnumber DOT * pnumber
          pnumber ::= * NUMBER

                        NUMBER shift-reduce 59     pnumber ::= NUMBER
                       pnumber shift-reduce 58     number ::= nnumber DOT pnumber

State 40:
          number ::= pnumber DOT * pnumber
          pnumber ::= * NUMBER

                        NUMBER shift-reduce 59     pnumber ::= NUMBER
                       pnumber shift-reduce 57     number ::= pnumber DOT pnumber

编辑2:导致问题的最小语法

start ::= prog.
prog ::= rule.
rule ::= REVERSE_IMPLICATION body DOT.
body ::= bodydef.
body ::= body CONJUNCTION bodydef.
bodydef ::= literal.
literal ::= variable.
variable ::= number.
number ::= pnumber.
number ::= nnumber.
number ::= pnumber DOT pnumber.
number ::= nnumber DOT pnumber.
pnumber ::= NUMBER.
nnumber ::= MINUS NUMBER.

2 个答案:

答案 0 :(得分:2)

您显示的冲突表明number非终端使用的方式存在问题,而不是number本身。

基本问题是,在看到pnumbernnumber后,当前瞻的下一个标记是DOT时,它无法决定是否应该结束number {减少,因此DOTnumber之后的其他非终端的一部分,或者DOT应该是作为number的一部分处理(移位以便稍后可以减少一个p / nnumber DOT pnumber规则。)

因此,为了诊断问题,您需要在右侧的任何地方显示使用number的所有规则(并且递归地使用任何其他使用这些规则的规则'右边的非终端。)

请注意,仅发布一个语法片段很少有用,因为LR解析器构建过程在很大程度上取决于语法中其他地方使用规则的上下文...

因此,问题在于您需要使用双令牌前瞻来区分(真实)DOT number中的literal和结尾处的DOT rule

简单的解决方法是让词法分析器处理它 - 词法分析器可以很容易地进行少量前瞻,因此你可以将REAL_NUMBER识别为NUMBER的一个独特的非终端(可能仍然如果没有-,那么您最终会以

结束
number ::= NUMBER | MINUS NUMBER | REAL_NUMBER | MINUS REAL_NUMBER

通过分解语法来消除冲突要困难得多,但可以做到。

通常,要重构语法以消除先行冲突,您需要找出表明冲突的规则(此处为rulenumber)并重构事物以将它们组合成规则具有共同的前缀,直到你足够消除歧义。

首先,我假设除了number之外还有其他规则可以出现在这里,否则我们就可以消除所有干预规则。

variable ::= number | name

我们希望移动number规则" up"在语法中将ruleDOT放在同一个地方。所以当我们以number结尾时,我们需要将包含规则拆分为特殊情况。我们添加后缀以表示与原始规则对应的规则,所有版本以number分割结束

variable ::= number | variable_n
variable_n ::= name

...并传播" up"

literal ::= number | literal_n
literal_n ::= variable_n

......再次

bodydef ::= number | bodydef_n
bodydef_n := literal_n

......再次

body ::= number | body_n
body := body CONJUNCTION number
body_n ::= bodydef_n
body_n ::= body CONJUNCTION bodydef_n

请注意,当你向上移动时,你需要拆分越来越多的规则,所以这个过程可能会夸大语法。但是,在您重构的rhs结尾处仅使用 的规则最终只需要_n版本,因此您不一定要规则数量加倍。

......最后一步

rule ::= REVERSE_IMPLICATION body_n DOT
rule ::= REVERSE_IMPLICATION number DOT
rule ::= REVERSE_IMPLICATION body CONJUNCTION number DOT

现在你在所有相同的地方都有DOT,所以展开number规则:

rule ::= REVERSE_IMPLICATION body_n DOT
rule ::= REVERSE_IMPLICATION integer DOT
rule ::= REVERSE_IMPLICATION integer DOT pnumber DOT
rule ::= REVERSE_IMPLICATION body CONJUNCTION integer DOT
rule ::= REVERSE_IMPLICATION body CONJUNCTION integer DOT pnumber DOT

并且shift-reduce冲突消失了,因为规则具有共同的前缀,直到超过所需的前瞻以确定使用哪个。 我通过添加

减少了最终扩展中的规则数量
integer ::= pnumber | nnumber

答案 1 :(得分:0)

您必须声明DOT运算符令牌与%left%right的关联性。

或者,另一个想法是放弃这种中间减少。语法中的一个显而易见的特征是数字增长DOT后跟一个数字。这可以通过一条规则来捕获:

number : number DOT NUMBER

后跟DOT后跟NUMBER令牌的数字仍然是一个数字。

此规则并不要求DOT声明关联性,因为没有歧义;规则纯粹是左递归的,DOT的右手是终端令牌。当状态机此时,解析器必须将堆栈顶部减少到number,然后转换DOT

number : number DOT NUMBER

您在这里解析的语言是常规的;它可以通过正则表达式进行解析而无需任何递归。这就是为什么规则中既包含左右递归又需要声明关联性的规则,这些规则在某种程度上是一个很大的锤子"。