如何在YACC中解决此Shift / Reduce冲突

时间:2009-11-19 00:20:49

标签: c++ c parsing yacc bison

我有这样的语法:

“匹配一个或多个rule1,其中rule1是一个或多个rule2,其中rule2是一个或多个rule3等,每个由换行符分隔”。请看下面的例子。

start:   rule1_list
      ;

rule1_list:   rule1
           |  rule1_list NEWLINE rule1
            ;

rule1:   rule2
     |   rule2 NEWLINE rule3_list
      ;

rule2:   TERMINAL2
      ;

rule3_list:   rule3
          |   rule3_list NEWLINE rule3
          ;

rule3 :  TERMINAL3
      ;

我这样做了转换/减少冲突,如何更改语法停止?基本上它需要在一个新行之后进行分支,看看下一个是TERMINAL2还是TERMINAL3。

3 个答案:

答案 0 :(得分:5)

模糊语法,而不是LALR(1),默认情况下不可解析yacc模式

总而言之,您可以使用%glr-parser声明“修复”此内容,如下所示:

%glr-parser
%%
start: rule1_list
. . .
. . .

长篇大论... ...

Shift-reduce冲突通常不是错误。通过总是做你想要的转变来解决冲突。大多数或所有现实世界的语法都有移位减少冲突。如果你想减少,你可以用优先声明来安排。

然而,在一个真正含糊不清的语法中,执行移位会将解析器发送到两个路径中的一个路径,其中只有一个最终会在语法中找到一个字符串。在这种情况下,S / R冲突是一个致命的错误。

分析第一个,当解析器在| rule2 NEWLINE rule3_list情况下看到换行符时,它可以 转移到一个新的状态,在那里它将是一个rule3_list,或者它可以减少使用rule1: rule2的规则1。由于默认选择shift,它总是会查找rule3_list。

第二次冲突是在rule3_list: rule3_list . NEWLINE rule3中看到换行符时发生的。现在它可以 转移并开始寻找规则3或使用| rule2 NEWLINE rule3_list减少规则1。

结果是写入时,假设终端为“2”和“3”,则只能解析2行后跟3行。如果你使用优先级,你只能解析'2'行,而不是'3'行。

最后,我应该补充一点,使用yacc生成的GLR解析器是一个很好的解决方案。我想它会工作得很好但它是纯粹的BFI,解析器分裂,保持两个堆栈,继续向下两个路径,直到找到语法中的字符串。可悲的是,其他修正也是kludges: 1。 将语法重新表述为LALR(1), 2。 在扫描仪中添加额外的前瞻并返回一个复合令牌, 3。 尝试使用您所拥有的语法规则,也许yacc可以处理变体。

这就是为什么我实际上并不喜欢yacc,而更喜欢手写的递归下降或像PEG那样更现代的东西。 (See Treetop.)

我尝试了一些(首选的)左递归规则,它们简单地忽略了换行符(这会使你的语法变得复杂,制作空格令牌......)..而且这个“有效”,虽然我不确定它是什么你想要......

%%
start:   stmtList
      ;

stmtList: /* nothing */ 
      | stmtList '2' threeList;
      ;

threeList: /* nothing */
      | threeList '3'
      ;
%%
int yylex() { int c; do {  c = getchar (); } while (c == '\n'); return c; }

答案 1 :(得分:1)

不含糊,只是不是LALR(1)

问题在于语法中的几个地方需要2个令牌lookeahead,以查看NEWLINE之后的哪个TERMINAL来决定做什么。你可以做很多事情来解决这个问题。

  1. 跳过scaaner中的换行符 - 然后它们将不再是令牌而不会妨碍前瞻

  2. 使用%glr-parser。如果您在语法中引入了歧义,这可能会很危险,因为它们需要合并函数来使事情有效。没有什么好方法可以确定任何给定的冲突是由于模棱两可还是需要更多的前瞻 - 你需要仔细分析每个冲突野牛报告。

  3. 重构语法以推迟决策,因此不需要前瞻性。一个简单的选择是将新行吸收到规则中作为终结符而不是分隔符:

    start:   rule1_list ;
    
    rule1_list:   rule1
              |  rule1_list rule1
              ;
    
    rule1:   rule2
         |   rule2 rule3_list
         ;
    
    rule2:   TERMINAL2 NEWLINE ;
    
    rule3_list:   rule3
              |   rule3_list rule3
              ;
    
    rule3 :  TERMINAL3 NEWLINE ;
    
  4. 当然,这会改变语法,因为现在在EOF之前的最后一条规则之后需要换行

答案 2 :(得分:0)

我认为你必须将左递归转换为正确的递归。 rule3_list的一个示例:

rule3_list: TERMINAL3 | TERMINAL3 NEWLINE rule3_list;