语法中的减少-减少冲突来自上下文:
... = ... x ,
此时,解析器必须决定x
是 anfe
还是 an fp
,并且它无法通过一个符号前瞻来知道。实际上,它无法通过任何有限的前瞻知道,您可以在x ,
不遇到 a或输入结尾的情况下重复该点的任意次数,其中任何一个都会揭示答案=
。;
这与 C 问题不太一样,可以通过单符号前瞻来解决。但是,C 示例是为什么 SLR(1) 语法不如 LALR(1) 语法强大的经典说明——它在龙书中用于此目的——并且类似的有问题的语法是两者之间差异的一个例子LALR(1) 和 LR(1);它可以在野牛手册(这里)中找到:
def: param_spec return_spec ',';
param_spec: type | name_list ':' type;
return_spec: type | name ':' type;
type: "id";
name: "id";
name_list: name | name ',' name_list;
(野牛手册解释了如何解决 LALR(1) 语法的这个问题,尽管使用 GLR 语法始终是可能的。)
在不使用 GLR 语法的情况下解决此类冲突的关键是避免强制解析器做出过早的决定。
例如,在语法上区分左值和右值是传统的做法,一些语言继续这样做。但是,C 和 C++ 没有;这在 C++ 中被证明是一个非常强大的特性,因为它允许定义可以充当左值的函数。
在 C 中,我认为这只是为了简化语法:C 语法允许任何一元运算符的结果出现在赋值运算符的左侧,但一元运算符实际上是左值 ( *v
, v[expr]
) 和右值的混合( sizeof v
, f(expr)
)。语法可以区分这两种一元运算符,但它无法解决实际的限制,即只有可修改的左值可能出现在赋值运算符的左侧。
C++ 允许任意表达式出现在赋值运算符的左侧(尽管有些需要用括号括起来);因此,以下内容是完全合法的:
(predicate(x) ? *some_pointer : some_variable) = 42;
在您的情况下,您可以通过替换来在语法上解决冲突te
with p
,因为两个非终结符都会产生相同的派生集。这可能不是一般的解决方案,除非在您的完整语法中确实存在左侧表达式是右侧表达式的严格子集的情况。在一个完整的语法中,你可能会得到三种类型的表达式(left-only、right-only、common),这可能会使语法变得相当复杂,而将分辨率留给语义分析可能会更容易(甚至,如在 C++ 的情况下,非常有用)。