如何解决这个 reduce/reduce 冲突?

How to resolve this reduce/reduce conflict?

我正在为 B 编程语言编写编译器。这种语言的语法在句法上区分左值和右值。在将语法转换为 yacc 语法时,我偶然发现了 reduce/reduce 冲突。这是一个最小的、完整的和可验证的例子:

%right '['
%left '+'

%%

rvalue  : '+' lvalue 
    | lvalue
    ;

lvalue  : 'x'
    | rvalue '[' rvalue ']'
    ;

Yacc 表示 1 reduce/reduce 冲突。此 reduce/reduce 冲突出现在状态 6:

6: reduce/reduce conflict (reduce 1, reduce 2) on '['
state 6
    rvalue : '+' lvalue .  (1)
    rvalue : lvalue .  (2)

    .  reduce 1

似乎很明显应该选择“reduce 1”作为此冲突的解决方案,因为“reduce 2”似乎永远无法导致成功解析。

我该如何解决这个冲突?

出于可移植性的原因,我不愿意在 POSIX.1 2008.

中指定的功能之外使用 bison 或 yacc 的任何功能

为了阅读此问答的任何人的利益,知道问题中的 + 标记旨在成为 pre-increment 运算符 ++。根据评论,进行更改是为了避免必须引入令牌声明。下面,我冒昧地将 '+' 更改为 Bison 语法 "++",因为我认为使用预期运算符的正常拼写不会造成混淆。我也确实使用了 Bison 的 quoted-token 扩展,因为它更具可读性。 (但删除起来很简单。)

冲突的发生是因为实际上存在一个使用 rvalue: lvalue 产生式的有效解析。具体来说,输入

++ x [ x ]

您的语法可以用两种不同的方式解析:

      rvalue                                      rvalue
  /           \                                      |
"++"        lvalue                                lvalue
        /--------------\                   /------------------\
     rvalue '[' rvalue ']'              rvalue   '['   rvalue   ']'
        |          |                   /      \           |
     lvalue     lvalue               "++"   lvalue     lvalue
        |          |                           |          |
       'x'        'x'                         'x'        'x'

注意第一个是你要的parse;下标运算符比前缀增量运算符绑定得更紧密,因此 ++x[x] 被正确解析为 ++ (x[x])。很好,所有语言都以这种方式处理后缀运算符,这与预期的行为相匹配。 (绝大多数程序员会期望 -x[3] 首先提取数组 x 的元素 3,然后取反。首先绑定 -x 根本没有意义。对于++;如果 x 是一个数组,++x-x 一样毫无意义。)

这与您关于应该选择 "reduce 1" 的主张相反;正确的解析需要 "reduce 2" 被采用。该错误也反映在您的优先声明中,逻辑上应该优先 right-associatively 后缀运算符:

%right "++" '['

(从技术上讲,前缀运算符的绑定不如后缀运算符紧密。但由于 right-associativity,它们共享优先级是可以的。)

但是做那个改变没有意义,因为优先级声明不能解决reduce/reduce冲突,因为优先级的解决总是涉及production的优先级之间的比较可以减少,并且可以移动前瞻 token 的优先级。 (也就是说,被比较的东西的种类不同。)

In State 6 (reproduced in the question), parser has shifted the "++" and then the 'x', and then performed the forced reduction of 'x' to [=35] =].所以解析器堆栈是 ... "++" lvalue 并且先行标记是 [。如果文法不试图分离左值和右值(这样栈顶只是 value 而不是 lvalue),那么解析器可用的选择将是减少 "++" valuevalue,或移动 [ 为 right-hand 侧 value '[' value ']' 做准备。使用上面的优先级声明,由于 right-associativity,移位将获胜,因此将出现正确的解析。

但是语法试图区分左值和右值,这使得解析器无法转换 [;要使 [ 有效,必须先 lvalue 减少为 rvalue。然而,优先决定总是立即发生的;解析器并没有真正将 rvalue: lvalue 的缩减视为移动 [ 的前奏。它看到的是两个相互竞争的 reduce 操作,优先级不适用于此类冲突。

由于优先级声明不会帮助解决这个特殊的冲突,因此最简单的方法是避免尝试将它们用于一元运算符,而将它们用于二元运算符。 (也可以根本不使用它们,但它们对于表达二进制优先级很方便。) B reference manual [注 1] 清楚地表明叙述文本,而不是包含的语法,才是精确定义的内容运算符优先级和结合性,叙述文本包括两个句法类别,基本表达式一元表达式,它们没有出现在语法中,但它们是, 事实上,语法上是必要的。

如果我们忽略 lvalue/rvalue 的区别,使用这些 non-terminals 编写语法很容易,所以这是一个很好的起点。 (注意:我将 post-increment/decrement 运算符移动到 primary 以避免依赖优先级声明。)

%token NAME CONSTANT
%token INC "++" DEC "--"
%left '+' '-'
%left '*' '/' '%'
%start value
%%
primary : NAME
        | primary '[' value ']'
        | CONSTANT
        | '(' value ')'
        | primary "++"
        | primary "--"
unary   : primary
        | '*' unary 
        | '-' unary
        | '&' unary
        | "++" unary
        | "--" unary
value   : unary
        | value '+' value
        | value '-' value
        | value '*' value
        | value '/' value
        | value '%' value

现在我们可以看到有两个不同的 non-terminals 需要拆分成 lr 变体,因为 primaryunary 都可以产生左值。 (分别为 x[x]*x。)但是,由于级联,它并不像将这两个 non-terminals 分为两类那么简单:

value   : unary
unary   : primary 

结合所需的左值到右值的隐式缩减。

我们的第一个想法可能是拆分 non-terminals,让级联流经 rvalue: lvalue 个作品:

value   : runary
runary  : lunary
        | rprimary
lunary  : lprimary
rprimary: lprimary

不幸的是,这会产生两条不同的路径来到达 lprimary:

value -> runary -> lunary   -> lprimary
value -> runary -> rprimary -> lprimary 

由于级联产生式没有关联的动作,并且左值到右值的转换(取消引用操作)对于两个实例都是相同的,实际上选择这些路径中的哪一条对我们没有影响。但是解析器会关心,所以我们必须消除其中一个。这是一种可能的解决方案:

%token NAME CONSTANT
%token INC "++" DEC "--"
%left '+' '-'
%left '*' '/' '%'
%start value
%%
lprimary: NAME
        | primary '[' value ']'
primary : lprimary
        | rprimary
rprimary: CONSTANT
        | '(' value ')'
        | lprimary "++"
        | lprimary "--"
lunary  : lprimary
        | '*' runary
runary  : lunary
        | rprimary 
        | '-' runary
        | '&' runary
        | "++" lunary
        | "--" lunary
value   : runary
        | value '+' value
        | value '-' value
        | value '*' value
        | value '/' value
        | value '%' value