You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
function A(scanner) {
// 选择A的某个产生式,A -> X1X2...XK
for (i to k) {
if (Xi为非终结符) {
X1(scanner)
} else if (Xi为终结符 && Xi == scanner.read()) {
scanner.next()
} else {
// 发生错误
}
}
}
增广文法就是在G中加上新开始符号S'和产生式 S' -> S 而得到的文法,该文法是为了保证接收器只有一个起点。
项目集闭包用来表示句柄分析的状态,是相同的句柄分析状态的集合。举例如下:
有了项目集闭包后,我们就可以以初始状态为起点,构造LR分析表。结果如下:
构造项目集闭包的大致过程如下:
// I 为某一项目集(状态)
// 返回项目集闭包
function clousure(I) {
J = I
for (J中每一项 A -> a.Bb) {
for (文法中每个产生式 B -> xxx) {
if (b -> xxx 不在J中) {
// 将 b -> xxx 加入J中
}
}
}
return J;
}
构造后继项目集闭包(扩展整个项目集)的大致过程如下:
// I 为某一项目集(状态),X 为某一非终结符
function goto(I, X) {
// 初始化J为空集
for (I中每一项 A -> a.Xb) {
// 将 A -> aX.b 加入J
}
return clousure(J)
}
以上clousure和goto方法我们可以得到文法的所有状态的集合(项目集),方法是从clousure({ S‘ -> S })开始,循环检查项目集中所有集合goto(I, X)是否在集合中,不在则加入,直到没有新的项目集加入到集合中为止。
有了上述的项目集闭包,构建LR(0)分析表的过程是循环遍历所有项目集中的所有项目,做如下判断:
1.移入,如果项目集i中有 A -> .aB && goto(Ii, a) == Ij, ACTION[i, a] = sj
2.状态变换,如果项目集i中有 A -> .B && goto(Ii, B) == Ij, GOTO[i, a] = j
3.归约,如果项目集i中有 A -> aB.,ACTION[i, a] = rj
4.成功,如果项目集i中有 S' -> S.,ACTION[i, $] = rj
最近复习了一下编译原理,编译原理主要有以下几个阶段:
其中,语法分析、语义分析、中间代码生成三个阶段可以合为语法制导翻译。
本文将针对上述几个阶段进行简要介绍。
词法分析
语法分析器从左到右的扫描程序中的字符,识别出各个单词,并确定单词类型,输出统一的词法单元token。
我们在做词法分析器时,主要遵循以下几个步骤:
我们以
sql-parser
中的词法分析部分为例:正则及匹配函数
主流程
文法
文法用来描述语言的规则,文法G定义为一个四元组(VN,VT,P,S),其中,VN为非终结符集合,VT终结符集合;P是产生式结合;S称为识别符或开始符号,也是一个非终结符,至少要在一条产生式的左边出现。
产生式的形式是α → β,α称为产生式左部,β称为产生式右部,α属于VN,β∈(VN∪VT)*,α∉ε
上下文无关文法
文法分为上下文有关文法和上下文无关文法,故名思义,上下文无关文法就是匹配产生式时,与上下文(前后已经推倒出的结果)无关,上下文无关文法的产生式左侧只有非终结符。只要文法的定义里有某个产生式,不管一个非终结符前后的串是什么,就可以应用相应的产生式进行推导。
消除左递归
一个文法含有下列形式的产生式之一时:
则称该文法是左递归的。
左递归的产生式是无法做
自顶向下分析
语法分析的,所以需要我们消除左递归。消除直接左递归的方式是将其转换成右递归。比如产生式:P
表示的是ba{1,}
,那么我们可以将其转换成如下右递归,消除左递归有一套通用的算法,算法如下:
简单用Javascript实现了一个消除直接递归的函数:
语法分析
语法分析的目的是构造分析树,按照分析树的构造方向,可以将语法分析分成自顶向下和自底向上分析法两种,下面来分别介绍。
自顶向下
自顶向上是从分析树的顶部(根节点)向底部(叶)节点方向构造分析树。
每一步推导中,都需要做两个选择:
针对第一个选择,有最左推导和最右推到,由于我们通常都是从左到右的遍历,所以通常使用最左推导。针对第二个选择,将在下面分析法中介绍。
自顶向下的分析法,对文法有一定的要求,可能需要做文法转换,比如消除左递归,这里不再赘述。
递归下降分析
递归下降由一组过程组成,每个非终结符都对应一个分析过程。该方法从起始非终结符S开始,递归的调用其他的非终结符的对应过程。如果S对应的过程恰好扫描了整个输入串,则成功的完成了递归分析。
这里针对第二个选择,当同一个非终结符对应多个产生式时,可以使用错误回溯或预测分析的方法。回溯的方法会挨个尝试非终结符的产生式,如果后面的解析发生错误,则尝试下一个,这种方法称之为回溯;预测分析通过向前看输入流的k个字符,决定应用的产生式,也就是LL(k)分析法。
预测分析法在每一步推导中根据当前句型的最左非终结符A和当前输入符号a,选择一个正确的A的产生式。对于预测分析法,需要计算非终结符的First集和Follow集,通过这两个集合,可以计算产生式的Select集(eg.SELECT(A -> aB))来帮助预测分析,通过每个产生式的SELECT集就可以构造预测分析表,预测最细最终就是通过预测分析表来决定选用哪个产生式的。预测分析表的例子如下:
基于回溯的递归下降分析法,每一个非终结符的助理过程大致如下:
递归的预测分析法通过预测分析表,决定调用哪个过程。我们在这里假设非终结符A对应两个表达式,分别的SELECT集为{:}、{,}大致过程如下:
非递归预测分析
非递归的预测分析又叫做表驱动的预测分析,结构如下:
主要由预测分析表、扫描器和一个栈组成。原理与树的深度优先遍历类似,将匹配的产生式入栈,当栈顶与当前的输入符号相同时,栈顶出栈,输入符号向后移一位。
算法的大致流程如下:
自底向上
自顶向上是从分析树的底部(叶节点)向顶部(根节点)方向构造分析树。
移入-归约分析
自底向上的分析通用框架是移入-归约分析。
移入-归约分析的过程如下:
这里有个选择是当可以归约时,是继续移入还是直接归约,确定移入还是归约需要向前查看k个输入符号来决定,这就是LR(k)分析。
由于合适的产生式(句柄)是逐步形成的,所以句柄识别情况是有“状态“的,LR分析法用以下方式描述状态:
LR分析器的结构如下图所示:
与LR分析器结构不同在于多了一个状态栈,用来描述当前的句柄状态;同时有动作转移表用来描述在某一状态下,遇到某一终结符或非终结符时的动作,该动作有可能是移入、归约、状态变化或成功。
LR分析表的结构如下图所示:
LR分析算法大致流程如下:
LR(0)分析法是就是不参考后续的输入字符,直接归约的分析法,LR(0)分析法使用的条件是不出现移进-归约和归约-归约冲突,也就是同一状态遇到相同输入时只有一种可选动作,没有歧义。虽然应用面比较小,但我们可以通过它来看LR分析表是如何构造的。
讲解LR分析表构造之前,先讲两个概念
增广文法
和项目集闭包
。增广文法
就是在G中加上新开始符号S'和产生式S' -> S
而得到的文法,该文法是为了保证接收器只有一个起点。项目集闭包
用来表示句柄分析的状态,是相同的句柄分析状态的集合。举例如下:有了项目集闭包后,我们就可以以初始状态为起点,构造LR分析表。结果如下:
构造项目集闭包的大致过程如下:
构造后继项目集闭包(扩展整个项目集)的大致过程如下:
以上clousure和goto方法我们可以得到文法的所有状态的集合(项目集),方法是从
clousure({ S‘ -> S })
开始,循环检查项目集中所有集合goto(I, X)
是否在集合中,不在则加入,直到没有新的项目集加入到集合中为止。有了上述的项目集闭包,构建LR(0)分析表的过程是循环遍历所有项目集中的所有项目,做如下判断:
LR(0)没有考虑分析的上下文环境,有时会出现冲突(移进-归约和归约-归约冲突),简单来说就是选择用哪个产生式归约,是移入还是归约。解决这个问题需要知道句柄归约的条件,需要向前看输入字符了,那么就引出了SLR、LR(1)分析法。
SLR分析法借助FOLLOW集来解决冲突,当然这就决定了冲突相关的非终结符的FOLLOW集不能存在交集,器对上述第三个过程进行了改造,设下一个许茹字符为x, 将
归约,如果项目集i中有 A -> aB.,ACTION[i, a] = rj
改为归约,如果项目集i中有 A -> aB. && x属于FOLLOW(A),ACTION[i, a] = rj
。在某些情况下,仅仅根据FOLLOW集来解决冲突是不够的,在特定位置,A的后继字符应该是A的FOLLOW集的子集,FOLLOW集可以帮助我们排出错误选项,但无法具体得知真正遇到哪个后继符号时执行归约,这就引出了LR(1)。
LR(1)分析法的关键是得到项目集中每个项目的展望符,也就是后继终结符,当下一个输入字符正好与展望符相同时,说明可是对该项目执行归约操作。展望符是该项目的后继符号,如果存在项目
<A -> a.BX, a>
, 其中a是A -> a.Bx的展望符,还有项目B -> .b
,那么项目B -> .b
的展望符等于first(Xa)
。语法制导翻译
语义分析不设计什么算法,只是分析文法对应的动作,完全可以嵌入在语法分析的算法中,其中语法分析、语义分析、中间代码生成可以合为语法制导翻译。
语法制导翻译为文法中每个定义一个属性,属性分为综合属性和继承属性,综合属性依赖于子节点,继承属性依赖于父节点或兄弟节点。SDT(语法制导方案)的文法如下:
语法分析过程中,计算综合属性可以在归约时计算,继承属性则在继承符号出现前执行,同时,在计算属性过程中还可以执行附加动作(比如注册符号表)。
语法制导翻译简单来说就是在语法分析过程中计算非终结符的属性、执行附加动作。
其中自顶向下的分析中,递归的方法比较简单,即每个非终结符处理函数多加一个继承属性的参数,在函数里面依照制导方案执行相应动作即可;非递归的方式则需要对符号栈进行扩展,加入属性栈,并且非终结符应在栈中具有两项(比如F和F.sync),其中F.sync代表F的综合属性,需要其子节点都计算完成后才能计算,F出栈时,F.sync会暂时留在栈中,知道计算完成后出栈。
要想在自底向上的语法分析中加入动作,首先需要替换表达式中间的语义动作,使所有语义动作位于产生式末尾。如下所示:
同时自底向上的语法分析中加入动作也需要扩展符号栈,加入属性栈。
总结
本文简要梳理了词法分析、语法分析、语法制导翻译的过程,可以看出其中关键的就在于自顶向下、自底向上的语法分析,而非递归的语法分析都是借助栈来完成的。
The text was updated successfully, but these errors were encountered: