Parsing 如何解决此减少/减少冲突?
我正在为B编程语言编写一个编译器。这种语言的语法在句法上区分左值和右值。在将语法转换为yacc语法时,我偶然发现了reduce/reduce冲突。以下是一个最小、完整且可验证的示例:Parsing 如何解决此减少/减少冲突?,parsing,yacc,reduce-reduce-conflict,b,Parsing,Yacc,Reduce Reduce Conflict,B,我正在为B编程语言编写一个编译器。这种语言的语法在句法上区分左值和右值。在将语法转换为yacc语法时,我偶然发现了reduce/reduce冲突。以下是一个最小、完整且可验证的示例: %right '[' %left '+' %% rvalue : '+' lvalue | lvalue ; lvalue : 'x' | rvalue '[' rvalue ']' ; Yacc表示1减少/减少冲突。这种减少/减少冲突出现在状态6中: 6: reduc
%right '['
%left '+'
%%
rvalue : '+' lvalue
| lvalue
;
lvalue : 'x'
| rvalue '[' rvalue ']'
;
Yacc表示1减少/减少冲突。这种减少/减少冲突出现在状态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的任何功能。为了便于阅读此问题和答案的任何人,了解问题中的+
标记是用于预增量运算符+
。根据一条评论,做出更改是为了避免引入令牌声明。下面,我自由地将'+'
更改为Bison语法“++”
,因为我认为使用预期运算符的正常拼写比较容易混淆。我还使用了Bison的引用令牌扩展,因为它更具可读性。(但删除它很简单。)
发生冲突的原因是实际存在使用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
毫无意义。这对于++
来说同样正确;如果x
是一个数组,+x
与-x
一样没有意义)
这与你认为应该选择“减少1”的主张相反;正确的解析要求使用“reduce 2”。该错误也反映在您的优先级声明中,该声明在逻辑上应赋予后缀运算符关联的优先级:
%right "++" '['
(从技术上讲,前缀运算符的绑定不如后缀运算符紧密。但由于正确的关联性,它们可以共享优先级。)
但是,进行这种更改没有意义,因为优先级声明无法解决reduce/reduce冲突,因为按优先级进行的解决总是涉及到可以减少的产品的优先级与可以转移的先行标记的优先级之间的比较。(换句话说,所比较的事物的类型不同。)
在状态6(在问题中再现)中,解析器先移动“+”
,然后移动'x'
,然后执行'x'
到左值的强制缩减。因此解析器堆栈是。。。“++”左值
,先行标记是[
。如果语法没有尝试分离左值和右值(因此堆栈的顶部只是值
,而不是左值
),那么解析器可以选择将的“++”值
减少为值
,或者将[
为右边的value'['value']'
做准备。使用上面的优先级声明,由于右关联性,移位将获胜,因此将出现正确的解析
但是语法试图区分左值和右值,这使得解析器无法将[
;for[
要想有效,它必须首先将lvalue
减少为rvalue
。然而,优先级决定总是立即做出的;解析器并不认为rvalue:lvalue
减少是移位[
。它看到的是两个相互竞争的reduce操作,优先级不适用于此类冲突
由于优先级声明无助于解决这一特定冲突,因此最简单的方法是避免将它们用于一元运算符,而保留它们用于二元运算符。(也可以完全不使用它们,但它们便于表示二元优先级。)[注1]明确了叙事文本,而不是包含的语法,是准确定义运算符优先级和关联性的,叙事文本包括两个句法类别,主要表达式和一元表达式,它们没有出现在语法中,但实际上在语法上是必要的
如果忽略左值/右值的区别,那么使用这些非终结符编写语法很容易,因此这是一个很好的起点。(注意:我将递增/递减后运算符移到了primary
中,以避免依赖优先级声明。)
现在我们可以看到,有两个不同的非终端需要分成l和r变体,因为主
和一元
都可以产生左值。(x[x]
和*x
)然而,由于级联的原因,它并不像将这两个非终端划分为两类那样简单:
value : unary
unary : primary
结合期望的将左值隐式缩减为右值
我们的第一个想法可能是拆分非终端,让级联流通过rvalue:lvalue
productions:
value : runary
runary : lunary
| rprimary
lunary : lprimary
rprimary: lprimary
不幸的是,这会产生两种不同的路径来到达lpprimary
:
value -> runary -> lunary -> lprimary
value -> runary -> rprimary -> 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