【问题标题】:How to resolve this Shift/Reduce conflict in YACC如何解决 YACC 中的这种 Shift/Reduce 冲突
【发布时间】:2025-12-09 09:40:01
【问题描述】:

我有这样的语法:

“匹配一个或多个规则 1,其中规则 1 是一个或多个规则 2,其中规则 2 是一个或多个规则 3,等等。每个规则由换行符分隔”。看下面的例子。

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。

【问题讨论】:

    标签: c++ c parsing yacc bison


    【解决方案1】:

    模棱两可的语法,不是 LALR(1),默认无法解析 yacc 模式

    长话短说,您可以使用%glr-parser 声明“修复”此问题,如下所示:

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

    把长篇故事写成中等长度的……

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

    但是,在真正模棱两可的语法中,执行移位将使解析器沿两条路径之一向下,其中只有一条最终会在语法中找到一个字符串。在这种情况下,S/R 冲突是一个致命错误。

    分析第一个,当解析器在| rule2 NEWLINE rule3_list 的情况下看到换行符时,它可以或者转换到一个新的状态,在这个状态下它应该是一个rule3_list,或者它可以减少一个rule1使用rule1: rule2。由于默认的 shift 选择,它将始终查找 rule3_list。

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

    结果是,如所写,假设终端为“2”和“3”,您只能解析 2 行后跟 3 行。如果您摆弄优先级,则只能解析“2”行,而不能解析“3”行。

    最后,我应该补充一点,使用 yacc 生成的 GLR 解析器有点儿杂乱无章。我想它会工作得很好,但它是纯 BFI,解析器分裂,保留两个堆栈,继续沿着两条路径,直到在语法中找到一个字符串。遗憾的是,其他修复也很麻烦: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; }
    

    【讨论】:

    • 哦,想法,如果您只是跳过扫描仪中的换行符,这可能会给您足够的前瞻性。通过将空白作为标记返回,您不必要地增加了解析器所需的前瞻量。
    • 有趣,但是如何测试用户没有输入换行符?
    • 您可以随时将逻辑下推到您可以完全控制的扫描仪中,并且可以编写您想要的任何 C 代码。您可以通过从 yylex() 返回错误标记来错误输出不跟在单个换行符之后的标记。或者,如果换行符是空白,那么为什么不只允许任意数量的换行符呢?这就是编译器和汇编器所做的......
    • 嘿,我仍然收到错误消息,“期望 TERMINAL3 得到 TERMINAL2”
    • 你确定你重新运行了 yacc 和 cc 吗?这是我的原始语法测试用例,我现在才运行它。我知道它有效:pastie.org/706454
    【解决方案2】:

    不是模棱两可,只是不是 LALR(1)

    问题是语法中的几个地方需要 2-token 前瞻,以查看哪个 TERMINAL 位于 NEWLINE 之后,以便决定要做什么。你可以做很多事情来解决这个问题。

    1. 在扫描器中跳过换行符 -- 这样它们就不再是标记,也不会妨碍前瞻

    2. 使用 %glr 解析器。如果您确实在语法中引入歧义,这可能会很危险,因为它们需要合并函数才能使事情正常进行。没有很好的方法来确定任何给定的冲突是由于模棱两可还是只是需要更多的前瞻性——您需要仔细分析每个冲突野牛报告来判断。

    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 ;
      

    当然,这会改变语法,因为现在在 EOF 之前的最后一条规则之后需要一个换行符

    【讨论】:

      【解决方案3】:

      我认为您必须将左递归转换为右递归。 rule3_list 的示例:

      rule3_list: TERMINAL3 | TERMINAL3 NEWLINE rule3_list;
      

      【讨论】:

        最近更新 更多