【问题标题】:How to resolve this reduce/reduce conflict?如何解决这个减少/减少冲突?
【发布时间】:2019-10-26 07:01:49
【问题描述】:

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

%right '['
%left '+'

%%

rvalue  : '+' lvalue 
    | lvalue
    ;

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

Yacc 表示 1 个 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”似乎永远无法成功解析。

我该如何解决这个冲突?

出于可移植性的原因,我不愿意使用 bison 或 POSIX.1 2008 中指定之外的任何 yacc 功能。

【问题讨论】:

  • 在您看来,+x[x] 的解析是什么? +(x[x]) 只能通过你说的减少来实现,这是不可能的。 (+x)[x] 不是一个非常常见的解析,尽管我没有寻找 B 语法来验证该语言不是例外。你的语法似乎允许两者,所以它模棱两可。
  • @rici 根据 B 语法,这应该解析为 (+x)[x]。对于++x[x][x],也有类似(+(+x)[x])[x] 的解析。 B grammar 似乎暗示了这一点,但我可能误解了它。
  • 我不同意。 (4.1,p7)“向量是主要表达式,后跟 [] 括号中的任何表达式。” + 似乎不是一元运算符,但在任何情况下,一元运算符 (4.2) 的应用都不是主表达式,因此 *x[x]*(x[x])(与 -x[x] 不同,它是左值)在 B 和 C 中。
  • 另外,++ 是一个单一的词位,因此不能分成两个 + 标记。作为一个单一的词位,它是一个一元运算符,所以我上面的评论也适用于它。
  • @rici 对于这个问题,我将++ 更改为+,所以我不需要令牌声明。但进一步思考,你是对的,它应该被解析为++(x[y])。并没有真正改变这个问题,因为我也无法将冲突减少到那个方向。

标签: parsing yacc reduce-reduce-conflict b


【解决方案1】:

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

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

++ x [ x ]

可以通过您的语法以两种不同的方式进行解析:

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

注意第一个是你想要的解析;下标运算符比前缀增量运算符绑定得更紧密,因此++x[x] 被正确解析为++ (x[x])。几乎所有语言都以这种方式处理后缀运算符,这符合预期的行为。 (绝大多数程序员会期望 -x[3] 首先提取数组 x 的元素 3,然后将其取反。首先绑定 -x 完全没有意义。对于 ++ 来说同样如此;如果 @ 987654335@ 是一个数组,++x-x 一样没有意义。)

这与您应该选择“减少 1”的断言相反;正确的解析要求采用“减少 2”。该错误也反映在您的优先声明中,从逻辑上讲,它应该以右关联的方式将优先权赋予后缀运算符:

%right "++" '['

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

但是进行这种更改是没有意义的,因为优先级声明无法解决减少/减少冲突,因为优先级解决总是涉及可以减少的生产的优先级与优先级之间的比较可以移动的前瞻 token。 (也就是说,被比较的事物的类型不同。)

在状态 6(在问题中重现)中,解析器已将 "++" 移位,然后将 'x' 移位,然后将 'x' 强制归约到 lvalue。所以解析器堆栈是... "++" lvalue,前瞻标记是[。如果语法没有尝试分离左值和右值(因此堆栈的顶部只是value 而不是lvalue),那么解析器可用的选择是将"++" value 减少到value,或转移[ 以准备右侧value '[' value ']'。使用上述优先级声明,由于右关联性,移位会获胜,因此会出现正确的解析。

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

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

如果我们忽略左值/右值的区别,使用这些非终结符编写语法很容易,所以这是一个很好的起点。 (注意:我将后自增/自减运算符移至 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

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

value   : unary
unary   : primary 

结合所需的将左值隐式简化为右值。

我们的第一个想法可能是只拆分非终端,让级联流过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

【讨论】:

  • 感谢您的回复。有趣的是,语法中的一个矛盾变得很明显:BNF 语法表明*1234[x] 必须被解析为(*1234)[x],因为1234 不是左值。然而,优先级描述表明这被解析为*(1234)[x],从而产生语义错误。我想我会选择后者,因为错误消息表明解析器确实没有处理左值。
  • @fuz:在 B 中,与在 C 中一样,1234[x]x[1234] 的含义完全相同。两者均表示*(x+1234)。所以这不是语义错误。
  • 嗯...确实。我必须再考虑一下。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-03
  • 1970-01-01
相关资源
最近更新 更多