消除shift/reduce个前缀相同的生产规则引起的冲突

Eliminate shift/reduce conflicts caused by production rules with same prefix

这是简化的 yaac 文件:

%token CONTEXT_    // the corresponding string is "context"
%token CONTEXTREF_ //"contextref"
%token IS_         //"is"
%token ID_L        //"id_l"
%token ID_L1       //"id_l1"
%token LIB_

%start design_file

%%
design_file   :design_unit
              |design_file design_unit 
              ;

design_unit   :context_cl LIB_
              |context_decl
              ;

context_cl    : /* empty */  {  }
              |context_cl context_ref
              ;

context_decl  :CONTEXT_ ID_L IS_ ';'
              ;

context_ref   :CONTEXT_ ID_L1 '.' ID_L ';'
              ;

有 2 个 shift/reduce 冲突。

CONTEXT_  shift, and go to state 1

CONTEXT_  [reduce using rule 5 (context_cl)]

规则 5 是 context_cl : /* empty */ { }

一般来说,这无关紧要,解析器在大多数时候都运行良好。 但是在一个奇怪的情况下,对于如下的源文件:

context id_l is ...
...

解析器不会移动而是减少,因此使用context_ref :CONTEXT_ ID_L1 '.' ID_L ';'来解析context id_l is ...,这会导致语法错误。

所以我必须消除冲突。 我猜它们是由规则 context_declcontext_ref 开头的 CONTEXT_ 引起的。

为了验证我的猜测,我刚刚将 context_ref :CONTEXT_ ID_L1 '.' ID_L ';' 更改为 context_ref :CONTEXTREF_ ID_L1 '.' ID_L ';'。然后,没有冲突,没有语法错误。

但是,我不能这样替换context_ref。这是标准。

那么,如何通过其他方式避免这两种冲突呢?或者,是否有解决此类冲突的通用方法?

并且,对于 shift/reduce 冲突,解析器会选择减少而不是移动吗?因为我认为解析器总是会转移到 reduce 上。

我不确定你的 "intermittent" 语法错误是从哪里来的——正如你写的那样,你的语法永远无法匹配 context id_l1 . id_l 这样的输入...因为转换会导致它尝试解析 context_decl 而不是 context_ref.

不过,您的冲突与通用前缀几乎没有关系 -- 它实际上来自 /* empty */,最简单的解决方法是重构它以删除该规则:

design_unit   : context_cl LIB_
              | LIB_
              | context_decl
              ;

context_cl    : context_ref
              | context_cl context_ref
              ;

所以现在 context_cl 是“1 个或多个”context_ref 而不是“0 个或多个”,并且我们复制了 design_unit 规则以分别具有 0 个案例.

引起这种冲突的基本模式是:

union : x x_rest
      | y_list y_rest
y_list: /* empty */
      | y_list y

其中 xy 都有以相同符号开头的产生式。

值得注意的是,如果 y_list 是一个或多个 y,而不是零个或多个,问题将消失:

y_list: y
      | y_list y

只要xy真的能区分就可以了。 (还有一些其他要求,取决于生产的其余部分,但如果它们具有相同的前缀基本上就可以了。它们甚至可以相同,前提是第一个标记的延续不同。)

如果你真的需要 y_list 可能为空(或者 xy 都可能为空),你应该进行通常的空生产消除,这涉及从语法中分解出可能为空的产生式:

union : x x_rest
      | y_rest           /* Corresponds to an empty y_list */
      | y_list y_rest
y_list: y                /* As above, this list cannot be empty */
      | y_list y