More Related Content Similar to 第6章 自底向上的lr分析法 (8) More from tjpucompiler (7) 第6章 自底向上的lr分析法2. 在第 5 章的自底向上分析中的
关键问题是寻找可归约串。
简单优先分析方法、算符优
先分析方法,基本上都是从左到右
地向前看若干个输入符号,找出句
柄 ( 或其变型 — 最左素短语 ) 的尾,
然后回头 ( 从右到左 ) 查看已扫描过
的符号,找出句柄 ( 或其变型 — 最左
素短语 ) 的头。
这实际上已不是严格地从左
到右地查看源程序进行归约了。 2
3. LR 分析法是严格的规范归约
LR 分析法是一种自底向上的语法
分析方法,它严格地从左到右地读入输
入符号串中的符号,且最多向前看确定
个数 (K 个 ) 的符号,就能无回溯地识别
给定的符号串。
3
5. 本章主要内容
本章主要介绍 LR 分析的基本思
想和当 K≤1 是 LR 分析方法的基本构造
原理和方法。
组成:总控程序 + LR 分析表 + 分析
栈
5
6. 分析表的构造方法:
规范的 LR 法 :
LR(0) ,能力最弱,理论上最重要;
LR(1) ,它功能最强,但代价也最大。
简单的 LR 法:
简称 SLR ,最容易实现,但功能最弱
。
向前看的 LR 法:
简称 LALR ,功能和代价处于前两者 6
之间。
8. 在语法分析中,句柄是逐步形成
的。
据此,可以用 “ 状态” 的概念来
描述不同时刻所形成的那部分句柄。
把每个句柄的识别过程划分为
若干状态,在每个状态下从左到右识
别句柄的一个符合。
8
10. 但 “ 展望 ” 材料的汇集是很困难的
,因为根据 “ 历史 ” 推测未来,即使是推
测未来一个符号也存在非常多的可能。
10
11. LR 分析法的实现原理
为了记住分析的 “ 历史 ” 和汇集 “
展望 ” 的信息, LR 分析法这样处理:
将归约过程的 “ 历史 ” 和 “ 展望 ”
材料综合抽象成某些状态,存放在一个
状态栈中,栈中每个状态都概括了从分
析开始直到某一归约阶段的全部 “ 历史 ”
和 “ 展望 ” 材料。
11
14. LR 分析法的实现
LR 分析法也是一种表驱动的分析
方法,有一个分析栈、一个总控程序和
一个分析表。
特殊性
栈 = 状态栈 + 文法符号栈
分析表 = 动作表 (ACTION) + 状态转移表
(GOTO)
总控程序都一样
文法不同,分析表不同
LR 分析表是 LR 分析器的核心部分
14
15. LR 分析器的逻辑结构
输入串 a … a … a #
1 i n
状态符号栈
Sn Xn 总控程序 输出
Sn-1 Xn-1
… …
… …
… …
S1 X1 动作表 状态转移表
分析表
S0 action goto
# 15
16. ACTION 表
ACTION 表是一个状态及终结符的
二维矩阵; ACTION[si,a] 定义了在栈顶
状态 si 下,当前输入符为 a 时应采取的
动作,不外乎下列四种:
16
17. ① 移进:若 ACTION[Si ,a]=sj(shift) ,
将状态 sj 及当前输入符 a 移入栈顶。
② 归约:若 ACTION[Si ,a]
=rj(reduce) ,将栈顶内容按第 j 个产
生式归约。
③ 接受:若 ACTION[Si ,a] =acc ,则
分析成功,正常停止。
④ 报错:若 ACTION[Si ,a] =error ,
语法出错,进行出错处理。 17
18. GOTO 表
GOTO 表是一个状态及非终结符的
二维矩阵; GOTO[Si,X] 定义了当栈顶
状态遇到当前符号时应转向的状态。
18
19. LR 分析法的分析过程
总控程序对任何 LR 分析法都
适用。它从输入串中读入当前输入符
a ,然后由栈顶状态 si 与当前输入字符
a 查看 ACTION 表,决定应采取何种动
作。
19
20. 若为 sj ,则将状态 j 及当前输入符 a 移入栈
顶。
若为 rj ,则按第 j 个产生式 A→β 归约,若
β 的长度为 r ,则从状态栈和文法符号栈中
自栈顶向下去掉 r 个符号,并把 A 移入文
法符号栈内, sj=GOTO[si , A] 移进状态栈。
其中 si 为修改后的栈顶状态。
若为 acc ,则分析成功,结束。
其他转错误处理程序。
20
21. 对输入串 abbcde# 的 LR 分析过
步骤 符号栈 输入符号 动作 状态栈 ACTION GOTO
程 串
1) # abbcde# 移进 0 S2
2) #a bbcde# 移进 02 S4
3) #ab bcde# 归约 (A→ b) 024 r2 3
4) #aA bcde# 移进 023 S6
5) #aAb cde# 归约 (A→ Ab) 0236 r3 3
6) #aA cde# 移进 023 S5
7) #aAc de# 移进 0235 S8
8) # aAcd e# 归约 (B→ d) 02358 r4 7
9) #aAcB e# 移进 02357 S9
10 ) #aAcBe # 归约 (S→ aAcBe) 023579 r1 1
11 ) #S # 接受 01 acc
Si: 移进,将状态 i 和输入符进栈
ri: 归约,用第 i 个产生式归约,同时状态栈与符号栈退出相应个符号
21
,并把 GOTO 表相应状态和第 i 个产生式的左部非终结符入栈。
23. 首先讨论一种只概括 “ 历史 ” 资
料而不包含推测性 “ 展望 ” 材料的 “ 状态 ” 。
我们希望仅由这种简单状态就能识别呈
现在栈顶的句柄。
在讨论之前,需要定义一个重
要概念,即文法的规范句型的 “ 活前缀”
。
23
24. 规范句型的活前缀
字 ( 符号串 ) 的前缀:字的前缀是指该字的
任意首部。
活前缀:是指规范句型的活前缀,它不包含
该句型的句柄右边的任何符号。
在活前缀的右边添上一些终结符号后,总可
以构成一个规范句型。
LR 识别过程中,栈里面的符号 ( 自栈底而
上 ) 就是一个活前缀,把输入串的剩余部
分配上后应成为一个规范句型。因此,只
要输入串已扫描过的部分能构成一个活前
缀,则意味着所扫描过的这一部分没有错 24
误。
25. 活前缀和句柄的关系
文法 G[S]: 若 S => αAω =>αβω r 是 αβ 的前缀
,则称 r 是 G 的一个活前缀。
活前缀已含有句柄的全部符号,表明产生
式 A→β 的 右部 β 已出现在栈顶。
活前缀只含句柄的一部分符号表明
A→β1β2 的右部子串 β1 已出现在栈顶,期
待从输入串中看到 β2 推出的符号。
活前缀不含有句柄的任何符号,此时期望
A→β 的右部所推出的符号串。 25
26. 对规范句型活前缀的识别
对于一个文法 G ,可以构造一 DFA
去识别给定文法的所有规范句型的活前缀,进
而由此构造文法的 LR 分析表,用来指导对句
柄的识别 — 可根据分析栈的栈顶状态了判断是
否得到句柄,并进行归约。只要输入串的已扫
描的部分 ( 符号栈中的内容 ) 可构成一个活前
缀,则说明所扫描过的部分没有错误。
这样,对句柄的识别就变成了对规范
句型活前缀的识别。因此,活前缀反映了句柄
的识别状态。
26
27. LR(0) 项目
定义:在文法的每一个产生式的右部适
当位置添加一个圆点 “ .” ,则一个
LR(0) 项目。
A → xyz 的 LR(0) 项目:
1. A → · xyz
2. A → x · yz
注意:一个产生式可对应的
3. A → xy · z 项目为它的右部符号长度加
1 ,对空产生式 A→ε 仅有
4. A → xyz · 项目 A→· 。
27
30. 构造识别活前缀的 NFA
文法 G[S] 的拓广: G’[S’]=G[S]+{S’ →
S}
文法的每个项目为 NFA 的一个状态。
确定状态之间的转换关系
按规则可对文法 G’ 的所有项目对应的
状态构造出识别活前缀的 NFA 。
30
32. LR(0) 项目集规范族
提出 “ LR(0) 项目集规范族 ” 的原因
若按以上方法先构造 NFA ,然
后再确定化为 DFA ,这样工作量较大
,不可取。
对于构成识别一个文法活前
缀的 DFA 项目集 ( 状态 ) 的全体称为
这个文法的 LR(0) 项目集规范族。
32
33. LR(0) 项目集规范族的构造
可利用闭包函数 (CLOSURE)
求出 LR(0) 项目集规范族中的每个项目
集 (DFA 的状态 ) ,然后根据转换函数
(goto) 直接构造 DFA 。
33
34. 构造步骤
对文法进行拓广;
构造拓广文法的 LR(0) 项目集规范族
(由初始项目出发,利用 CLOSURE 和
goto 函数);
将 LR(0) 项目集规范族中的每个项目
集作为 DFA 的状态,将 goto 函数作
为状态转换函数,构造出的 DFA 即为
所求。 34
35. 项目集 I 的闭包 CLOSURE(I)
设 I 是文法 G’( 拓广文法 ) 的任一项目集
,则定义和构造 CLOSURE(I) 的规则如下:
① 属于 I 的任何项目也属于 CLOSURE(I) ;
② 若 A → α .Bβ 属于 CLOSURE(I) ,那么,对
于任何关于 B 的产生式 B→ γ , 项目 B→ .γ 也
属于 CLOSURE(I) ;
③ 重复②直到 CLOSURE(I) 不再增大为止。此
CLOSURE(I) 即为所求的一个项目子集,将其
作为 DFA 的一个状态。
35
36. 项目集闭包的例子
文法: 若 I={E ’ →.E}
0. E ’ → E CLOSURE(I) 为
: ’ →.E
E
1. E → E+T
2. E → T E→.E+T
3. T → T*F E→.T
4. T → F T→.T*F
T→.F
5. F →(E)
F→.i
6. F → i
F→.(E) 36
37. goto(I,X) 函数
如果 LR(0) 项目集规范族中的每个项目集
看做 DFA 一个状态,则项目集规范族的
goto 函数把这些项目集连接成一个 DFA 。
定义:设 I 是一个项目集 ,X 是任一文法符
,则 goto(I,X) 定义为:
goto(I,X)=CLOSURE(J),
其中 J={ 任何具有 [A → αX.β] 的项目 | [A
→ α.Xβ] ∈ I }
X
I: A→α·Xβ J: A→αX·β
37
38. 重要结论
构造拓广文法的项目集规范族
C 时,其中的项目集可看作一个 DFA
的状态,该 DFA 能够识别文法的所有
活前缀。 C 上的 goto 函数可以看作
DFA 的状态转换函数。
初始状态: CLOSURE({S ’ →.S})
38
39. LR(0) 文法
如果文法 G’ 的项目集规范族的
每个项目集中不存在下述冲突项目:
① 移进项目和归约项目并存,
② 多个归约项目并存,
则称文法 G’ 为 LR(0) 文法。
注意:只有对于 LR(0) 文法,才能构
造它的 LR(0) 分析表。
39
40. LR(0) 分析表的构造
设文法 G’ 的项目集规范族
C={I0,I1,…,In},
令其中每个项目集的下标作
为分析器的状态。
令包含项目 [S’ → .S] 的项目
集 Ik 的下标 k 为分析器的初态。
构造 LR(0) 分析表的步骤如
下: 40
41. ① 若项目 A→α.aβ∈ Ii 且
移进
项目
goto(Ii,a)=Ij, 其中 a 为终结符,置
ACTION[i,a]=“ 把状态 j 和符号 a
移进栈 ” ,简记为 “ sj”;
归约
项目
② 若项目 A→α.∈ Ii , 则对于任何输
入符 a 或结束符 # ,置
ACTION[i,a]=“ 用产生式 A→α 进
行归约 ” ,简记为 “ rj”( 假定 A→α
41
是文法 G’ 的第 j 条产生式 );
42. 接受 ③ 若项目 S’→S.∈ Ii ,则置
项目 ACTION[i,#]=“ 接收 ” ,简记
为 ‘ acc’ ;
④ 若 goto(I,A)=Ij , A 为非终结符
,则置 GOTO(i,A)=j
⑤ 分析表中凡不能用规则① - ④
添入信息的元素均置上 ERROR 。
42
43. 构造 LR(0) 分析表的步骤小结
文法拓广、产生式编号;
利用 CLOSURE 和 goto 函数,求出项
目集规范族 C;
构造识别活前缀的 DFA ;
由算法构造 LR(0) 分析表。
43
45. 使用 SLR(1) 的原因
例: G: (0) S’→S (1) S→rD (2) D→D,i (3) D→i
LR ( 0 )项目集规范族
I0: S’→ . S I3: S→r D .
S→ . r D D→D . i
I1: S’→S . I4: D→i .
I2: S→r . D I5: D→D . i
D→ . D i I6: D→Di .
D→ . I 其中 I3 中含有移进 / 归约冲突。
文法不是 LR(0) 的,如何解决? 45
46. SLR(1) 分析法
若 LR(0) 项目集规范族中有
项目集 IK 含移进 / 归约、归约 / 归约冲
突: IK :{X→α.bβ, A → γ . , B →δ. }
若满足 FOLLOW(A) ∩FOLLOW(B)
=φ
FOLLOW(A) ∩ { b } =φ
FOLLOW(B) ∩ { b} =φ 46
冲突即可解决。这就是 SLR(1) 分析
47. 当在状态 Ik 时面临某输入符号
a 时,则动作可由下规定决策。
若 a=b ,则 action [ k,b ] = 移进
。
若 a ∈FOLLOW (A) ,则 action
[ k,a ] = 用 A → γ 归约。
若 a ∈FOLLOW (B) ,则 action [
k,a ] = 用 B→δ 归约。
能用 SLR(1) 技术解决冲突的文
法称为 SLR(1) 文法。 47
SLR(1) 文法是无二义的。
48. SLR 分析表
移进 ① 若项目 A→α.aβ∈ Ii 且
项目 goto(Ii,a)=Ij, 其中 a 为终结符,置
ACTION[i,a]=“ 把状态 j 和符号 a
移进栈 ” ,简记为 “ sj”; ( 同 LR(0))
归约 ② 若项目 A→α .属于 Ii, 那么,
项目
对任何输入符号 a, a∈ FOLLOW(A),
置 ACTION[i, a] 为 “ 用产生式 A→α
进行规约 ” ,简记为 “ rj”; 其中,假
定 A→α 为文法 G’ 的第 j 个产生式
48
49. 接受 ③ 若项目 S’→S.∈ Ii ,则置
项目 ACTION[i,#]=“ 接收 ” ,简记
为 ‘ acc’ ; ( 同 LR(0))
④ 若 goto(I,A)=Ij , A 为非终结符
,则置 GOTO(i,A)=j ( 同 LR(0))
⑤ 分析表中凡不能用规则① - ④
添入信息的元素均置上 ERROR 。
( 同 LR(0)) 49
50. 按上述算法构造的含有
ACTION 和 GOTO 两部分的分析表,
如果每个入口不含多重定义,则称它
为文法 G 的一张 SLR 表。
具有 SLR 表的文法 G 称为一
个 SLR ( 1 )文法。
数字 1 的意思是,在分析过程
中顶多只要向前看一个符号。
50
51. § 6.6
LR(1)
分析表的构造
51
52. 用 SLR(1) 也不能解决的冲突
文法 G:(0)S’→S (1) S→aAd (2) S→bAc
(3)S→aec (4) S→bed (5) A→e
LR(0) 项目集规范族(识别 G 的活前缀的
DFA) :
I0: S’→ . S I1: S’→S . I2: S→a . Ad
S→ . aAd S→a . ec
S→ . bAc A→ . e
S→ . aec
S→ . bed 52
53. I3: S→b . Ac I4: I5:
S→b . ed S→aA . d S→ae . c
A→ . e A→e .
I6: I7: I8:
S→bA . c S→be . d
S→aAd .
A→e .
53
54. 上例的 SLR 分析表
ACTION GOTO
a c e b d # S A
0 S2 S3 1
1 acc
2 S5 4
3 S7 6
4 S8
5 r5 r5/S9 r5 r5 r5 r5
6 S10
7 r7 r7 r7 r7 r7/S11 r7
8 r1 r1 r1 r1 r1 r1
9 r3 r3 r3 r3 r3 r3
10 r2 r2 r2 r2 r2 r2
11 r4 r4 r4 r4 r4 r4
54
55. 对问题的进一步分析
对于 SLR 分析法而言,若项目
[A→ α .] ∈ Ii ,则对于 a ∈ FOLLOW(A), 规定
ACTION[I,a]=“ 用 A→ α 归约 ” 。
有时,当状态 i 出现在栈顶时,
栈里的符号串组成的活前缀 βα 未必允许把
α 归约为 A ,因为可能根本就不存在一个规
范句型含有前缀 “ βA a” 。在这种情况下,
用 A→ α 进行归约未必有效。
55
56. 不能用 SLR(1) 解决的原因
I5: S →ae . c
A →e .
R R R
S’==>S==>aAd==>aed
R R
S’==>S==>aec
I7: S →be . d
A →e .
R R R
S’==>S==>bAc==>bec
R R
S’==>S==>bed
哪些输入符号能跟在句柄之后! 56
57. 解决方案
让每个状态携带更多的信息,
以帮助 LR 分析器解决语法分析过程中
可能面临的移进 - 归约冲突,并尽可能
排除不合适的归约动作。
必要时需要对状态进行分裂,
以便 LR 的每个状态都恰好指明:当那
些输入符号紧跟 在句柄 α 之后时,才
允许利用产生式 A→ α 进行归约。
57
59. LR(1) 项目
LR(0) 项
目
定义: [ A → α.β, a ]
在原来的 LR(0) 项目中加一
个向前搜索符 a ,这样得到的项目
称为 LR(1) 项目。
59
60. 向前搜索符的意义
向前搜索符 a 只对归约项目 [
A → α. , a ] 有意义,它表示当它所
属的状态处于栈顶,且下一个输入符
为 a 时,才可以将栈顶符号 α 归约为
A 。
除此之外的其他符号出现,
即使 FOLLOW(A) 中的符号,也不归
约。
在其他项目中, a 只是起传递 60
作用。
61. LR(1) 分析法
若 A →α .B β ∈ 项目集 I
则 B → . γ ( B → γ 是一产生式)
) I
把 FIRST(β) 中的符号作为用
B → γ 归约的搜索符 , 称为向前搜索符
,作为归约时查看的符号集合用以代替
SLR(1) 分析中的 FOLLOW 集,把此搜
索符号的集合也放在相应项目的后面,
这种处理方法即为 LR(1) 分析法。 61
62. 构造 LR(1) 项目集规范族
1. 构造 LR(1) 项目集的 closure(I)
(1) I 的任何项目属 closure(I) ;
(2) 若 [A →α .B β , a]∈ closure(I) , B→
γ 是一产生式,那么对于 FIRST(βa) 中
的每个终结符 b ,如果 [B→ . . ,b] 不
在 closure(I) 中,则把它加进去;
(3) 重复 (1)(2) ,直至 closure(I) 不再增大
。 62
63. 2. 构造 GO 转换函数
若 I 是一个项目集, X 是一个文法符号
GO(I, X)= closure(J)
其中 J={ 任何形如 [A→αX . β,a] 的项
目∣ [A→α.Xβ,a]∈ I}
LR(I) 项目规范族 C 的构造算法类同
LR(0) 的,只是初始时:
C={ closure( { S’→ . S , # } ) };
63
64. LR(1) 分析表的构造
设文法 G’ 的项目集规范族 C={I0,I1,
…,In}, 令其中每个项目集的下标作为分析器
的状态,令包含项目 [S’ → .S,#] 的项目集 Ik
的下标 k 为分析器的初态。构造 LR(1) 分析
表的步骤如下:
① 若项目 [A→α.aβ,b]∈ Ik 且 goto(Ik,a)=Ij, 其
中 a 为终结符,置 ACTION[I,a]=“ 把状态 j
和符号 a 移进栈 ” ,简记为 “ sj”;
② 若项目 [A→α.,a]∈ Ik , 则置
ACTION[I,a]=“ 用产生式 A→α 进行归约 ”
,简记为 “ rj”( 假定 A→α 是文法 G’ 的第 j
条产生式 );
64
65. ③ 若项目 [S’→S.,#]∈ Ik, 则置
ACTION[I,#]=“ 接收 ” ,简记
为 ‘ acc’ ;
④ 若 goto(I,A)=Ij,A 为非终结符,
则置 GOTO(I,A)=j
⑤ 分析表中凡不能用规则① - ④
添入信息的元素均置上 ERROR 。
65
67. LR(0) 、 LR(1) 、 SLR(1) 的比较
状态集的计算方法和 LR(0) 基本相同
分析表的构造方法和 LR(0) 基本相同
构造方法的不同点:
第 2 步处理中归约动作的选择:
LR(0) 分析考虑所有终结符
SLR(1) 分析参考 FOLLOW 集
LR(1) 分析仅考虑 LR(1) 项目中的后继符
( 用 FIRST 集近似考虑 ) 67
69. LALR 分析技术
Look Ahead LR technique.
界于 LR(1) 分析器与 SLR(1) 分析器的之
间的折衷方案。
利用这种分析技术,构造出的 LALR 分
析表的状态数等于 SLR 分析表的状态数
,比规范 LR 分析表少得多;分析能力
比 LR 分析器要差一些,但比 SLR 强大
,能够处理一些 SLR 分析器难以处理的
情况。
69
70. 同心项目集
对于两个 LR(1) 项集,如果除了
搜索符(向前看符号)外,其它完全相
同,那么这两个项集被称为同心项目集
。
同心项目集的心:即对应的
LR(0) 项目集。
70
73. LALR 的基本步骤
首先生成 LR(1) 项集规范族。
合并同心项目集。并且相应地建立合
并后的项目集之间的转换关系。合并
后的项目集的个数和 LR(0) 项目集规
范族中的项集个数相同。
根据这个项目集族,构造分析表。
73
74. LALR 分析表构造算法
1. 构造 LR(1) 项目集规范族 C={I0,I1,I2,
…In} 。
2. 合并 C 中的同心集,记 C’={J0,J1,
…,Jn} 为合并后的新族。令包含项目
[S’ → .S,#] 的项目集 Ik 的下标 k 为
分析器的初态。
74
75. 3. 根据 C’ 构造 ACTION 表:
① 若项目 [A→α.aβ,b]∈ Ji 且
goto(Ji,a)=Ij, 其中 a 为终结符,置
ACTION[J,a]=“ 把状态 j 和符号 a 移进
栈 ” ,简记为 “ sj”;
② 若项目 [A→α.,a]∈ Ji , 则置
ACTION[i,a]=“ 用产生式 A→α 进行归约
” ,简记为 “ rj”( 假定 A→α 是文法 G’ 的
第 j 条产生式 );
③ 若项目 [S’→S.,#]∈ Ji , 则置
ACTION[i,#]=“ 接收 ” ,简记为 ‘ acc’ ;
75
76. 4. 构造 GOTO 表:
假定 J 是由 I1,I2,…,Im 合并后的新项目集
,其中,每个 Ii 都是一个 LR(1) 项目集,
由于 I1,I2,…,Im 同心,因此 goto(I1,X),
goto(I2,X) ,…,goto(Im,X) 也同心。令 K 是由
这些同心项目集合并后的项目集,那
么, goto(J,X)=K 。于是,若 toto(Ji,A)=Jj,
则置 GOTO[i,A]=j 。
5. 分析表中凡不能用 3 、 4 添入信息的空白格
均置 ERROR 。
76