【Compiler】-5 自上而下的语法分析
自上而下的语法分析
1、语法分析的地位
- 是编译程序的核心部分
2、语法分析的任务
- 识别由词法分析得出的单词序列是否是给定文法的句子。
3、语法分析的理论基础
- 上下文无关文法和下推自动机
4、语法分析的方式
-
自上而下语法分析:从开始符号->符号串。反复使用不同产生式进行推导以谋求与输入符号串相匹配
-
自下而上语法分析:从符号串->开始符号。对输入符号串,寻找不同产生式进行归约直到文法开始符号。
下推自动机
相对于一个自动机,多出了个下推栈
定义如下
- H下推栈内字母表
- z0是一个标志,表示栈到底了(栈空状态)
- z是栈顶元素
- q0是初始状态
- q是状态
举例
输入:在q状态下,如果栈顶元素为z,输入符号(读头的符号)为a
使用这个函数
输出:将q变成不同状态q’,并且让栈顶元素z变成r1,r2,r3……
因为这个PDA是不确定的PDA
基本构成
-
将栈顶元素和读头进行比较,如果相同,就top–,读头++;
-
如果不相同,就从语法表中找到这个非终结符的产生式,用产生式替换非终结符(栈顶元素)位置,再取栈顶符号
-
当栈中的开始符号“#”和读头里面的“#”相遇时,整个过程就完成了
- 但这种回溯方法,计算机每次需要保留状态,浪费一定空间和时间。
- 并且,如果有P->Pa这种文法,如果使用上面的分析法,会无限循环下去
面临上面的两个问题,需要改进成“不带回溯的自上而下算法”
不带回溯的自上而下算法
在讲不带回溯的自上而下算法前,先学习“消除左递归”
消除左递归
左递归定义
消除左递归
- 消除直接左递归
- 消除间接左递归
消除直接左递归
举例
通用性算法
举例1
结果
举例2
如果非终结符在右式子有多个,也要分清楚
消除左递归(通用法)
通用算法,对直接或间接左递归都适用
算法
这个算法不适合于包含P->Px的产生式,或包含空串的产生式;如果对P->P的产生式,用上面的方式
举例
对每个非终结符,如果右边的非终结符的号大于自己的号,则不处理
如果小于自己的号,则把右边的非终结符替换成右边的非终结符的候选式。比如:Q->Rb|b,R小于Q的号,用R->Sa|a的Sa|a
替换掉R
====>Q->(Sa|a)b|b
当然,刚开始的非终结符顺序可以不同
举例2
题目:
- E->ET+|T
- T->TF*|F
- F->E|i
解:
使用消除直接左递归,代替法。不是排序法
E->TE’
E’->T+E’|εT->FT’
T’->F*T’|εF->把处理后的E、T代入
F->FT’E’|i
再对F消除左递归
F->iF’
F’->T’E’F’|ε
消除回溯
产生回溯的原因
进行推导时,若产生式存在多个候选式,选择哪个候选式进行推导存在不确定性。
消除回溯的基本原则
对文法的任何非终结符,若能根据当前读头下的符号,准确的选择一个候选式进行推导,那么回溯就可以消除。
两种消除回溯的方法
- 预测
- 提左因子
预测法
根据读头下符号选择候选式,使其第一个符号与读头下符号相同,或该候选式可推导出的第一个符号与读头下符号相同。这相当于向前看了一个符号,所以称为预测。
求首符集
Frist()集
求候选式的终结首符集
举例
提取公共左因子
注:
- 1)通过反复提取左因子,就能把所有非终结符的所有候选首符集变为两两不相交。
- 2)反复提取左因子也有一定代价,因为在提取过程中会大量引入非终结符和s产生式,增加语法分析的复杂性。
例子
题目:
- A -> id |
B
_B - B -> S I |id
解:A->id | id_B | S I _B【将A中第一个B替换成对应的产生式,可以发现有公共左因子】
所以
- A ->
id
A’|S I _B - A’-> ε | _B
预测分析程序
求随符集
求随符集的原因
- 当栈顶为F,读头为a时,但
F->bc|ε
,正确应该用F后面的E来替换 - 所以应该存在一个方法,能求出Follow(F)是不是a
Follow()集
算法
对于非终结符A,求A的Follow(A)
- 1)对文法开始符号S,将开始符号入栈之前,肯定有个#号已经在栈中,所以将‘#’加入到Follow(S)中;
- 2)若
B→αAβ
是文法G的一个产生式,则将First(β)-ε
(减去空串ε)加入到Follow(A)中; - 3)若
B→αA
是文法G的一个产生式,或B→αAβ
是文法G的一个产生式,且β->ε(β经过多步推出空串ε),则将Follow(B)加入到Follow(A)中;
注意
- 这里的方法必须是消除了左递归并且提取了左因子后的文法
举例
求Follow(E)
- 先求Follow(E),因为E是开始符号,所以
#
一定在,则得到# - 再看E出现在了F->
(E)
的产生式,所以还要并
上First()
)(这里的)
)已经是终结符了,所以没必要再求First,如果是非终结符(假如是X),需要再求First(X)。 - 所以Follow(E)={
#,)
}
求Follow(E’)
- E’出现在E->
TE'
的产生式中,符合上面算法第三条B→αA
,所以Follow(E’)=Follow(E)={#,)
} - E’出现在E’->+TE’,也符合上面
算法第三条B→αA
,Follow(E’)=Follow(E’)就是自身……这步没必要
求Follow(T)重要!!
- T出现在E’->
+TE'
,符合上面算法第二条B→αAβ
(应该求出First(β)-ε
再加入到A中),所以Follow(T)=First(E’) - 因为First(E’)={+,ε},但要
把ε减去
,所以Follow(T)={+}
算法中式子 | 对应实际式子 |
---|---|
B | E’ |
α | + |
A | T |
β | E’ |
- T出现在E->
TE'
,由上面知,First(E’)会产生空串,用ε把E->TE'
的E’代替,变成E->T
,那跟在E后面的其实也跟在T后面,所以Follow(T)={+} U Follow(E)={+,#,)}
,这步很重要,因为E’可能会被空串代替,就需要变化到算法第三条B→αA
在
算法第二条
中,如果发现β会产生空串,就像上面的First(E’)={+,ε},就要变化成算法第三条
结果
上面求的首符集、随符集都是为了构造预测分析表,下面来看
构造预测分析表
基本思想
- 1)若
A->α
是一个产生式,a属于First(α)
,说明α可能以a开头。那么当A是栈顶元素且将读入字符a时,选择α
取代A
,这样匹配成功的希望最大。 - 故:M[A,a]元素为
A→α
,M为矩阵
若α会产生空串,或着A->ε,就是上面"求随符集"的原因举例。这时,需要
判断a是否属于Follow(A)
,如果a属于Follow(A),说明A就应该被ε替代,让Follow(A)来和a匹配。
算法
构造预测分析表
根据First和Follow构造
上面的分析表中,因为First(E’)={
+,ε
}和First(T’)={*,ε
}出现了ε,所以需要考虑他们为ε的情况,也就是在当某个字符在E’或T’的Follow()集中,就需要将E’或T’填写成ε
比如:First(E’)={+,ε},所以在+
号下面填写E'->+TE'
。又Frist(E’) = 包含ε,所以在Follow(E')
={ ) , #
},在")"
和"#"
处填写E'->ε
对T’也是同理
小结:
在Follow(X)集出现的位置下,填写 X->ε
在First(X)集出现的位置下,填写 X->……
预测分析表的使用
如,有得到以下的预测分析表
求式子i+i#
过程:
初始 | 栈内(向右是栈顶) | 读头 | 操作 | |
---|---|---|---|---|
1 | #E | i+i# | (E,i)=TE’ | TE’倒序入栈 |
2 | #E'T |
i+i# | (T,i)=FT’ | FT’倒序入栈 |
3 | #E’T'F |
i+i# | (F,i)=i | 匹配 |
4 | #E’T’ | +i# | (T’,+)=ε | ε入栈 |
5 | #E’ | +i# | (E’,+)=+E | +E倒序入栈 |
6 | #E+ | +i# | (+,+) | 匹配 |
7 | #E | i# | (E,i)=TE’ | TE’倒序入栈 |
…… | ………… | |||
LL(1)文法
定义
若文法G的预测分析表M中**不含有多重定义项,**则称G为LL(1)文法。每一项只有一个候选式
注:
- 1)LL(1)文法是无二义的,二义文法一定不是LL(1)文法。
- 2)LL的含义是从左到右扫描输入串,采用最左推导分析句子。
- 3)数字1表示分析句子时需向前看一个输入符号。
判断定理
文法G是LL(1)文法当且仅当,对于G的每个非终结符A的任何两个不同产生式A→α|β有:
- 1)First(α) ∩ First(β)=空
- 2)若ε属于First(β),则First(α) ∩ Follow(A)= 空
只考虑会产生两个及两个以上候选式的非终结符,并且,需要同时满足上面的两个条件,对于第二个条件,
如果ε属于First(β)
才需要判断,否则只要求判断条件1。
解释:如果ε属于First(β),就是说非终结符A会产生空串,那需要将Follow(A)和First(α)应该是和除了空串的其它所有候选式进行判断。
如果没有交集,说明可以根据First(α)和Follow(A)对读头的字符进行划分。
如果有交集,说明读头的字符可能会归属到First(α),也可以归属到Follow(A),就有了冲突
注:
- 1)可以使用这个定理直接根据首符集、随符集来判断文法是否是LL(1)。
- 2)但判断之前,必须消除左递归和提取公共左因子,因为包含左递归和公共左因子的文法**肯定不是LL(1)**文法。
- LL(1)文法只是上下文无关文法的一个子集。
举例
求LL(1)的三个步骤:
- 消除左递归
- 提取公共因子
- 求首符集和随符集
1、消除左递归
这里没有左递归
2、提取公共左因子
3、求首符集和随符集
判断定理
根据判断定理第二条
:S'->else S|ε
中,因为ε属于First(β)
,First(else S)与Follow(S’)交集非空,所以不是LL(1)文法
举例2
求出First与Follow
判断是否为LL(1)文法
状态表
不考……跳过
为了节省存储空间并提高算法的效率,可以对预测分析表进行简化。不需把整个产生式放在分析表的各项中,只需要将右部候选式倒序存放其中,甚至在分析表中只保存产生式编号,此生式另存在一个语法表中
递归下降分析法
定义
若一个文法G不含有左递归,而且每个非终结符的所有候选式的首符集都是两两不相交的(和LL1一样的要求),那么就能为G中每个非终结符编写一个相应的递归过程。把该文法中所有这样的递归过程组合起来就可能构成一个不带回溯的自上而下分析程序——递归下降分析程序。
实现思想
为文法中每个非终结符编写一个递归过程,每个过程的功能是识别由该非终结符推出的串,当某非终结符的产生式有多个候选式时,按LL(1)形式唯一确定选择哪个候选式进行推导,若遇到某候选式为s,认为其自动匹配。把这些递归过程组合起来就构成了文法的递归下降分析程序。
实现
1、使用LL(1)文法
先将文法消除左递归、提取公共左因子,使之成为LL(1)文法,之后将每个非终结符对应一个递归过程,过程体是按照相应产生式的右部符号串顺序编写。每匹配一个终结符,则再读入下一个符号,对产生式右部的每个非终结符,则调用相应的过程。
2、使用BNF范式
先将文法改写为BNF形式,后再书写递归子程序。
递归下降分析法的缺点
- 对文法的要求高,必须满足LL(1)文法。
- 高深度的递归调用会影响语法分析的效率,速度慢占空间多。