[英]How to simplify this left-recursive rule?
我正在尝试以以下形式简化具有左递归的规则:
A → Aα | β
----------
A → βA'
A' → αA' | ε
我的规则是:
selectStmt: selectStmt (setOp selectStmt) | simpleSelectStmt
根据我对公式的理解,这就是我的变量:
A = selectStmt
α = setOp selectStmt
β = simpleSelectStmt
A'= selectStmt' // for readability
然后,从应用规则我们得到:
1. A → βA'
selectStmt → simpleSelectStmt selectStmt'
2. A' → αA' | ε
selectStmt' -> setOp selectStmt selectStmt' | ε
但是然后我如何进一步简化它以获得最终的生产? 在我上一个问题的评论中删除这种左递归方式来定义 SELECT 语句,它指出:
在我们的例子中,一个直接的应用程序将把我们从我们最初的应用中带走:
selectStmt: selectStmt (setOp selectStmt) | simpleSelectStmt
至selectStmt: simpleSelectStmt selectStmt'
和selectStmt': (setOp selectStmt) | empty
这简化为selectStmt: simpleSelectStmt (setOp selectStmt)?
我不明白这种简化是如何工作的。 具体来说:
selectStmt' -> setOp selectStmt selectStmt' | ε
selectStmt' -> setOp selectStmt selectStmt' | ε
简化为selectStmt': (setOp selectStmt) | empty
selectStmt': (setOp selectStmt) | empty
? ε
是如何在这里去除的? 我假设(setOp selectStmt) | empty
(setOp selectStmt) | empty
简化为(setOp selectStmt)?
因为如果它可以是空的,那么它只是意味着可选的?
.
你的出发点:
# I removed the parentheses from the following
selectStmt: selectStmt setOp selectStmt | simpleSelectStmt
是模棱两可的。 左递归消除不能解决歧义; 相反,它保留了歧义。 因此,首先解决歧义并不是一个坏主意。
现实世界的解析器生成器可以通过使用运算符优先规则来解决这种歧义。 一些解析器生成器要求您写出优先规则,但 Antlr 更喜欢使用一组默认的优先规则(使用语法中产生式的顺序,并假设每个运算符都是左关联的,除非另有声明)。 (我提到 Antlr 是因为您似乎将它用作参考实现,尽管它的生产语义有点古怪;隐式优先规则只是其中之一。)
将运算符优先级转换为精确的 BNF 是一项艰巨的工作。 解析器生成器倾向于通过消除某些产生式来实现运算符优先级,无论是在语法编译时(yacc/bison)还是使用运行时谓词(Antlr4 和大多数基于分流场算法的生成器)。 然而,由于运算符优先级不影响上下文无关属性,我们知道存在一个上下文无关文法,其中歧义已得到解决。 在某些情况下,比如这个,很容易找到。
这基本上与您在算术表达式中发现的歧义相同。 如果没有某种优先级解析, 1+2+3+4
在语法上是模棱两可的,有五种不同的解析树。 ( (1+(2+(3+4)))
, (1+((2+3)+4))
, ((1+2)+(3+4))
, ((1+(2+3))+4)
, (((1+2)+3)+4)
)。 碰巧,这些在语义上是相同的,因为加法是关联的(在数学意义上)。 但是对于其他运算符,例如-
或/
,不同的解析会导致不同的语义。 (如果使用非关联的浮点运算,语义也会有所不同。)
因此,与您的语法一样,代数语法开始:
expr: expr '+' expr
expr: expr '*' expr
模棱两可; 它恰恰导致了上述歧义。 解决方案是说+
和大多数其他代数运算符都是左结合的。 这导致对语法的调整:
expr: expr '+' term | term
term: term '*' factor | factor
...
这不是模棱两可的(但仍然是递归的)。
请注意,如果我们选择使这些运算符具有右关联性,从而产生解析(1+(2+(3+4)))
,那么明确的语法将是右递归的:
expr: term '+' expr | term
term: factor '*' term | factor
...
由于这些特定的运算符是关联的,因此我们选择哪种语法绑定并不重要(只要*
绑定比+
更紧密),我们可以完全绕过左递归消除,只要这些是我们关心的唯一运算符关于。 但是,如上所述,有很多运算符的语义不太方便。
值得停下来了解一下为什么明确的语法是明确的。 它应该不难理解,它是上下文无关语法的一个重要方面。
以生产expr: expr '+' term
为例。 请注意, term
不会派生2 + 3
; term
只允许乘法运算符。 所以1 + 2 + 3
只能通过将1 + 2
减少到expr
并将3
减少到term
来产生,留下expr '+' term
,它与expr
的产生相匹配。 因此, ((1+2)+3)
是唯一可能的解析。 (1+(2+3))
只能用显式括号编写。
现在,很容易对expr: expr '+' term
或selectStmt: selectStmt setOp simpleSelectStmt | simpleSelectStmt
selectStmt: selectStmt setOp simpleSelectStmt | simpleSelectStmt
,返回手头的问题。 我们完全按照您的指示进行,除了 α 是setOp simpleSelectStmt
。 然后我们得到:
selectStmt: simpleSelectStmt selectStmt'
selectStmt': setOp simpleSelectStmt selectStmt'
| ε
通过将selectStmt
反代入selectStmt'
的第一个产生式,我们得到
selectStmt: simpleSelectStmt selectStmt'
selectStmt': setOp selectStmt
| ε
这很酷; 它不是模棱两可的,不是左递归的,也没有 LL(1) 冲突。 但它不会产生与原始文件相同的解析树。 事实上,解析树相当奇特: S1 UNION S2 UNION S3
被解析为(S1 (UNION S2 (UNION S3 ())))
。
有趣的是,如果我们使用右关联语法selectStmt: simpleSelectStmt setOp selectStmt | simpleSelectStmt
selectStmt: simpleSelectStmt setOp selectStmt | simpleSelectStmt
。 该语法是明确的并且不是左递归的,但它不是 LL(1),因为这两种选择都以simpleSelectStmt
开头。 所以我们需要左因子,把它变成selectStmt: simpleSelectStmt (setop selectStmt | ε)
,与我们从左递归起点得到的语法完全相同。
但是左递归和右递归语法确实不同:其中一个解析为((S1 UNION S2) UNION S3)
,另一个解析为(S1 UNION (S2 UNION S3))
。 使用UNION
,我们有幸不关心,但例如,使用SET DIFFERENCE
运算符就不会出现这种情况。
所以要点:左递归消除消除了左右关联运算符之间的差异,并且必须使用一些非语法特征(例如 Antlr 的运行时语义)来恢复这种差异。 另一方面,像 Yacc/Bison 这样的自底向上解析器不需要左递归消除,可以在不需要任何额外机制的情况下实现任一解析。
无论如何,让我们 go 回到
selectStmt: simpleSelectStmt selectStmt'
selectStmt': setOp simpleSelectStmt selectStmt'
| ε
应该清楚selectStmt'
表示 setOp setOp simpleSelectStmt
的零次或多次重复。 (试着用一张纸,依次推导出更长的句子,以说服自己这是真的。)
因此,如果我们有一个实现了 Kleene *
运算符(零次或多次重复)的解析器生成器,我们可以将selectStmt'
写为(setOp simpleSelectStmt)*
,生成最终语法
selectStmt: simpleSelectStmt (setOp simpleSelectStmt)*
这不再是 BNF——BNF 没有分组、可选性或重复运算符——但实际上它更容易阅读,如果你使用 Antlr 或类似的解析器生成器,你将不可避免地编写它。 (尽管如此,它仍然没有表明setOp
是绑定到左边还是右边。所以便利确实是以很小的代价来的。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.