C++ 如何解决YACC中的这种转移/减少冲突

C++ 如何解决YACC中的这种转移/减少冲突,c++,c,parsing,yacc,bison,C++,C,Parsing,Yacc,Bison,我的语法是这样的: “匹配一个或多个规则1,其中规则1是一个或多个规则2,其中规则2是一个或多个规则3,等等。每个规则用换行符分隔。”。看看下面的例子 start: rule1_list ; rule1_list: rule1 | rule1_list NEWLINE rule1 ; rule1: rule2 | rule2 NEWLINE rule3_list ; rule2: TERM

我的语法是这样的:

“匹配一个或多个规则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。

语法不明确,而不是LALR(1),默认情况下是yacc模式下不可解析的 长话短说,您可以使用
%glr解析器
声明“修复”此问题,如下所示:

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

把一个长故事变成一个中等长度的故事

移位减少冲突通常不是错误。冲突可以通过始终进行轮班来解决,这通常是您想要的。大多数或所有现实世界的语法都有移位减少冲突。如果你想要减少,你可以用优先级声明来安排

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

分析第一个,当解析器在
|rule2 newline rule3_list
案例中看到换行符时,它可以切换到一个新的状态,在该状态下它将被期望成为一个rule3_list,或者它可以使用
rule1:rule2
减少rule1。由于默认的班次选择,它将始终查找规则3_列表

第二个冲突发生在
rule3\u列表:rule3\u列表中看到换行符时。换行规则3
。现在,它可以移动并开始查找规则3,或者使用
| rule2换行规则3_列表
减少规则1

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

最后,我应该补充一点,使用yacc生成的GLR解析器有点困难。我想它会工作得很好,但它是纯BFI,解析器拆分,保留两个堆栈,沿着两条路径继续,直到在语法中找到一个字符串。不幸的是,其他的修正也是乱七八糟的:1。将语法重新格式化为LALR(1),2。在扫描程序中添加额外的前瞻,并返回一个复合标记,3。尝试一下语法规则,也许yacc可以处理一个变体

这就是为什么我不喜欢yacc,更喜欢手写的递归下降法或者像PEG这样更现代的东西

我尝试了一些(首选)左递归规则,它们忽略了换行符(这会使语法复杂化,生成空白标记…)。。虽然我不确定这是不是你想要的

%%
start:   stmtList
      ;

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

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

我认为必须将左递归转换为右递归。
规则3\u列表的示例如下:

rule3_list: TERMINAL3 | TERMINAL3 NEWLINE rule3_list;

没有歧义,只是没有LALR(1)

问题是语法中有几个地方需要2-token lookeahead,以查看换行符后面的终端,从而决定要做什么。你可以做很多事情来解决这个问题

  • 跳过scaaner中的换行符——这样它们就不再是代币,也不会妨碍前瞻

  • 使用%glr解析器。如果在语法中引入无歧义,这可能会很危险,因为它们需要合并函数才能工作。没有好的方法来确定任何给定的冲突是由于模糊性还是仅仅需要更多的前瞻性——您需要仔细分析每一个冲突野牛报告来告诉我们

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

    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之前的最后一条规则之后现在需要换行符

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