Parsing 为什么正确的递归语法需要Yacc提供更大的解析器?

Parsing 为什么正确的递归语法需要Yacc提供更大的解析器?,parsing,compiler-construction,yacc,Parsing,Compiler Construction,Yacc,从: 使用正确的递归规则,例如 seq : item | item seq ; 解析器会稍微大一点,可以看到项目,并且 从右向左缩小。更严重的是,在 如果使用很长的序列,解析器将有溢出的危险 阅读因此,用户应该在合理的情况下使用左递归 我知道Yacc生成LR解析器,所以我尝试手动解析一些简单的右递归语法。到目前为止我还看不出这个问题。任何人都可以举一个例子来说明这些问题?解析器的大小不是一个严重的问题(大

从:

使用正确的递归规则,例如

    seq     :       item
            |       item  seq
            ;
解析器会稍微大一点,可以看到项目,并且 从右向左缩小。更严重的是,在 如果使用很长的序列,解析器将有溢出的危险 阅读因此,用户应该在合理的情况下使用左递归


我知道Yacc生成LR解析器,所以我尝试手动解析一些简单的右递归语法。到目前为止我还看不出这个问题。任何人都可以举一个例子来说明这些问题?

解析器的大小不是一个严重的问题(大多数情况下)

运行时堆栈大小可能是一个问题。问题在于,右递归规则意味着在解析器到达序列末尾之前无法减少堆栈,而使用左递归规则,每当语法遇到
seq item
,它就可以减少堆栈上的项数

传统上,令牌堆栈是固定的,并且大小有限。因此,正确的递归规则(如要处理的规则):

IF <cond> THEN
    <stmt-list>
ELSIF <cond> THEN
    <stmt-list>
ELSIF <cond> THEN
    <stmt-list>
ELSE
    <stmt-list>
ENDIF

我似乎记得很久以前(十多年前)就做过一些测试,当时我使用的Yacc与语言语法结合使用,类似于上面的,这意味着在大约300个ELIF子句之后,解析器停止了(我认为它在控制下停止了,因为它意识到空间不足,而不是在空间耗尽的情况下崩溃)。

解析器大小不是一个严重的问题(大多数情况下)

运行时堆栈大小可能是一个问题。问题在于,右递归规则意味着在解析器到达序列末尾之前无法减少堆栈,而使用左递归规则,每次语法遇到
seq item
,都会减少堆栈上的项数

传统上,令牌的堆栈是固定的,并且大小有限。因此,一个正确的递归规则,例如要处理的规则:

IF <cond> THEN
    <stmt-list>
ELSIF <cond> THEN
    <stmt-list>
ELSIF <cond> THEN
    <stmt-list>
ELSE
    <stmt-list>
ENDIF

我似乎记得很久以前(十多年前)就做过一些测试,当时我使用的Yacc与语言语法结合使用,类似于上面的,这意味着在大约300个ELIF子句之后,解析器停止了(我认为它在控制下停止了,意识到它已经耗尽了空间,而不是在空间耗尽的情况下崩溃)。

我完全不确定他为什么说正确的递归解析器会更大——一般来说,它需要在其状态机中少一个状态(如果有什么可以使它变小的话),但真正的问题是右递归语法需要无界堆栈空间,而左递归语法需要常量空间(O(n)空间vs O(1)空间)


现在O(n)和O(1)听起来可能很重要,但取决于你在做什么,它可能并不重要。特别是,如果你正在将整个输入读入内存来处理它,那么O(n)空间会完全淹没O(n)和O(1)右递归和左递归的区别。如果您使用的是仍然有固定解析器堆栈的特别旧版本的yacc,这可能是个问题,但yacc的最新版本(Berkeley yacc,bison)根据需要自动扩展解析堆栈,因此唯一的限制是可用内存。

我完全不确定他为什么说正确的递归解析器会更大——一般来说,它需要在状态机中少一个状态(如果有什么问题的话,应该会使它更小),但真正的问题是右递归语法需要无界堆栈空间,而左递归语法需要常量空间(O(n)空间vs O(1)空间)


现在O(n)和O(1)听起来可能很重要,但取决于你在做什么,它可能并不重要。特别是,如果你正在将整个输入读入内存来处理它,那么O(n)空间会完全淹没O(n)和O(1)右递归和左递归的区别。如果您使用的是仍然有固定解析器堆栈的特别旧版本的yacc,这可能是个问题,但yacc的最新版本(Berkeley yacc,bison)根据需要自动扩展解析堆栈,因此唯一的限制是可用内存。

您的示例中没有任何内容需要右递归——您只需将
opt_elif_子句_list
规则交换为左递归,而无需更改任何其他内容来解析相同的语言。更令人信服的是不带e的C风格ifsndif标记,没有特殊的elsif标记。然后自然表示是右递归的,转换为左递归是困难的。我同意语法可以更改为LR;事实上,我创建了它的LR版本,并编译了它,RR版本的源代码长了3行,但编译的二进制文件大小相同。有一个为什么在这个构造上使用RR比较方便——与动作相关,而不是语法相关——但那是大约十年前的事了,我现在已经忘记了细节。在你的例子中没有什么需要右递归的——你可以将
opt_elif_子句_list
规则换成左递归,而不需要使用cha解析同一种语言时,如果不使用endif标记和特殊的elsif标记,则更令人信服的是C风格的ifs。然后,自然表示是右递归的,转换为左递归是很困难的。我同意语法可以更改为LR;事实上,我创建了LR版本,并编译了它,以及sourcRR版本的e长了3行,但编译后的二进制文件大小相同。有一个原因可以解释为什么在这个结构上使用RR比较方便——与动作相关,而不是语法相关——但那是大约十年前的事了,我现在已经忘记了细节。