在编译器设计中,为什么应在语法中消除左递归?我正在读这是因为它可能导致无限递归,但是对于正确的递归语法来说,它也不是真的吗?
在编译器设计中,为什么应在语法中消除左递归?我正在读这是因为它可能导致无限递归,但是对于正确的递归语法来说,它也不是真的吗?
Answers:
左递归语法不一定是一件坏事。可以像使用LR解析器一样,使用堆栈轻松地解析这些语法以跟踪已解析的短语。
回想一下,CF语法的左递归规则 的形式为:
每当语法解析器(从词法分析器)接收到一个新的终端时,该终端就被推到栈顶:此操作称为shift。
每当规则的右手边被堆栈顶部的一组连续元素匹配时,此组将被代表新匹配短语的单个元素替换。这种替换称为减少。
使用正确的递归语法,堆栈可以无限期增长,直到发生归约为止,从而极大地限制了解析的可能性。但是,左递归将使编译器更早地生成缩减(实际上是尽快)。有关更多信息,请参见Wikipedia条目。
考虑以下规则:
example : 'a' | example 'b' ;
现在考虑一个LL解析器,它试图匹配'b'
与此规则不匹配的字符串。由于'a'
不匹配,它将尝试匹配example 'b'
。但是为了做到这一点,它必须匹配example
……这就是它最初试图做的。尝试永远查看是否可以匹配可能会陷入困境,因为它一直试图将相同的令牌流匹配到相同的规则。
为了防止这种情况,您要么必须从右边进行解析(就我所见,这是非常不常见的,并且将使正确的递归成为问题),人为地限制了允许的嵌套量,或者进行匹配递归开始之前需要一个令牌,因此总会有一个基本情况(即所有令牌都被消耗掉了,仍然没有完全匹配的情况)。由于右递归规则已经执行了第三条规则,因此它没有相同的问题。
(我知道这个问题现在已经很老了,但是如果其他人也有同样的问题...)
您是在递归下降解析器环境中询问吗?例如,对于语法expr:: = expr + term | term
,为什么要这样(左递归):
// expr:: = expr + term
expr() {
expr();
if (token == '+') {
getNextToken();
}
term();
}
有问题,但不是(正确的递归)吗?
// expr:: = term + expr
expr() {
term();
if (token == '+') {
getNextToken();
expr();
}
}
看起来像是两个版本的expr()
呼叫本身。但是重要的区别是上下文-即进行该递归调用时的当前令牌。
在左递归的情况下,expr()
使用相同的令牌连续调用自身,并且没有任何进展。在正确的递归情况下,term()
在到达之前,它将消耗调用中的一些输入和PLUS令牌expr()
。因此,在这一点上,递归调用可以调用term,然后在再次到达if测试之前终止。
例如,考虑解析2 + 3 +4。左递归解析器expr()
在卡在第一个标记上的同时进行无限次调用,而右递归解析器在expr()
再次调用前消耗“ 2 +” 。第二个expr()
匹配“ 3 +”的呼叫expr()
,仅剩下4的呼叫。4匹配一个术语,并且解析终止,而不再调用expr()
。
从野牛手册:
“任何类型的序列都可以使用left递归或right递归来定义,但是您应该始终使用left递归,因为它可以解析具有有限堆栈空间的任意数量的元素序列。Right递归会占用Bison堆栈中的空间。 ”,它与序列中元素的数量成比例,因为在规则一次可以应用一次之前,所有元素都必须移到堆栈上。有关此的进一步说明,请参阅Bison Parser算法。”
http://www.gnu.org/software/bison/manual/html_node/Recursion.html
因此,这取决于解析器的算法,但是如其他答案所述,某些解析器可能根本无法使用左递归