1 回答

TA贡献1797条经验 获得超4个赞
这是一个更简单的例子;希望你能看到这个模式。
关键的见解是当生产的匹配被完全解析时执行缩减操作(即解析器函数)。这意味着如果一个产生式包含非终结符,则这些非终结符的动作在整个产生式的动作之前执行。
应该清楚为什么这是真的。每个生产动作取决于所有组件的语义值,在非终结符的情况下,这些值是通过运行相应的动作产生的。
现在,考虑这两种非常相似的解析 a list
of thing
s 的方法。在这两种情况下,我们都假设有一个基础产生式,它识别出一个空的list
( list :
) 并且什么都不做。
右递归:
list : thing list
左递归:
list : list thing
在这两种情况下,操作都会打印thing
,这p[0]
在右递归的情况下,p[1]
在左递归的情况下。
右递归产生将导致thing
s 以相反的顺序打印,因为thing
直到内部list
被解析(并且它的组件被打印)之后才会打印 s 。
但thing
出于同样的原因,左递归产生式将按从左到右的顺序打印s。区别在于左递归情况下的 tgat,内部(递归)list
包含初始thing
s,而在右递归情况下,则list
包含最终thing
s。
如果您只是构建thing
s的 Python 列表,这可能无关紧要,因为执行顺序并不重要。它仅在此示例中可见,因为该操作具有副作用(打印值),这使得执行顺序可见。
在极少数情况下确实有必要时,还有其他技术可以对操作进行排序。但最佳实践是在语法上可行时始终使用左递归。左递归解析器效率更高,因为解析器不需要累积一堆不完整的产生式。并且左递归通常也更适合您的操作。
例如,在这里,左递归操作可以附加新值 ( p[0].append(p[1]); return p[0]
),而右递归操作需要创建一个新列表 ( return [p[0] + p[1]
)。由于重复追加是平均线性时间,而重复串联是二次的,左递归解析器对于大列表更具可扩展性。
添加回答
举报