编译原理学习笔记——第六讲 语法分析:自底向上分析
- 1. 自底向上分析
-
- 2. 短语与直接短语
- 3. 算符优先分析方法
- 4. 构造优先关系表
-
- 4.1 FIRSTVT和LASTVT集合
- 4.2 FIRSTVT和LASTVT集合的计算
-
- 4.2.1 构造集合FIRSTVT( P )的算法
- 4.2.2 构造集合LASTVT(P)的算法
- 4.2.3 示例
- 4.3 构造优先关系表示例
- 5. 算符优先分析算法
-
- 6. LR分析法
-
- 6.1 语法分析阶段
-
- 6.1.1 句柄和规范规约
-
- 6.1.2 LR分析器结构
- 6.1.3 LR分析法示例
- 6.1.4 LR文法
- 6.2 产生分析表阶段
-
- 6.2.1 字的前缀、活前缀
- 6.2.2 构造识别活前缀的DFA
-
- 6.2.2.1 文法拓展与LR(0)项目
- 6.2.2.2 识别活前缀的NFA
- 6.2.2.3 通过计算项目集规范族构造识别活前缀的DFA
- 6.2.3 构造LR(0)分析表
-
- 6.2.3.1 构造LR(0)分析表的算法
- 6.2.3.2 LR(0)分析示例
- 6.2.3.3 小结
1. 自底向上分析
1.1 语法分析
- 自底向上分析:
1.从输入串开始,逐步进行归约,直到文法的开始符号
2.归约:根据文法的产生式规则,把串中出现的产生式的右部替换成左部符号
- 算符优先分析法
按照算符的优先关系和结合性质进行语法分析
适合分析表达式
- LR分析法
规范归约:句柄作为可归约串
- 自下而上分析的基本思想
1.采用“移进-归约”思想进行自下而上分析
2.用一个寄存符号的先进后出栈,把输入符号一个一个地移进到栈里,当栈顶形成某个产生式的候选式时,即把栈顶的这一部分替换成(归约为)该产生式的左部符号。
1.2 移进-归约分析示例
- 设文法G(S):
(1) S→ aAcBe
(2) A→b
(3) A→ Ab
(4) B→d
试对abbcde进行“移进-归约”分析。
- 自下而上分析过程:边输入单词符号,边归约
- 核心问题:识别可归约串
- 分析树和语法树不一定一致
2. 短语与直接短语
- 定义:令G是一个文法,S是文法的开始符号,假定αβδ是文法G的一个句型,如果有
则β称是句型αβδ相对于非终结符A的短语
- 直接短语一定是短语。
- 如果短语代表可规约串,直接短语就代表了可立即规约的串。
G(E):
E→ T | E+T
T→ F | T*F
F→ (E) | i
短语:i1, i2, i3, i1*i2, i1*i2+i3
直接短语:i1, i2, i3
现在,给出句型i1*i2+i3的分析过程:
3. 算符优先分析方法
- 优先关系
任何两个可能相继出现的终结符a与b可能三种优先关系
- 算符文法:一个文法,如果它的任一产生式的右部都不含两个相继(并列)的非终结符,即不含…QR…形式的产生式右部,则我们称该文法为算符文法。
- 算符优先文法:假定G是一个不含ε-产生式的算符文法。对于任何一对终结符a、b,我们说:
- 考虑下面的文法G(E):
(1) E→E+T | T
(2) T→T*F | F
(3) F→P↑ F | P
(4) P→(E) | i
优先关系表:
4. 构造优先关系表
4.1 FIRSTVT和LASTVT集合
4.2 FIRSTVT和LASTVT集合的计算
4.2.1 构造集合FIRSTVT( P )的算法
- 反复使用下面两条规则构造集合FIRSTVT( P )
(1)若有产生式P→a…或P→Qa…,则a∈FIRSTVT( P )
(2)若a∈FIRSTVT(Q),且有产生式P→Q…,则a∈FIRSTVT( P )
- (1)
布尔数组F[P,a],使得F[P,a]为真的条件是,当且仅当a∈FIRSTVT§。开始时,按上述的规则1对每个数组元素F[P,a]赋初值。
栈STACK,把所有初值为真的数组元素F[P,a]的符号对(P,a)全都放在STACK之中。
- (2)
若栈STACK不空,就将栈顶项弹出,记此项为(Q,a)。对于每个形如P→Q…的产生式,若F[P,a]为假,则变其值为真且将(P,a)推进STACK栈。
上述过程一直重复,直至栈STACK为空为止。
4.2.2 构造集合LASTVT§的算法
- 反复使用下面两条规则构造集合LASTVT§
(1)若有产生式P→… a或P→ … aQ,则a∈LASTVT§
(2)若a∈LASTVT(Q),且有产生式P→… Q,则a∈LASTVT§
4.2.3 示例
- 考虑下面的文法,计算文法G的FIRSTVT和LASTVT。
G(E):
(1) E→E+T | T
(2) T→T*F | F
(3) F→P↑ F | P
(4) P→(E) | i
-
FIRSTVT
-
LASTVT
4.3 构造优先关系表示例
G(E):
(1) E→E+T | T
(2) T→T*F | F
(3) F→P↑F | P
(4) P→(E) | i
- 首先根据4.2.2得到G的FIRSTVT集合与LASTVT集合:
FIRSTVT(E)={ +, *,↑, (, i }
FIRSTVT(T)={ *,↑, (, i }
FIRSTVT(F)={↑, (, i }
FIRSTVT(P)={ (, i }
LASTVT(E)={ +, *,↑, ), i }
LASTVT(T)={ *,↑, ), i }
LASTVT(F)={↑, ), i }
LASTVT(P)={ ), i }
|
+ |
* |
↑ |
i |
( |
) |
# |
+ |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
↑ |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
( |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
# |
|
|
|
|
|
|
|
- 根据E→E+T | T:
- LASTVT(E)中每个元素的优先级要比右边+高
- 左边+要比FIRSTVT(T)中的每个元素的优先级低
左\右 |
+ |
* |
↑ |
i |
( |
) |
# |
+ |
> |
< |
< |
< |
< |
|
|
* |
> |
|
|
|
|
|
|
↑ |
> |
|
|
|
|
|
|
i |
> |
|
|
|
|
|
|
( |
|
|
|
|
|
|
|
) |
> |
|
|
|
|
|
|
# |
|
|
|
|
|
|
|
- 根据T→T*F | F:
- LASTVT(T)中每个元素的优先级要比右边*高
- 左边*要比FIRSTVT(T)中的每个元素的优先级低
左\右 |
+ |
* |
↑ |
i |
( |
) |
# |
+ |
> |
< |
< |
< |
< |
|
|
* |
> |
> |
< |
< |
< |
|
|
↑ |
> |
> |
|
|
|
|
|
i |
> |
> |
|
|
|
|
|
( |
|
|
|
|
|
|
|
) |
> |
> |
|
|
|
|
|
# |
|
|
|
|
|
|
|
- 根据F→P↑F | P:
- LASTVT§中每个元素的优先级要比右边↑高
- 左边↑要比FIRSTVT(F)中的每个元素的优先级低
左\右 |
+ |
* |
↑ |
i |
( |
) |
# |
+ |
> |
< |
< |
< |
< |
|
|
* |
> |
> |
< |
< |
< |
|
|
↑ |
> |
> |
< |
< |
< |
|
|
i |
> |
> |
> |
|
|
|
|
( |
|
|
|
|
|
|
|
) |
> |
> |
> |
|
|
|
|
# |
|
|
|
|
|
|
|
- 根据P→(E) | i:
- (和)是相等的
- LASTVT§中每个元素的优先级要比右边↑高
- 左边↑要比FIRSTVT(F)中的每个元素的优先级低
左\右 |
+ |
* |
↑ |
i |
( |
) |
# |
+ |
> |
< |
< |
< |
< |
> |
|
* |
> |
> |
< |
< |
< |
> |
|
↑ |
> |
> |
< |
< |
< |
> |
|
i |
> |
> |
> |
|
|
> |
|
( |
< |
< |
< |
< |
< |
= |
|
) |
> |
> |
> |
|
|
> |
|
# |
|
|
|
|
|
|
|
- 最后考虑#的判断,根据文法的开始符号是E,我们判断#E#
左\右 |
+ |
* |
↑ |
i |
( |
) |
# |
+ |
> |
< |
< |
< |
< |
> |
> |
* |
> |
> |
< |
< |
< |
> |
> |
↑ |
> |
> |
< |
< |
< |
> |
> |
i |
> |
> |
> |
|
|
> |
> |
( |
< |
< |
< |
< |
< |
= |
|
) |
> |
> |
> |
|
|
> |
> |
# |
< |
< |
< |
< |
< |
|
= |
5. 算符优先分析算法
5.1 最左素短语
- 可归约串,句型,短语
- 素短语:至少含有一个终结符,除它自身之外不再含任何更小的素短语
- 最左素短语:是指处于句型最左边的那个素短语
- 考虑文法G(E):
(1) E→E+T | T
(2) T→T*F | F
(3) F→P↑ F | P
(4) P→(E) | i
对于句型:T+F*P+i
短语:T, F, P , F*P, , i T+F*P , T+F*P+i
直接短语:T, F, P, i
素短语:i , F*P
最左素短语:F*P
- 算符优先文法句型(括在两个#之间)的一般形式:
#N1a1N2a2…NnanNn+1#
其中,ai都是终结符,Ni是可有可无的非终结符。
5.2 算符优先分析算法描述
- 使用一个符号栈S,用它寄存终结符和非终结符,k代表符号栈S的使用深度
- 在正确的情况下,算法工作完毕时,符号栈S应呈现:# N #
- 算符优先分析结果不一定是语法树
- 算符优先分析程序构成:
(1)总控程序,根据现行栈顶符号和当前输入符号,执行动作
(2)优先关系表,用于指导总控程序进行移进-归约
(3)分析栈 STACK,用于存放文法符号
- 特点
优点: 简单,快速
缺点: 可能错误接受非法句子
- 使用广泛
用于分析各类表达式
ALGOL 60
6. LR分析法
- 1965年 由Knuth提出
- L:从左到右扫描输入串
- R:自下而上进行归约
- 工作框架
6.1 语法分析阶段
6.1.1 句柄和规范规约
6.1.1.1 句柄
在一个句型对应的语法树中
- 以某非终结符为根的两代以上的子树的所有末端结点从左到右排列就是相对于该非终结符的一个短语:i1,i2,i3, i1*i2, i1*i2+i3
- 如果子树只有两代,则该短语就是直接短语:i1,i2,i3
- 最左两代子树末端就是句柄:i1
句柄规约示例:
句型 归约规则
abbcde (2) A → b
aAbcde (3) A → Ab
aAcde (4) B → d
aAcBe (1) S → aAcBe
S
6.1.1.2 规范归约
- 定义:假定α是文法G的一个句子,我们称序列αn, αn-1,… ,α0 是α的一个规范归约,如果此序列满足:
(1)αn= α
(2)α0为文法的开始符号,即α0=S
(3)对任何i,0 ≤ i ≤ n, αi-1是从αi经把句柄替换成为相应产生式左部符号而得到的
- 算符优先分析一般并不等价于规范归约
- 规范归约是最左归约
规范归约的逆过程就是最右推导
- 最右推导也称为规范推导
- 由规范推导推出的句型称为规范句型
- 规范归约的关键问题是寻找句柄
6.1.2 LR分析器结构
历史信息:已移入符号栈的内容
展望信息:根据产生式推测未来可能遇到的输入符号
现实信息:当前的输入符号
- LR分析方法:把"历史"及"展望"综合抽象成状态;由栈顶的状态和现行的输入符号唯一确定每一步工作。
- LR分析器的核心是一张分析表
ACTION[s,a]:当状态s面临输入符号a时,应采取什么动作.
GOTO[s,X]:状态s面对文法符号X时,下一状态是什么
- s移进(shift):把(s,a)的下一状态s’和输入符号a推进栈,下一输入符号变成现行输入符号
- r归约(reduce):用某产生式A→β进行归约。 假若β的长度为r, 去除栈顶r个项,使状态sm-r变成栈顶状态,然后把下一状态s’=GOTO[sm-r, A]和文法符号A推进栈
- acc接受:宣布分析成功,停止分析器工作
LR分析过程:
- 分析开始时:
状态栈 符号栈 输入串
(s0 , #, a1a2 … an #)
- 以后每步的结果可以表示为:
(s0 s1 … sm, # X1 … Xm, aiai+1 … an #)
6.1.3 LR分析法示例
根据文法G(E),分析输入串i*i+i:
(1) E→E+T
(2) E→T
(3) T→T*F
(4) T→F
(5) F→(E)
(6) F→I
上表的推导过程后续给出,现在根据该表进行LR分析。
步骤 |
状态 |
符号 |
输入串 |
(1) |
0 |
# |
i*i+i# |
(2) |
05 |
#i |
*i+i# |
(3) |
03 |
#F |
*i+i# |
(4) |
02 |
#T |
*i+i# |
(5) |
027 |
#T* |
i+i# |
(6) |
0275 |
#T*i |
+i# |
(7) |
027 10 |
#T*F |
+i# |
(8) |
02 |
#T |
+i# |
(9) |
01 |
#E |
+i# |
(10) |
016 |
#E+ |
i# |
(11) |
0165 |
#E+i |
# |
(12) |
0163 |
#E+F |
# |
(13) |
0169 |
#E+T |
# |
(14) |
01 |
#E |
# |
(15) |
接受 |
|
|
6.1.4 LR文法
- 定义:对于一个文法,如果能够构造一张分析表,使得它的每个入口均是唯一确定的,则这个文法就称为LR文法。
- 定义:一个文法,如果能用一个每步顶多向前检查k个输入符号的LR分析器进行分析,则这个文法就称为LR(k)文法。
- LR文法不是二义的,二义文法肯定不会是LR的
- LR文法⊂ 无二义文法
6.2 产生分析表阶段
6.2.1 字的前缀、活前缀
- 字的前缀:是指字的任意首部,如字abc的前缀有ε,a,ab,abc
- 活前缀:是指规范句型的一个前缀,这种前缀不含句柄之后的任何符号。即,对于规范句型αβδ,β为句柄,如果αβ=u1u2…ur,则符号串u1u2…ui(1≤i≤r)是αβδ的活前缀。(δ必为终结符串)
- 规范归约过程中,保证分析栈中总是活前缀,就说明分析采取的移进/归约动作是正确的
6.2.2 构造识别活前缀的DFA
6.2.2.1 文法拓展与LR(0)项目
- 将文法G(S)拓广为G′(S′)
构造文法G′,它包含了整个G,并引进不出现在G中的非终结符S′、以及产生式S′→S,S′是G′的开始符号
称G′是G的拓广文法
- R(0)项目
在每个产生式的右部添加一个圆点,表示我们在分析过程中看到了产生式多大部分
A→XYZ有四个项目:
A→ •XYZ、A→X•YZ、A→XY•Z、A→XYZ•
- A→α• 称为"归约项目"
归约项目 S′→α • 称为"接受项目"
A→α•aβ (a∈VT) 称为"移进项目"
A→α•Bβ (B∈VN) 称为"待约项目"
6.2.2.2 识别活前缀的NFA
1. S′→•E 2. S′→E•
3. E→•aA 4. E→a•A
5. E→aA• 6. A→•cA
7. A→c•A 8. A→cA•
9. A→•d 10. A→d•
11. E→•bB 12. E→b•B
13. E→bB• 14. B→•cB
15. B→c•B 16. B→cB•
17. B→•d 18. B→d•
- LR(0)项目集规范族:构成识别一个文法活前缀的DFA的项目集(状态)的全体称为文法的LR(0)项目集规范族。
6.2.2.3 通过计算项目集规范族构造识别活前缀的DFA
一、有效项目
- 项目A→β1•β2对活前缀αβ1是有效的,其条件是存在规范推导:
- 文法G(S′)
S′→E
E→aA|bB
A→cA|d
B→cB|d
项目B → c•B,B → •cB,B → •d
- 对于活前缀:bc 有效
- 若项目A→α•Bβ对活前缀η=δα是有效的,
且B→γ 是一个产生式,
则项目B→•γ对η=δα也是有效的。
二、LR(0)项目集规范族的构造
- 项目集的闭包CLOSURE:
假定I是文法G′的任一项目集,定义和构造I的闭包CLOSURE(I)如下:
(1)I的任何项目都属于CLOSURE(I);
(2)若A→α•Bβ属于CLOSURE(I),那么,对任何关于A的产生式B→γ,项目B→•γ也属于CLOSURE(I);
(3)重复执行上述两步骤直至CLOSURE(I) 不再增大为止。
- 状态转换函数
为了识别活前缀,我们定义一个状态转换函数GO是一个状态转换函数。I是一个项目集,X是一个文法符号。函数值GO(I,X)定义为:
GO(I,X)=CLOSURE(J)
其中J={任何形如A→αX•β的项目| A→α•Xβ属于I}。
直观上说,若I是对某个活前缀γ有效的项目集,那么,GO(I,X)便是对 γX 有效的项目集。
三、示例
- 文法G(S′)
S′→E
E→aA|bB
A→cA|d
B→cB|d
- I0={S′→•E, E→•aA, E→•bB}
- GO(I0, E)= closure(J)=closure({S’→E•})= {S’→E•} = I1
- GO(I0, a)= closure(J)=closure({E→a•A})={ E→a•A, A→•cA, A→•d} )=I2
- GO(I0, b)= closure(J)=closure ({E →b•B})={E→b•B, B→•cB, B→•d}= I3
两种构造识别活前缀的DFA的方法
- 项目 → NFA → DFA
- Closure → GO → DFA
6.2.3 构造LR(0)分析表
6.2.3.1 构造LR(0)分析表的算法
- 假若一个文法G的拓广文法G′的活前缀识别自动机中的每个状态(项目集)不存在下述情况:
既含移进项目又含归约项目;
含有多个归约项目
- 则称G是一个LR(0)文法。
LR(0)分析表的ACTION和GOTO子表构造
- 若项目A→α•aβ属于Ik且GO(Ik, a)=Ij,a为终结符,则置ACTION[k, a]为“sj”。
- 若项目A→α•属于Ik,那么,对任何终结符a(或结束符#),置ACTION[k, a]为 “rj”(假定产生式A→α是文法G′的第j个产生式)。
- 若项目S′→S•属于Ik,则置ACTION[k,#]为“acc”。
- 若GO(Ik, A)=Ij,A为非终结符,则置GOTO[k, A]=j。
- 分析表中凡不能用规则1至4填入信息的空白格均置上“报错标志”。
6.2.3.2 LR(0)分析示例
按照LR(0)分析表对bcd进行分析
步骤 |
状态 |
符号 |
输入串 |
1 |
0 |
# |
bcd# |
2 |
0 3 |
#b |
cd# |
3 |
0 3 5 |
#bc |
d# |
4 |
0 3 5 11 |
#bcd |
# |
5 |
0 3 5 9 |
#bcB |
# |
6 |
0 3 7 |
#bB |
# |
7 |
0 1 |
#E |
# |
8 |
接受 |
|
|
6.2.3.3 小结
LR分析表确定了当前状态和输入符号的下一步动作是移进还是规约,
DFA的转化关系构成了分析表的action和goto集合,
项目集规范族是DFA的项目集的全体集合