【问题标题】:Bison shift/reduce conflict - tiger compiler野牛移位/减少冲突 - 老虎编译器
【发布时间】:2014-11-17 08:45:19
【问题描述】:

我根据Tiger Book(附录A,Tiger 手册)编写了一个yacc 文件。

但仍然存在一些移位/减少冲突。我不知道如何解决这些冲突。

% yacc --version
bison (GNU Bison) 3.0.2

你可以使用这个cmd来重现问题:

% yacc -dvt tiger.y
tiger.y: warning: 37 shift/reduce conflicts [-Wconflicts-sr]

% cat tiger.y:

%{
#include <stdio.h>
//#include "util.h"
//#include "errormsg.h"

int yylex(void); /* function prototype */

void yyerror(char *s)
{
    EM_error(EM_tokPos, "%s", s);
}
%}


%union {
    int pos;
    int ival;
    string sval;
}


%token <sval> ID STRING
%token <ival> INT

%token
  COMMA COLON SEMICOLON LPAREN RPAREN LBRACK RBRACK
  LBRACE RBRACE DOT
  PLUS MINUS TIMES DIVIDE EQ NEQ LT LE GT GE
  AND OR ASSIGN
  ARRAY IF THEN ELSE WHILE FOR TO DO LET IN END OF
  BREAK NIL
  FUNCTION VAR TYPE


%right ASSIGN
%left OR
%left AND
%nonassoc EQ NEQ LT LE GT GE
%left  PLUS MINUS
%left  TIMES DIVIDE
%left  UNARYMINUS

%precedence THEN
%precedence ELSE


%start program

%%

program:    exp {  }
       ;

exp:lvalue {  }
   |NIL    {  }
   |LPAREN explist RPAREN {  }
   |LPAREN         RPAREN {}
   |INT {}
   |STRING {}
   |MINUS exp %prec UNARYMINUS {}
   |ID LPAREN RPAREN {}
   |ID LPAREN arglist RPAREN {}
   |exp PLUS exp {}
   |exp MINUS exp {}
   |exp TIMES exp {}
   |exp DIVIDE exp {}
   |exp EQ exp {}
   |exp NEQ exp {}
   |exp LT exp {}
   |exp LE exp {}
   |exp GT exp {}
   |exp GE exp {}
   |exp AND exp {}
   |exp OR exp {}
   |ID LBRACE RBRACE {}
   |ID LBRACE idlist RBRACE {}
   |ID LBRACK exp RBRACK OF exp {}
   |lvalue ASSIGN exp {}
   |IF exp THEN exp ELSE exp {}
   |IF exp THEN exp {}
   |WHILE exp DO exp {}
   |FOR ID ASSIGN exp TO exp DO exp {}
   |BREAK {}
   |LET decs IN END {}
   |LET decs IN explist END {}
   ;

lvalue: ID {}
      | lvalue DOT ID {}
      | lvalue LBRACK exp RBRACK {}
      ;

explist: exp {}
       | explist SEMICOLON exp {}
       ;

arglist:exp {}
       |exp COMMA arglist {}
       ;

idlist:ID EQ exp {}
      |ID EQ exp COMMA idlist {}
      ;

decs:dec {}
       |decs dec {}
       ;

dec:tydec {}
   |vardec {}
   |fundec {}
   ;

tydec:TYPE ID EQ ty {}
     ;

ty:ID {}
  |LBRACK tyfields RBRACK {}
  |ARRAY OF ID {}
  ;

tyfields:/* NULL */
        |notnulltyfields {}
        ;

notnulltyfields:ID COLON ID {}
               |ID COLON ID COMMA notnulltyfields {}
               ;

vardec:VAR ID ASSIGN exp {}
      |VAR ID COLON ID ASSIGN exp {}
      ;

fundec:FUNCTION ID LPAREN tyfields RPAREN EQ exp {}
      |FUNCTION ID LPAREN tyfields RPAREN COLON ID EQ exp {}
      ;

【问题讨论】:

  • 向我们展示错误信息...
  • @leppie 嗨,我已经更新了这个问题。 :)

标签: compiler-construction yacc shift-reduce-conflict


【解决方案1】:

通过查看使用-v 标志生成的tiger.output 文件,很容易发现移位减少冲突。

这是一个例子(我把重复的部分删掉了):

State 88

   11 exp: exp . PLUS exp
   12    | exp . MINUS exp
# ...
   29    | WHILE exp DO exp .

    PLUS    shift, and go to state 34
    MINUS   shift, and go to state 35
# ...

    PLUS      [reduce using rule 29 (exp)]
    MINUS     [reduce using rule 29 (exp)]
# ...

    $default  reduce using rule 29 (exp)

我们可以看到,当WHILE 表达式可以被归约时,就会出现状态 88(从状态描述中 . 的位置可以明显看出:

   29    | WHILE exp DO exp .

如果此时的前瞻标记是二元运算符,解析器不知道是移动运算符,使WHILE 表达式中的尾随exp 更长,还是立即减少WHILE。显然(对我们来说,不是bison),解决方案是转移。 bison 不知道这一点,因为生产 exp: WHILE exp DO exp 没有优先级。该产生式的优先级将是其最后一个终端的优先级,即DO,因此简单的解决方案是为DO 定义一个优先级。不出所料,它应该与ELSE 的优先级相同,正如IF exp THEN exp ELSE exp . 确实不会产生移位/减少冲突的事实所示。

类似的问题发生在状态 112 和 129。

从output文件中也可以看出状态1中的移位/减少冲突:

State 1

    9 exp: ID . LPAREN RPAREN
   10    | ID . LPAREN arglist RPAREN
   23    | ID . LBRACE RBRACE
   24    | ID . LBRACE idlist RBRACE
   25    | ID . LBRACK exp RBRACK OF exp
   34 lvalue: ID .

    LPAREN  shift, and go to state 15
    LBRACK  shift, and go to state 16
    LBRACE  shift, and go to state 17

    LBRACK    [reduce using rule 34 (lvalue)]
    $default  reduce using rule 34 (lvalue)

在这里,解析器刚刚在exp 可能被减少的上下文中找到了ID,它面临两种可能性:

  1. 转移。 exp 是ID [exp] OF exp,所以最后的结果是:

    ID '[' exp ']' OF exp        --> exp    (rule 25)
    
  2. 减少。 exp 是左值 ID[exp],使用以下产生式:

    ID                           --> lvalue (rule 34)
    lvalue '[' exp ']'           --> lvalue (rule 36)
    lvalue                       --> exp    (rule 2)
    

为了使用第二种选择,解析器必须立即将ID 减少到lvalue,这就是问题所在:解析器在看到OF 之后才知道这两种可能性中的哪一种是正确的匹配 ],但那是遥远的未来——事实上,它可能是任意数量的令牌。

这里的解决方案是避免强制解析器在此时做出决定。有几种可能。

  1. 由于表达式只能是ID [ exp ] OF(不能再复杂),我们可以将ID 排除在冲突之外:

    exp   : ID
          | lvalue_not_id
          | ...
    
    lvalue: ID
          | lvalue_not_id
    
    lvalue_not_ID
          : lvalue DOT ID
          | ID            LBRACK exp RBRACK
          | lvalue_not_ID LBRACK exp RBRACK
    

    将当前状态机与此更改后的状态机进行比较应该可以清楚地了解其工作原理(并且是学习自下而上解析的有用练习)。

  2. 如果你不想做所有这些工作,你可以简单地添加一个“明显多余”的产品,正如 Appel 在他的教科书中所建议的那样:

    lvalue: ID 
          | lvalue DOT ID 
          | lvalue LBRACK exp RBRACK
          | ID LBRACK exp RBRACK
    

    添加到lvalue 的生产显然会造成班次减少冲突;实际上,它与原始语法中的移位减少冲突完全相同。但是这一次,冲突出现在lvalue 的两个不同产生之间,默认的移位操作绝对是您想要在裸ID 后跟一个 [ 的情况下采取的那个。班次后,lvalue 生产和exp 生产仍然可用,因此解析器不必做出决定,直到找到 ] 之后的令牌。

    此解决方案的缺点是解析器生成器将继续报告移位减少冲突,因为显然存在冲突。由于 shift-reduce 冲突通常被认为是语法可能不明确的标志,因此在代码中留下 shift-reduce 冲突将是一个长期维护问题:在每次语法更改后,都需要验证 shift-reduce减少冲突是良性的。

  3. 不幸的是,另一个解决方案也是使用 bison 的 %glr-parser 指令来生成 GLR 解析器。 GLR 算法能够通过同时有效地维护两个(或更多)不同的可能解析器堆栈来延迟归约决策。对于明确的文法,输入的长度仍然是 O(n),但速度稍慢。 (此外,此选项在许多其他 yacc 衍生产品中不可用。)

  4. 最后,您只需将 lvalue 添加到 exp 即可摆脱它。然后,您需要将lvalue [ exp ] 概括为exp [ exp ],这意味着语法将识别原始语言的超集:它现在将接受某些无效的输入。但是,很容易检查相关产生式的语义动作以查看exp 是否具有lvalue 的形式;如果不是,您可以在语义操作中生成语法错误。

【讨论】:

  • 谢谢一百万。我真的很感激。
  • 这个答案真的很有价值。非常感谢。在 shift/reduce 错误的情况下,解析器生成器是否可以生成一个解析器,它首先尝试“shift”,如果失败,则尝试“reduce”?
  • @shanelu:有解析器生成会生成回溯解析器,但野牛不是其中之一(Java CUP 也不是)。使用野牛,您可以生成一个 GLR 解析器,它可以延迟解析直到信息可用;在这种情况下会起作用。
  • @ShaneLu:我没有阿佩尔的教科书,但我在谷歌书籍中找到了相关页面,所以我想我现在知道阿佩尔在暗示什么(尽管他也问是否是可以消除我原来的答案所做的移位减少冲突)。无论如何,我添加了“冗余生产”解决方案,因为它看起来很有趣。
  • @rici 我现在在这个问题上花了很长时间。非常感谢你让我这么清楚!我现在真的对解析器有了更好的理解。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-20
  • 1970-01-01
  • 2021-12-24
  • 2013-07-09
  • 1970-01-01
相关资源
最近更新 更多