Parsing 左/右递归、最左/最右派生、优先级、关联性等之间的差异
我目前正在学习语言处理器,经常提到的一个话题是语法元素的使用方向。从左到右或从右到左。 我理解这个概念,但似乎有很多方法来编写这些规则,我不确定它们是否都是一样的。到目前为止,我看到的是: 右/左递归, 最右边/最左边的派生, 右/左缩减、优先级、关联性等Parsing 左/右递归、最左/最右派生、优先级、关联性等之间的差异,parsing,compiler-construction,grammar,Parsing,Compiler Construction,Grammar,我目前正在学习语言处理器,经常提到的一个话题是语法元素的使用方向。从左到右或从右到左。 我理解这个概念,但似乎有很多方法来编写这些规则,我不确定它们是否都是一样的。到目前为止,我看到的是: 右/左递归, 最右边/最左边的派生, 右/左缩减、优先级、关联性等 这些都是同一个意思吗?不,它们都有不同的含义 右递归和左递归是指生产规则中的递归。一个非终端的产品是递归的,如果它能派生出一个包含该非终端的序列;如果非端点可以出现在派生序列的开始(左边缘),则为左递归;如果非端点可以出现在结束(右边缘),则
这些都是同一个意思吗?不,它们都有不同的含义 右递归和左递归是指生产规则中的递归。一个非终端的产品是递归的,如果它能派生出一个包含该非终端的序列;如果非端点可以出现在派生序列的开始(左边缘),则为左递归;如果非端点可以出现在结束(右边缘),则为右递归。产品可以是递归的,而不是左递归或右递归,甚至可以是左递归和右递归 例如:
term: term '*' factor { /* left-recursive */ }
assignment: lval '=' assignment { /* right-recursive */ }
上面的例子都是直接递归;非终端直接派生包含非终端的序列。递归也可以是间接的;它仍然是递归
所有常见的解析算法都从左到右处理,这是LL和LR中的第一个L。自顶向下(LL)解析找到最左边的派生(第二个L),而自底向上(LR)解析找到最右边的派生(R)
实际上,这两种类型的解析器都是从一个非终结符(起始符号)开始,并根据当前序列中的某个非终结符“猜测”派生,直到派生出输入文本为止。在最左边的派生中,它始终是展开的最左边的非终结符。在最右边的派生中,它始终是最右边的非终结符
因此自顶向下的解析器总是猜测第一个非终端使用哪个产品,之后它需要再次处理现在是第一个非终端的任何产品。(“Guess”在这里是非正式的。它可以查看要匹配的输入——或者至少是输入的下一个k标记——以确定要使用哪个产品。)这称为自顶向下处理,因为它自上而下构建解析树
更容易(至少对我来说)将自底向上解析器的操作反向可视化;它通过反复读取刚好足够的输入来找到一些产品,这将是派生链中的最后一个派生,从而自下而上构建解析树。所以它确实产生了一个最右边的派生,但是它将它输出到前面
在运算符语言的LR语法中(粗略地说,是类似于算术表达式的语言的语法),左关联性和右关联性分别使用左递归语法规则和右递归语法规则建模。“结合性”和“优先性”一样,是对语法的非正式描述
通过使用一系列语法规则对优先级进行建模,每个规则都引用下一个规则(通常以处理括号的递归结果--'(“expr')”
--既不是左递归的,也不是右递归的)
有一种较旧的自底向上的解析方式,称为“运算符优先解析”,其中优先权是语言描述的明确部分。一种常见的操作员优先算法是所谓的调车场算法。但是,如果你有一个LALR(1)解析器生成器,比如bison,你也可以使用它,因为它更通用、更精确。(我不是解析器和编译器理论方面的专家。我碰巧在学习一些相关的东西。我想与大家分享我迄今为止发现的一些东西。)
我强烈建议你看看这个
它解释并演示了LL和LR算法。您可以清楚地看到为什么LL被称为自顶向下,LR被称为自下而上
一些引文:
LL和LR解析器操作方式的主要区别在于
LL解析器输出解析树的预顺序遍历和LR
解析器输出后序遍历
我们正在一个非常简单的模型上讨论LL和LR解析器的工作原理
操作。两者都读取输入令牌流并输出相同的令牌
流,在适当的位置插入规则以实现
解析树的预排序(LL)或后排序(LR)遍历
当您看到诸如LL(1)、LR(0)等名称时,在
括号是前瞻标记的数量
至于首字母缩略词:()
LR和LL中的第一个L表示:解析器读取输入
一个方向的文本,无需备份;这个方向是典型的
在每一行中从左到右,在每一行中从上到下
完整的输入文件
剩下的R和L分别表示:最右边和最左边的派生
这是两种不同的解析策略。解析策略确定下一个要重写的非终端。()
- 对于最左边的派生,它始终是最左边的非终结符
- 对于最右边的派生,它始终是最右边的非终结符