左/右递归和Bison解析堆栈行为

时间:2018-02-04 04:06:03

标签: recursion bison flex-lexer yacc lex

所以在我的< 24小时的野牛/弹性调查我已经看到很多文档表明左递归比右递归更好。有些地方甚至提到左递归,你需要在Bison解析器堆栈上有恒定的空间,而右递归需要N阶空间。但是,我找不到任何能明确说明情况的来源。

作为示例(仅添加和减少的解析器):

扫描仪:

%%
[0-9]+ {return NUMBER;}
%%

分析器:

%%
/* Left */
expression:
    NUMBER
  | expression '+' NUMBER { $$ = $1 + $3; }
  | expression '-' NUMBER { $$ = $1 - $3; }
  ;
/* Right */
expression:
    NUMBER
  | NUMBER '+' expression { $$ = $1 + $3; }
  | NUMBER '-' expression { $$ = $1 - $3; }
  ;
%%

对于1 + 5-2的示例,似乎左递归,解析器从词法分析器接收'1'并看到'1'匹配expression: NUMBER并将值1的表达式推送到解析器堆。它看到+并推动。然后它看到5并且表达式(1),+和5匹配expression: expression '+' NUMBER因此它弹出两次,进行数学运算并在堆栈上推送值为6的新表达式,然后重复进行减法。在任何一点,堆栈上最多有3个符号。所以它就像一个就地计算,从左到右操作。

使用正确的递归,我不确定为什么必须在堆栈上加载所有符号,但我将尝试描述为什么会出现这种情况。它看到1并匹配expression: NUMBER,因此它在堆栈上推送值为1的表达式。它在堆栈上推送'+'。当它看到5时,我的第一个想法是它自己的5可以匹配expression: NUMBER因此是值5的表达式然后它加上堆栈中的最后两个符号可以匹配expression: NUMBER '+' expression但是我的假设因为expression在规则的右边,它不能跳枪并将5作为表达式评估为NUMBER,因为使用LALR(1),它已经知道更多的符号即将到来,所以它必须等到它到达列表的末尾?

TL; DR;

有人可能会详细解释Bison如何管理其解析堆栈相对于解析器语法规则如何进行移位/缩减?欢迎愚蠢/做作的例子!

2 个答案:

答案 0 :(得分:4)

使用LR(自下而上)解析时,每个非终端在遇到其最后一个令牌时都会精确缩小。 (LALR解析是一个简化的LR解析,它稍微不那么精确地处理前瞻。)在非终端减少之前,它的所有组件都存在于堆栈中。因此,如果您使用正确的递归并且正在解析

NUMBER + NUMBER + NUMBER + NUMBER

在您结束之前,缩减不会开始,因为每个NUMBER都会expression开始,所有表达式都会在最后NUMBER结束。

如果您使用左递归,则每个NUMBER都会终止expression,因此每次遇到NUMBER时都会发生缩减。

这不是使用左递归的原因。您使用左递归,因为它准确地描述了语言。如果你有7 - 2 - 1,你希望结果为4,因为那是代数规则所要求的:表达式被解析为(7 - 2) - 1,所以必须首先减少7 - 2。使用右递归,你会错误地将其推算为6,因为2 - 1会先减少。

大多数操作符关联到左侧,因此您使用左递归。对于偶然关联到右侧的操作员,您需要正确的递归,并且您必须忍受堆栈的增长。没什么大不了的。你的机器有大量的内存。

例如,考虑分配。 a = b = 42表示a = (b = 42)。如果您以关联方式执行此操作,则首先将a设置为b,然后尝试将某些内容设置为42; (a = b) = 42在大多数语言中没有意义,肯定不是预期的行动。

LL(自上而下)解析使用预测预测将减少哪个生产。它根本无法处理左递归,因为预测最终会以递归循环结束:expressionexpression开头,以expression开头......并且解析器永远无法预测一个NUMBER。因此,对于LL解析器,您必须使用右递归,然后您的语法不能正确描述语言(假设语言具有左关联运算符,通常就是这种情况)。有些人不介意这一点,但我认为语法实际上应该表明正确的解析,并且我发现必须进行修改以使用自上而下的解析器解析语法,使其变得混乱且难以阅读。您的里程可能会有所不同。

顺便说一句,“强迫你的喉咙”是一个非常简单的文档描述,试图给你很好的建议。怀疑是好的 - 如果你努力弄清楚他们为什么按照他们的方式工作,你会更好地理解事情 - 但很多人只是想要好的建议。

答案 1 :(得分:0)

所以在阅读了野牛文档中这个相当重要的页面之后:

https://www.gnu.org/software/bison/manual/html_node/Lookahead.html#Lookahead

一起运行
%debug

yydebug = 1;

main()

我想我确切地知道发生了什么。

使用左递归,它会看到1并将其移位。前瞻现在是+。然后它确定它可以通过expression将1减少到expression: NUMBER。因此它弹出并在堆栈上放置expression。它移动+和NUMBER(5),然后看到它可以通过expression: expression '+' NUMBER减少并弹出3x并推送一个新的表达式(6)。基本上,通过使用前瞻和规则,野牛可以在读取令牌时随时确定是否需要移位或减少。重复一遍,解析堆栈上最多有3个符号/分组(对于这个简化的表达式评估部分)。

使用正确的递归,它会看到1并将其移位。前瞻现在是+。解析器没有理由将1减少到表达式(1),因为没有规则进入expression '+'所以它只是继续移动每个标记,直到它到达输入的末尾。此时,堆栈上有[NUMBER,+,NUMBER, - ,NUMBER],因此它可以看到最近的数字可以缩减为表达式(2),然后将其移动。然后规则开始应用(`expression:NUMBER' - 'expression')等等。

因此,我理解的关键是,Bison使用前瞻标记做出有关现在减少或根据其可以使用的规则进行转移的明智决策。