编译原理-陈意云-第三版
哈工大编译原理 MOOC
词法分析 → 语法分析 → 语义分析 → 中间代码生成 → 代码优化 → 目标代码生成 词法分析\to语法分析\to语义分析\to中间代码生成\to代码优化\to目标代码生成 词法分析→语法分析→语义分析→中间代码生成→代码优化→目标代码生成
术语 | 含义 |
---|---|
字母表 | 符号的有限集合 |
串 | 符号的有穷序列 |
空串 | 长度为0的串 |
语言 | 字母表上的串集 |
句子、字 | 属于该语言的串 |
连接 | x和y都是串,将y加到x后面形成的串,称为x和y的连接 |
r
表示一个语言 L(r)
ε
)是正规式,表示 {ε}
Finite Automaton
,有限自动机,简称 FA
有限自动机由以下几个要素组成:
示例
直接看概念过于抽象,这里提供一个简单的示例
假设有一个自动机:能够判断输入的2
位数字,是否每一位都大于5,那么这个自动机应该长这样
start
是开始状态,是、不是
是结束状态Non-deterministic Finite Automaton
,不确定的有限自动机,简称 NFA
若一个 FA
中,存在某个状态,输入符号后,转移后的状态有多个,那么这个 FA
就是 NFA
示例
正规式 ( a ∣ b ) a b (a|b)ab (a∣b)ab 的NFA
状态转换表
状态 | a | b |
---|---|---|
0 | 0, 1 | 0 |
1 | 2 | |
2 |
Deterministic Finite Automaton
,确定性有限自动机,简称 DFA
FA
中,存在某个状态,输入符号后,转移后的状态只有一个,那么这个 FA
就是 DFA
epsilon-closure(T)
T
,经过有限次的epsilon
(ε),可以到达的状态集合状态 | epsilon闭包 |
---|---|
1 | 1,2,4 |
3 | 1,2,3,4,6,7 |
5 | 1,2,4,5,6,7 |
6 | 6,7 |
move(T, a)
状态 | 输入符号 | move |
---|---|---|
2 | a | m o v e ( { 2 } ) = e p s i l o n − c l o s u r e ( { 3 } ) = { 1 , 2 , 3 , 4 , 6 , 7 } move(\{2\})=epsilon - closure(\{3\}) = \{1, 2, 3, 4, 6, 7\} move({2})=epsilon−closure({3})={1,2,3,4,6,7} |
确认NFA 的输入符号;
求开始状态的 epsilon 闭包 A;
将 A 存入最终结果 res;
while (res 中有尚未标记的状态 T) {
标记状态 T;
for (每一个输入符号 a) {
U = epsilon-clousre(move(T,a));
if(U未标记过) {
将 U 加入 res;
}
状态转换表 T 行 a 列 = U;
}
}
move(A, a)
,其中只有状态 2、7 可以接收 amove(A, b)
move(B,a)
move(B,b)
move(C, a)
move(C,b)
move(D,a)
move(D,b)
move(E, a)
和 move(E, b)
不会再产生新的集合0
,而 E 中包含结束状态10
接收状态子集Ⅰ
非接收状态子集Ⅱ
// 将 Ⅰ、Ⅱ 的并集称为 U
for (在 U 中的集合 G) {
对于 G 中任意两个状态 s, t
当输入 a 时,s,t 转移后的状态为 S1, S2
if(S1 和 S2 在 U 中不同的子集) {
将 s, t 划分成两个子集
} else {
将 s, t 划分到同一个子集
}
划分后的 U' 作为新的 U
}
确认输入符号集合 Σ = { a , b } \Sigma = \{a, b\} Σ={a,b}
将状态划分为:接收状态子集、非接收状态子集
接收状态子集就是包含接收状态的集合,非接受状态子集是其余的集合
接收状态子集Ⅰ = { D } 接收状态子集 Ⅰ= \{ D \} 接收状态子集Ⅰ={D}
非接受状态子集Ⅱ = { A , B , C } 非接受状态子集Ⅱ = \{ A,B,C \} 非接受状态子集Ⅱ={A,B,C}
化简,意味着某些集合是等价的
而 { D } \{D\} {D}显然不可再分,那么考虑化简 { A , B , C } \{ A,B,C \} {A,B,C}
由于 AC 和 B 对于不同的符号,转移到的状态子集不同(Ⅰ、Ⅱ),因此将 { A , B , C } \{A,B,C\} {A,B,C} 划分为 2 个子集: { A , C } \{A,C\} {A,C}、 { B } \{B\} {B}
目前只有 { A , C } \{A,C\} {A,C}可以再划分,考虑 { A , C } \{A,C\} {A,C}
m o v e ( A , a ) = m o v e ( C , a ) = B ∈ { B } move(A,a) = move(C,a) = B \in \{B\} move(A,a)=move(C,a)=B∈{B}
m o v e ( A , b ) = m o v e ( C , b ) = C ∈ { A , C } move(A,b)=move(C,b)=C \in \{A,C\} move(A,b)=move(C,b)=C∈{A,C}
A 和 C 对于不同的符号,转移到的子集相同,因此不能再划分(AC 是等价的)
用 A A A代表 { A , C } \{ A,C \} {A,C}
构造正规式 r = ( a ∣ b ) ∗ a b r = (a|b)^*ab r=(a∣b)∗ab 的 NFA
( a ∣ b ) ∗ (a|b)^* (a∣b)∗ 后面是 a b ab ab,并且 a b ab ab 后面不会再有其他符号
产生式
A → a b A \to ab A→ab
文法
文法由一个或多个推导构成
E -> A
A -> ab
A -> ε
非终结符
考虑推导 A → a b A \to ab A→ab,符号 A A A 可以推导出其他字符,那么 A A A 被称为非终结符
在文法中,出现在产生式左侧的,都是非终结符
expression
的缩写终结符
在文法中,没有在产生式左侧出现过的符号,都是终结符
顾名思义,终结:到这个符号结束了,不能再继续推导了
E -> A
A -> ab
其中 E E E, A A A 都是非终结符, a a a 和 b b b 是终结符上下文无关文法
用 G
表示 上下文无关文法
那么 G = ( V T V N , S , P ) G = (V_TV_N,S,P) G=(VTVN,S,P),其中:
V T V_T VT 表示终结符(Terminal)
V N V_N VN表示非终结符(Non Terminal)
S S S 表示开始符号(Start)
P P P表示产生式(Production)
上下文无关文法,还可以通过我的另一篇博客了解(点击跳转)
句型:从文法的开始符号出发,能够推导出的一切字符串(有限长度)
E -> (E)
E -> -E
E -> E + E
E -> id
E -> (E)
E -> -E
E -> E + E
E -> id
E -> (E)
E -> E + E
E -> E * E
E -> id
推导串 i d ∗ i d + i d id * id + id id∗id+id
E -> E * E
E -> id * E
E -> id * E + E
E -> id * id + E
E -> id * id + id
E -> E + E
E -> E * E + E
E -> id * E + E
E -> id * id + E
E -> id * id + id
直接左递归
A → A a A \to Aa A→Aa
注意到,产生式右侧的第一个符号,与产生式左侧相同
那么可以进行无限的推导 A → A a → A a a → . . . A \to Aa \to Aaa \to ... A→Aa→Aaa→...
间接左递归
A → B a A \to Ba A→Ba
B → A b B \to Ab B→Ab
如果将第一个产生式中的 B B B 再进行推导,那么就会得到直接左递归 A → A b a A \to Aba A→Aba
在最左推导中,左递归会导致无限循环,因为每次替换的都是最左侧的非终结符
我的另一篇博客中有更具体的描述(点击跳转)
考虑产生式 A → A a ∣ b A \to Aa | b A→Aa∣b(A可以推出 A a Aa Aa或 b b b)
如果 A
的推导想要结束,必然是经过了非左递归的推导,即 A → b A\to b A→b
那么我们将 A 的推导重写为 A → b A ′ A \to bA' A→bA′,A'
是新引入的符号
接下来分析 A'
到底是什么
A'
就是递归部分, A → A a A\to Aa A→Aa,每次左递归,都会携带一个 a a aA'
还可以推导出 ε
总结
对于产生式 A → A α 1 ∣ A α 2 ∣ . . . ∣ A α m ∣ β 1 ∣ β 2 ∣ . . . ∣ β n A \to A\alpha_1|A\alpha_2|...|A\alpha_m|\beta_1|\beta_2|...|\beta_n A→Aα1∣Aα2∣...∣Aαm∣β1∣β2∣...∣βn
A -> Ba | b
B -> Ab
First(A)
考虑文法
E -> TE'
E' -> +TE' | epsilon
T -> FT'
T' -> *FT' | epsilon
F -> (E) | id
求解 First(E)
E -> TE'
First(T)
T -> FT'
First(F)
F -> (E) | id
(
加入 F i r s t ( F ) First(F) First(F); F → i d F\to id F→id 的第一个符号是非终结符,因此将id
加入 F i r s t ( F ) First(F) First(F)因此 F i r s t ( E ) = F i r s t ( T ) = F i r s t ( F ) = { ( , i d } First(E) = First(T) = First(F) = \{ (, id\} First(E)=First(T)=First(F)={(,id}
注意到,求解 First 集合是一个递归的过程
该文法各终结符的 First 集合
非终结符 | First集合 |
---|---|
E | (,id |
E’ | +,epsilon |
T | (,id |
T’ | +,epsilon |
F | (,id |
Follow(A)
考虑文法
S -> aBC
B -> bB
B -> epsilon
C -> a
C -> d
分析串abd
中间句子 | 产生式 |
---|---|
S | S -> aBC |
aBC | B -> bB |
abBC | B -> epsilon |
abC | C -> d |
abd | ACCEPT |
其中,第 3 步使用了 B 的 epsilon 产生式( B → e p s i l o n B\to epsilon B→epsilon)
Follow
集合),B 的后面是 C,而 C 可以转化为 a 或 d
Follow
集合中,不允许出现epsilon
每一次 Follow 集元素的增加,若跟产生式左侧有关,都是左侧添加到产生式右侧
Follow(S)
添加 $
对于每一条产生式,对于产生式右侧
对于产生式右侧的非终结符 B
,若其右侧存在非终结符 C
,将First(C)
添加到 Follow(B)
A -> BC
若 C
可以推出 epsilon
,将Follow(A)
添加到 Follow(B)
A - > BC
C - > epsilon
将 Follow(A)
添加到 Follow(C)
A -> C
若最右侧存在终结符α
,且终结符左侧存在非终结符B
,将α
添加到Follow(B)
A -> Bα
回到步骤2.
若过程中有Follow
集发生变化,则继续执行步骤2
否则结束
考虑文法
E -> TE'
E' -> +TE' | epsilon
T -> FT'
T' -> *FT' | epsilon
F -> (E) | id
最终结果
符号 | First | Follow |
---|---|---|
E | ( , id | $,) |
E’ | +, epsilon | $,) |
T | ( , id | +,$,) |
T’ | *, epsilon | +,$,) |
F | ( , id | *,+,$,) |
分析第一个产生式E -> TE'
E 本身是一个句型,Follow集中添加 $
T 的Follow集添加 E’ 的 First集:+,epsilon
由于 E’ 可以推出 epsilon,所以 E 的Follow集添加到 T 的Follow集
E’ 是产生式的最后一个字符,因此可以跟在 E 后的符号,也可以跟在 E’ 后,将 E 的Follow集添加到 E’ 的Follow集
对于第二个产生式E' -> +TE' | epsilon
,TE’ 的位置与第一个产生式相同,跳过
分析第三个产生式T -> FT'
T 本身是一个句型,Follow集中添加 $
F 的Follow集添加 T’ 的 First集:*
由于 T’ 可以推出 epsilon,所以 T 的Follow集添加到 F 的Follow集
T’ 是产生式的最后一个字符,因此可以跟在 T 后的符号,也可以跟在 T’ 后,将 T 的Follow集添加到 T’ 的Follow集
分析第四个产生式:T' -> *FT' | epsilon
,FT’ 的位置与第三个参数是相同,跳过
分析第五个产生式:F -> (E) | id
F 本身是一个句型,Follow集中添加 $
推出的式子中,非终结符 E 的右侧为 ),添加到到 E 的Follow集中
处理Follow集的依赖关系
第一个产生式E -> TE'
E’ 依赖 E,因此将 E 的Follow集添加到 E’ 的Follow集
E‘ 可以推出 epsilon,T 依赖 E,将 E 的Follow集添加到T的Follow集
对于第二个产生式E' -> +TE' | epsilon
,TE’ 的位置与第一个产生式相同,跳过
分析第三个产生式T -> FT'
T’ 依赖 T,将 T 的 Follow集添加到 T’ 的Follow集
T’ 可以推出 epsilon,F 依赖 T,将 T 的Follow集添加到 F 的Follow集
分析第四个产生式:T' -> *FT' | epsilon
,FT’ 的位置与第三个参数是相同,跳过
分析第五个产生式:F -> (E) | id
,没有依赖关系
结束
LL(1) 对输入串进行分析,是根据预测分析表进行的
select
集合
select(A)
:A 可以接收什么样的输入符号A -> BC
,select 集合为:
B
是终结符,那么 select(A -> BC) = B
B
是非终结符,那么 select(A -> BC) = First(B)
BC
为 epsilon
,那么 select(A -> BC) = Follow(A)
预测分析表的组成
select
集合对应的列,填上对应的产生式考虑文法
E -> TE'
E' -> +TE' | epsilon
T -> FT'
T' -> *FT' | epsilon
F -> (E) | id
计算 First
与 Follow
集合
符号 | First | Follow |
---|---|---|
E | ( , id | $,) |
E’ | +, epsilon | $,) |
T | ( , id | +,$,) |
T’ | *, epsilon | +,$,) |
F | ( , id | *,+,$,) |
对于文法中的每一个产生式,若有选择,进行分解
例如:E' -> +TE' | epsilon
E' -> +TE'
E' -> epsilon
本例中的分析表
分解后的文法
E -> TE'
E' -> +TE'
E' -> epsilon
T -> FT'
T' -> *FT'
T' -> epsilon
F -> (E)
F -> id
非终结符 \ 输入符号 | id | + | * | ( | ) | $ |
---|---|---|---|---|---|---|
E | E -> TE’ | E -> TE’ | ||||
E’ | E’ -> +TE’ | E’ -> epsilon | E’ -> epsilon | |||
T | T -> FT’ | T -> FT’ | ||||
T’ | T’ -> epsilon | T’ -> *FT’ | T’ -> epsilon | T’ -> epsilon | ||
F | F -> id | F -> (E) |
从非终结符 E
推导的产生式有:
E -> TE'
其中,输入符号为 First(T) = { id, ( }
那么 E
这一行,对应输入符号的列id, (
就填上该产生式
非终结符 \ 输入符号 | id | + | * | ( | ) | $ |
---|---|---|---|---|---|---|
E | E -> TE’ | E -> TE’ |
从非终结符E'
推导的产生式有:
E' -> +TE'
E' -> epsilon
E' -> +TE'
,输入符号为 +
那么 E'
这一行,对应输入符号的列+
就填上该产生式
非终结符 \ 输入符号 | id | + | * | ( | ) | $ |
---|---|---|---|---|---|---|
E’ | E’ -> +TE’ |
E' -> epsilon
,输入符号为Follow(E') = { ), $ }
那么 E'
这一行,对应输入符号的列), $
就填上该产生式
非终结符 \ 输入符号 | id | + | * | ( | ) | $ |
---|---|---|---|---|---|---|
E’ | E’ -> epsilon | E’ -> epsilon |
其余非终结符的推导同理
有分析表
非终结符 \ 输入符号 | id | + | * | ( | ) | $ |
---|---|---|---|---|---|---|
E | E -> TE’ | E -> TE’ | ||||
E’ | E’ -> +TE’ | E’ -> epsilon | E’ -> epsilon | |||
T | T -> FT’ | T -> FT’ | ||||
T’ | T’ -> epsilon | T’ -> *FT’ | T’ -> epsilon | T’ -> epsilon | ||
F | F -> id | F -> (E) |
预测分析器接收输入句子:id * id + id
先将 $
与 开始符号E
压入栈,将句子连接$
作为输入
栈 | 输入 | 输出 |
---|---|---|
$E | id * id + id $ |
弹出栈顶元素 E
,输入队列的队头id
,在分析表中对应的产生式为:E -> TE'
,输出为TE'
栈 | 输入 | 输出 |
---|---|---|
$ | id * id + id $ | E -> TE’ |
将 TE'
逆序入栈
栈 | 输入 | 输出 |
---|---|---|
$E’T | id * id + id $ |
弹出栈顶元素 T
,输入队列的队头id
,在分析表中对应的产生式为:T -> FT'
,输出为FT'
栈 | 输入 | 输出 |
---|---|---|
$E’ | id * id + id $ | T -> FT’ |
将 FT'
逆序入栈
栈 | 输入 | 输出 |
---|---|---|
$E’T’F | id * id + id $ |
弹出栈顶元素 F
,输入队列的队头id
,在分析表中对应的产生式为:F -> id'
,输出为id'
栈 | 输入 | 输出 |
---|---|---|
$E’T’ | id * id + id $ | F -> id |
将 id
逆序入栈
栈 | 输入 | 输出 |
---|---|---|
$E’T’id | id * id + id $ |
此时栈顶与队头相同,弹出栈顶与队头
栈 | 输入 | 输出 |
---|---|---|
$E’T’ | * id + id $ |
以此类推,每当栈顶为终结符时,与输入队列队头对比,若相同,弹出栈顶与队头
若栈顶与队头不同,或栈顶无队头对应的输入,则拒绝该语句
归约
将产生式右侧的串,替换成产生式左侧的非终结符
S -> ab
ab
,则可以将ab
归约成S
句柄 :句型中,与产生式右侧相等的子串(可以被归约的串),并且该归约是最右推导中逆过程的一步
最右推导:任何一次推导,都是对产生式最右侧的非终结符进行替换
句型:从文法的开始符号出发,能够推导出的一切字符串(有限长度)
E -> E * E | E + E
对于句型E * E + E
,E + E
是该句型的句柄
为什么E * E
不是?
因为E -> E + E
是句型中最右推导逆过程的一步,若先归约E * E
,则不满足最右推导的要求
E -> E + E
E -> E * E
E -> (E)
E -> id
栈 | 输入 | 动作 |
---|---|---|
$ | id1 * id2 + id3 $ | 移进 |
$ id1 | * id2 + id3 $ | 按 E -> id 归约 |
$ E | * id2 + id3 $ | 移进 |
$ E * | id2 + id3 $ | 移进 |
$ E * id2 | + id3 $ | 按 E -> id 归约 |
$ E * E | + id3 $ | 移进 |
$ E * E + | id3 $ | 移进 |
$ E * E + id3 | $ | 按 E -> id 归约 |
$ E * E + E | $ | 按 E -> E + E |
$ E * E | $ | 按 E -> E * E |
$ E | $ | ACCEPT |
考虑文法
E -> E + E
E -> E + E + id
E -> id
输入串为id + id + id
栈 | 输入 | 动作 |
---|---|---|
$ | id + id + id $ | 移进 |
$ id | + id + id $ | 按 E -> id 归约 |
$ E | + id + id $ | 移进 |
$ E + | id + id $ | 移进 |
$ E + id | + id $ | 按 E -> id 归约 |
$ E + E | + id $ | 冲突 |
对于栈$ E + E
和输入+ id $
有两种选择,发生冲突
E + E
,可以按E -> E + E
进行归约+ id
移入栈,先按 E -> id
归约,再按E -> E + E + id
归约这种不确定性可能会导致某种选择分析失败
LR 分析器
LR 分析算法需要借助分析表,分析表的构建方式有多种,但是分析方法是一样的
E → E + T (1) E \to E + T \tag{1} E→E+T(1)
E → T (2) E \to T \tag{2} E→T(2)
T → T ∗ F (3) T \to T * F\tag{3} T→T∗F(3)
T → F (4) T \to F\tag{4} T→F(4)
F → ( E ) (5) F \to (E)\tag{5} F→(E)(5)
F → i d (6) F \to id\tag{6} F→id(6)
分析表
状态 | id | + | * | ( | ) | $ | E | T | F |
---|---|---|---|---|---|---|---|---|---|
0 | s5 | s4 | 1 | 2 | 3 | ||||
1 | s6 | acc | |||||||
2 | r2 | s7 | r2 | r2 | |||||
3 | r4 | r4 | r4 | r4 | |||||
4 | s5 | s4 | 8 | 2 | 3 | ||||
5 | r6 | r6 | r6 | r6 | |||||
6 | s5 | s4 | 9 | 3 | |||||
7 | s5 | s4 | 10 | ||||||
8 | s6 | s11 | |||||||
9 | r1 | s7 | r1 | r1 | |||||
10 | r3 | r3 | r3 | r3 | |||||
11 | r5 | r5 | r5 | r5 |
sj
:将当前符号和状态j
移入栈rj
:将栈顶串按第j
个产生式进行归约acc
:接收对于输入串 i d 1 ∗ i d 2 + i d 3 id_1 * id_2 + id_3 id1∗id2+id3,LR 的非递归预测过程如下图
拓广文法
设文法的开始符号为S
,添加S' -> S
为什么要引入拓广文法?
考虑文法:
E -> E + E
E -> E * E
E -> id
显然,该文法的开始符号为 E
,若成功分析输入串,则必然由某条产生式归约到E
但是,该文法有 3
条产生式都能够归约到E
,这样接收状态对应的产生式就不确定
若引入了E' -> E
,则接收状态对应的产生式是唯一确定的
LR(0)
项目
简称项目
在产生式的右侧的某个位置加·
考虑产生式:A -> XYZ
共有 4
个项目
A -> ·XYZ
A -> X·YZ
A -> XY·Z
A -> XYZ·
项目集
由多个项目组成的集合,一般用符号I
表示
后继项目
·
的出现位置向右移动一位A -> ·XY
A -> X·Y
归约项目
若项目的·
在产生式的最右侧,则称该项目为归约项目
例如A -> XY·
closure(I)
项目集 I
的闭包
构造规则
初始时,I
中的每一个项目都加入closure(I)
若I
中有形如A -> α·B
的项目,将所有 B
的产生式 B -> ·C
添加到closure(I)
若C
为非终结符,则将 C -> ·D
添加到closure(I)
有点类似求 FIrst
集合这是一个递归的过程
goto(I, X)
I
:项目集
X
:文法符号
对于项目集 I
中的每一个项目,输入文法符号 X
后的产生项目
能否输入符号X
:项目中·
右侧的符号是否为X
考虑项目:A -> ·BC
,输入符号 C
无法产生任何项目
考虑项目:A -> ·BC
,输入符号 B
产生的项目为:B·C
项目集的规范族
colsure
闭包得到的项目考虑文法
E' -> E
E -> E + T
E -> T
T -> T * F
T -> F
F -> (E)
F -> id
I0
表示初始项目集
核心项目:E' -> ·E
添加closure({E' -> ·E})
E -> ·E + T
E -> ·T
T -> ·T * F
T -> ·F
F -> ·(E)
F -> ·id
I0
最终为:
E' -> ·E
E -> ·E + T
E -> ·T
T -> ·T * F
T -> ·F
F -> ·(E)
F -> ·id
I1
I0
可能的输入有:E, T, F, (, id
其中 E
尚未作为I0
的输入
I1
的核心项目为goto(I0, E)
,有:
E' -> E·
E -> E· + T
其他的产生式都无法输入
E
,例如E -> ·T
,只能输入T
核心项目的闭包为空
E' -> E·
,·
的右侧为空E -> E· + T
,·
的右侧为终结符+
I1
最终为:
E' -> E·
E -> E· + T
I2
I0
可能的输入有:E, T, F, (, id
其中 T
尚未作为I0
的输入
I2
的核心项目为goto(I0, T)
,有:
E -> T·
T -> T· * F
核心项目的闭包为空
I2
最终为:
E -> T·
T -> T· * F
I3
I0
可能的输入有:E, T, F, (, id
其中 F
尚未作为I0
的输入
I3
的核心项目为goto(I0, F)
,有:
T -> F·
核心项目的闭包为空
I3
最终为:
T -> F·
I4
I0
可能的输入有:E, T, F, (, id
其中(
尚未作为I0
的输入
I4
的核心项目为goto(I0, ( )
,有:
F -> (·E)
核心项目的闭包为:
E -> ·E + T
E -> ·T
T -> ·T * F
T -> ·F
F -> ·(E)
F -> ·id
I4
最终为:
F -> (·E)
E -> ·E + T
E -> ·T
T -> ·T * F
T -> ·F
F -> ·(E)
F -> ·id
I5
I0
可能的输入有:E, T, F, (, id
其中id
尚未作为I0
的输入
I4
的核心项目为goto(I0, id)
,有:
F -> id·
核心项目的闭包为空
I5
最终为:
F -> id·
I6
I1
可能的输入有:+
其中+
尚未作为I1
的输入
I6
的核心项目为goto(I1, +)
,有:
E -> E +· T
核心项目的闭包为:
T -> ·T * F
T -> ·F
F -> ·(E)
F -> ·id
接下来考虑I2
可能的输入
需要注意的是,若I7
与求出的I
完全相同,那么视为无状态转换
若考虑完所有的 I
,都没有状态转换,那么结束
项目集 I i I_i Ii
I0
输入 E 到 I1
,E 是非终结符,因此I0
行, E 列,就应该填 1(从 0 转移到 1)
I0
输入 (
到 I4
,I4
中没有归约状态,因此 I0
行 (
列,填 s 4 s_4 s4
I2
中有 E → T ⋅ E \to T· E→T⋅,因此 F o l l o w ( E ) = { + , ) , $ } Follow(E) = \{+, ), $ \} Follow(E)={+,),$} 对应的元素 k k k, a c t i o n [ 2 , k ] = r 2 action[2,k]=r_2 action[2,k]=r2,2 是 E → T E\to T E→T 的编号
状态转换图中,没有指向其他状态的状态,都是归约状态
考虑文法
S -> V = E
S -> E
V -> * E
V -> id
E -> V
构建的 SLR 分析表中,action[2, =] = s6
,且 Folllow(E)
包含 =
,使得 action[2, =] = r5
那么状态 2
遇到 =
有 2
种动作,这种不确定性可能导致分析失败
对于项目A -> α·
,SLR 分析会将 action[i, Follow(A)]
按照 rj
进行归约,i
为当前项目集的编号,j
为A -> α
的编号
V = E
,E
只有遇到 $
时,才会按照S -> V = E
进行归约E
的 Follow
集合中还有 =
,按照 SLR
分析action[i, =] = rj
,这就会导致不应该的归约Follow
集合的子集考虑文法
S -> V = E
S -> E
V -> * E
V -> id
E -> V
最右侧的叶子节点V
归约成E
时,只有遇到$
才会进行E -> V·
归约(自下而上分析)
E -> V·
改写为E -> V·,$
$
时,才能将栈顶的V
归约成E
E -> v,=
规范 LR(1)项目定义为
A → α ⋅ β , a A \to α·β,a A→α⋅β,a
a
:展望符(也叫搜索符)
β = ε
时,遇到 a
才能按照A -> α
进行归约β ≠ ε
时,a
没用A → α ⋅ B β , a A \to \alpha · B \beta ,a A→α⋅Bβ,a
B → ⋅ γ , b B \to ·\gamma,b B→⋅γ,b
其中 b 为 B → γ B \to \gamma B→γ 的展望符
b = F i r s t ( β ) b = First(\beta ) b=First(β)
而 β \beta β 有可能是 ϵ \epsilon ϵ,因此 b = F i r s t ( β a ) b = First(\beta a) b=First(βa)
当 β → ϵ \beta \to \epsilon β→ϵ 时, b = a b=a b=a称为继承的后继符,否则叫自生的后继符
考虑文法
S ′ → S (0) S' \to S \tag{0} S′→S(0)
S → L = R (1) S \to L = R \tag{1} S→L=R(1)
S → R (2) S \to R \tag{2} S→R(2)
L → ∗ R (3) L \to * R \tag{3} L→∗R(3)
L → i d (4) L \to id \tag{4} L→id(4)
R → L (5) R \to L \tag{5} R→L(5)
I0
S ′ → ⋅ S , $ S' \to ·S,\$ S′→⋅S,$
S 后为空串,那么由 S 推出的产生式中,展望符继承 S ′ → ⋅ S , $ 的展望符 S 后为空串,那么由 S 推出的产生式中,展望符继承 S' \to ·S,\$ 的展望符 S后为空串,那么由S推出的产生式中,展望符继承S′→⋅S,$的展望符
S → ⋅ L = R , $ S \to ·L = R,\$ S→⋅L=R,$
S → ⋅ R , $ S \to ·R,\$ S→⋅R,$
S → ⋅ L = R , $ 中, ⋅ 后为 L ,而 L 后的第一个终结符为 = ,所以由 L 推出的产生式,展望符为 = S \to ·L = R,\$中,·后为L,而L后的第一个终结符为=,所以由 L 推出的产生式,展望符为 = S→⋅L=R,$中,⋅后为L,而L后的第一个终结符为=,所以由L推出的产生式,展望符为=
L → ⋅ ∗ R , = L \to ·*R,= L→⋅∗R,=
L → ⋅ i d , = L \to ·id,= L→⋅id,=
R 后为空串,由 R 推出的产生式中,展望符继承 S → ⋅ R , $ 的展望符 R后为空串,由R推出的产生式中,展望符继承 S \to ·R,\$ 的展望符 R后为空串,由R推出的产生式中,展望符继承S→⋅R,$的展望符
R → ⋅ L , $ R \to ·L,\$ R→⋅L,$
L 后为空串,由 L 推出的产生式中,展望符继承 R → ⋅ L , $ 的展望符 L后为空串,由L推出的产生式中,展望符继承R \to ·L,\$的展望符 L后为空串,由L推出的产生式中,展望符继承R→⋅L,$的展望符
L → ⋅ ∗ R , $ L \to ·*R,\$ L→⋅∗R,$
L → ⋅ i d , $ L \to ·id,\$ L→⋅id,$
I1
I0
输入S
S ′ → S ⋅ , $ S' \to S·,\$ S′→S⋅,$
I2
I0
输入L
S -> ·L=R,$
R -> ·L,$
⋅ 移动一位称为后继项目,后继项目的展望符继承自原项目 ·移动一位称为后继项目,后继项目的展望符继承自原项目 ⋅移动一位称为后继项目,后继项目的展望符继承自原项目
S → L ⋅ = R , $ S \to L·=R,\$ S→L⋅=R,$
R → L ⋅ , $ R \to L·,\$ R→L⋅,$
I3
I0
输入R
S → R ⋅ , $ S \to R·,\$ S→R⋅,$
I4
I0
输入*
L → ∗ ⋅ R , = L \to *·R,= L→∗⋅R,=
L → ∗ ⋅ R , $ L \to *·R,\$ L→∗⋅R,$
R 后是 $ , R 推出的产生式的展望符继承 L → ∗ ⋅ R , = 的展望符 R后是\$,R推出的产生式的展望符继承L \to *·R,= 的展望符 R后是$,R推出的产生式的展望符继承L→∗⋅R,=的展望符
R → ⋅ L , = R \to ·L,= R→⋅L,=
R 后是 $ , R 推出的产生式的展望符继承 L → ∗ ⋅ R , $ 的展望符 R 后是 \$,R推出的产生式的展望符继承L \to *·R,\$的展望符 R后是$,R推出的产生式的展望符继承L→∗⋅R,$的展望符
R → ⋅ L , $ R \to·L,\$ R→⋅L,$
L 后面是空串, L 推出的产生式继承 R → ⋅ L , = 、 R → ⋅ L , $ 的展望符 L 后面是空串,L 推出的产生式继承 R \to ·L,=、R \to ·L,\$ 的展望符 L后面是空串,L推出的产生式继承R→⋅L,=、R→⋅L,$的展望符
L → ⋅ ∗ R , = L \to ·*R,= L→⋅∗R,=
L → ⋅ i d , = L \to ·id,= L→⋅id,=
L → ⋅ ∗ R , $ L \to ·*R,\$ L→⋅∗R,$
L → ⋅ i d , $ L \to·id,\$ L→⋅id,$
I5
I0
输入id
L → ⋅ i d , = L \to ·id,= L→⋅id,=
L → ⋅ i d , $ L \to·id,\$ L→⋅id,$
I6
I2
输入=
S → L = ⋅ R , $ S \to L=·R,\$ S→L=⋅R,$
R 后面是空串, R 推出的产生式继承 S → L = ⋅ R , $ 的展望符 R 后面是空串,R 推出的产生式继承 S \to L=·R,\$ 的展望符 R后面是空串,R推出的产生式继承S→L=⋅R,$的展望符
R → ⋅ L , $ R \to ·L,\$ R→⋅L,$
L 后面是空串, L 推出的产生式继承 R → ⋅ L , $ 的展望符 L后面是空串,L推出的产生式继承R \to ·L,\$的展望符 L后面是空串,L推出的产生式继承R→⋅L,$的展望符
L → ⋅ ∗ R , $ L \to ·*R,\$ L→⋅∗R,$
L → ⋅ i d , $ L \to ·id,\$ L→⋅id,$
I7
I4
输入R
L → ∗ R ⋅ , = L \to *R·,= L→∗R⋅,=
L → ∗ R ⋅ , $ L \to *R·,\$ L→∗R⋅,$
I8
I4
输入L
R → L ⋅ , = R \to L·,= R→L⋅,=
R → L ⋅ , $ R \to L·,\$ R→L⋅,$
I9
I6
输入R
S → L = R ⋅ , $ S \to L=R·,\$ S→L=R⋅,$
I10
I6
输入L
R → L ⋅ , $ R \to L·,\$ R→L⋅,$
I11
I6
输入*
L → ∗ ⋅ R , $ L \to *·R,\$ L→∗⋅R,$
R → ⋅ L , $ R \to ·L,\$ R→⋅L,$
L → ⋅ ∗ R , $ L \to ·*R,\$ L→⋅∗R,$
L → ⋅ i d , $ L \to ·id,\$ L→⋅id,$
I12
I6
输入id
L → i d ⋅ , $ L \to id·,\$ L→id⋅,$
I13
I11
输入R
L → ∗ R ⋅ , $ L \to *R·,\$ L→∗R⋅,$
S' -> S
S -> BB
B -> bB
B -> a
考虑文法
S' -> S
S -> BB
B -> bB | a
考虑两个相似的项目集合
B → a ⋅ , b ∣ a (I4) B \to a·,b|a \tag{I4} B→a⋅,b∣a(I4)
B → a ⋅ , $ (I7) B \to a·,\$ \tag{I7} B→a⋅,$(I7)
这两个项目集合除了展望符(搜索符)不同以外,其余都是相同的
可以将同行项目进行合并,例如合并 I4
和 I7
,命名为 I47
B → a ⋅ , b ∣ a ∣ $ B \to a·,b|a|\$ B→a⋅,b∣a∣$
转换图中,原先到达I4
或I7
的,现在都转移到I47
状态转换图中,没有指向其他状态的状态,都是归约状态
冲突
移进-归约
冲突归约-归约
冲突编译的几个阶段
语义分析
和中间代码生成
可以同时进行,称为语义翻译语法分析
、语义分析
、中间代码生成
可以同时进行,称为语法制导翻译句子:属于该语言的串
语法分析树
SDD
语法制导定义(Syntax-Directed Definition
)
语法制导翻译
仅用语法分析对源代码进行翻译
语法制导
带属性和语义规则的上下文无关文法
每个文法符号都有一组属性
例如文法符号 S
,S
可能有 name
,val
属性,等等。
这些属性名(name
、val
,等等)都是自定义的
每个产生式有一组语义规则
例如产生式E -> E + T
这样的产生式只是符号的推导,没有任何意义,并且+
只是个符号,不一定是数学中的加法
但是如果定义一组语义规则,例如E.val = E.val + T.val
那么这个产生式的语义就是:将 E 的 val 属性和 T 的 val 属性相加,再赋值给 E 的 val 属性
综合属性
继承属性
定义终结符的属性为:综合属性
终结符是无法作为产生式左侧符号的,终结符的值由词法分析器提供
判断一个属性是继承属性还是综合属性
只要看这个属性来自产生式左侧还是右侧即可,来自左侧是综合,来自右侧是继承
副作用
语义规则不是表达式,而是一些其他功能,例如打印
属性文法
一个没有副作用的文法称为属性文法
注释分析树:每个节点的属性值都标出出来
如果一条语义规则的作用和求值无关,如打印一个值或向符号表中添加记录,则成为产生式左侧非终结符的虚拟综合属性
语法分析树是针对分析过程的,语法树是针对串的
addtype(param1, param2)
param1
,属性值为 param2
L
(产生式左侧)的虚拟综合属性分析串 i n t i d 1 , i d 2 , i d 3 int \quad id_1, id_2, id_3 intid1,id2,id3
如果只看语法分析树,会发现,分析过程是完全自上而下的
但是如果考虑属性值,则并非如此
自下而上
T -> int
,将 int
(integer
) 赋值给了 T.type
兄弟节点
D -> TL
, T.type
赋值给L.in
,那么就要求 T.type
先计算出来
自上而下
L -> L1 , id
,将 L.in
赋值给 L1.in
,那么就要求 L1.in
先计算出来
分析树上属性的计算有依赖关系,可以用依赖图(有向图)来表示
依赖图本质上是一个拓扑图
综合属性写在符号的右边
继承属性写在符号的左边
属性的编号,从小到达,表示先后顺序
顺序并不是固定的
例如1, 2, 3, 4
是没有依赖关系的,因此可以任意替换顺序
4, 5
显然不能替换顺序,因为 L.in = T.type
,5
是依赖 4
的
6
(addType(id.entry, L.in)
)是有多个依赖项的
前面提到,这种非表达式的属性,称为副作用,也叫虚拟综合属性
addType
这个操作需要 id.entry
和 L.in
,因此 6
是依赖 3
和 5
的
S 属性定义
仅使用综合属性的 SDD
如果一个 SDD 是 S 属性的,那么可以根据语法树的任何 自底向上 顺序来计算属性值
L 属性定义
每个 S 属性定义都是 L 属性定义
综合属性定义,产生式左侧的依赖产生式右侧的属性,也就是依赖图的边从左到右
语法制导翻译SDT
(Semantic Directed Translation)
翻译方案中,将语义规则改了个名字,叫语义动作
将语义动作写在{}
内,并插入到产生式右侧
对于综合属性,语义动作插入到产生式最右侧
对于继承属性,语义动作插入到该符号的左侧
语法制导翻译中,不仅要分析输入串,分析过程中,还要执行相应的语义动作。语义动作中,一般会设计属性的计算,因此属性也需要保存在栈中
示例
形如 E → T E \to T E→T 的产生式,语义规则只要不是副作用,代码段可以省略
因为 E . v a l = T . v a l E.val = T.val E.val=T.val 这种语义规则,翻译成代码段就是 s t a c k [ t o p ] . v a l = s t a c k [ t o p ] . v a l stack[top].val = stack[top].val stack[top].val=stack[top].val,没有改变值,因此可以忽略
示例
与 LR 分析不同的是,S 属性定义的计算中,多出了状态栈
但由于 SDD 中,只有一个 val 属性,因此将状态栈简化为 val 栈
分析输入串 d i g i t + d i g i t digit + digit digit+digit,其中词法分析器的输入为 1 , 2 1, 2 1,2,即 1 + 2 1 + 2 1+2
对于每一个移进状态,将输入串同时移入 stack 栈和 val 栈
对于每一个归约状态
考虑代码段,代码段主要对应 val 栈
result
入栈SDD中存在左递归(如 E → E + T E \to E + T E→E+T 出现了左递归),而自上而下分析处理不了左递归,因此先消去左递归
L -> En
E -> TR
R -> +TR
R -> epsilon
T -> FM
M -> *FM
M -> epsilon
F -> (E)
F -> digit
语法制导翻译方案 SDT
L -> En {print(E.val)}
E -> T {R.i = T.val} R {E.val = R.s}
R -> +T {R1.i = R.i + T.val} R {R.s = R1.i}
R -> epsilon {R.s = R.i}
T -> F {M.i = F.val} M {T.val = M.s}
M -> *F {M1.i = M.i * F.val} M {M.s = M1.i}
M -> epsilon {M.s = M.i}
F -> (E) {F.val = E.val}
F -> digit {F.val = digit.lexval}
- R.i:R之前的表达式的值
- R.s:分析完 R 后的值
- M 同理
求解 First, Follow 集合
符号 | First | Follow |
---|---|---|
L | (, digit | $ |
E | (, digit | $, ) |
R | +, epsilon | $, ) |
T | (, digit | $, ), + |
M | *, epsilon | $, ), + |
F | (, digit | $, ), +, * |
构建 LL(1) 分析表(非终结符的select
集合)
( | ) | + | * | digit | $ | |
---|---|---|---|---|---|---|
L | L->E | L->E | ||||
E | E->TR | E->TR | ||||
R | R->epsilon | R->+TR | R->epsilon | |||
T | T->FM | T->FM | ||||
M | M->epsilon | M->epsilon | M->*FM | M->epsilon | ||
F | F->(E) | F->digit |
遍历每一条产生式 A → a A \to a A→a
a 是 epsilon
Follow(A) 中的每一个符号对应的列,添加产生式
a 不是 epsilon
First(a) 中的每一个符号对应的列,添加产生式
分析串 d i g i t + d i g i t digit + digit digit+digit,词法分析器的输入是 1, 2
LL(1) 分析,用非递归的栈,过程更清晰
由于该文法只有一种属性 val,因此属性栈可以默认为 val 栈
$ 的一侧为栈底
分析过程
栈 | 输入 | 动作 | |
---|---|---|---|
L$ | digit + digit$ | L -> E {print(E.val)} | |
E {print(E.val)} $ | digit + digit$ | E -> T {R.i = T.val} R {E.val = R.s} | |
T {R.i = T.val} R {E.val = R.s} {print(E.val)} $ | digit + digit$ | T -> F {M.i = F.val} M {T.val = M.s} | |
F {M.i = F.val} M {T.val = M.s} {R.i = T.val} R {E.val = R.s} {print(E.val)} $ | digit + digit$ | F -> digit {F.val = digit.lexval} | |
digit {F.val = digit.lexval} {M.i = F.val} M {T.val = M.s} {R.i = T.val} R {E.val = R.s} {print(E.val)} $ | digit.lexval=1 | digit + digit$ | 匹配 digit |
{F.val = digit.lexval} {M.i = F.val} M {T.val = M.s} {R.i = T.val} R {E.val = R.s} {print(E.val)} $ | F.val=1 | + digit$ | 执行 {F.val = digit.lexval} |
{M.i = F.val} M {T.val = M.s} {R.i = T.val} R {E.val = R.s} {print(E.val)} $ | M.i=1 | + digit$ | 执行 {M.i = F.val} |
M {T.val = M.s} {R.i = T.val} R {E.val = R.s} {print(E.val)} $ | + digit$ | M -> epsilon {M.s = M.i} | |
epsilon {M.s = M.i} {T.val = M.s} {R.i = T.val} R {E.val = R.s} {print(E.val)} $ | + digit$ | 匹配 epsilon | |
{M.s = M.i} {T.val = M.s} {R.i = T.val} R {E.val = R.s} {print(E.val)} $ | + digit$ | 执行 {M.s = M.i} | |
{T.val = M.s} {R.i = T.val} R {E.val = R.s} {print(E.val)} $ | M.s=1 | + digit$ | 执行 {T.val = M.s} |
{R.i = T.val} R {E.val = R.s} {print(E.val)} $ | T.val=1 | + digit$ | 执行 {R.i = T.val} |
R {E.val = R.s} {print(E.val)} $ | R.i=1 | + digit$ | R -> +T {R1.i = R.i + T.val} R {R.s = R1.i} |
+T {R1.i = R.i + T.val} R {R.s = R1.i} {E.val = R.s} {print(E.val)} $ | + digit$ | 匹配 + | |
T {R1.i = R.i + T.val} R {R.s = R1.i} {E.val = R.s} {print(E.val)} $ | digit$ | T -> F {M.i = F.val} M {T.val = M.s} | |
F {M.i = F.val} M {T.val = M.s} {R1.i = R.i + T.val} R {R.s = R1.i} {E.val = R.s} {print(E.val)} $ | digit$ | F -> digit {F.val = digit.lexval} | |
digit {F.val = digit.lexval} {M.i = F.val} M {T.val = M.s} {R1.i = R.i + T.val} R {R.s = R1.i} {E.val = R.s} {print(E.val)} $ | digit$ | 匹配 digit | |
{F.val = digit.lexval} {M.i = F.val} M {T.val = M.s} {R1.i = R.i + T.val} R {R.s = R1.i} {E.val = R.s} {print(E.val)} $ | $ | 执行 {F.val = digit.lexval} | |
{M.i = F.val} M {T.val = M.s} {R1.i = R.i + T.val} R {R.s = R1.i} {E.val = R.s} {print(E.val)} $ | F.val=2 | $ | 执行 {M.i = F.val} |
M {T.val = M.s} {R1.i = R.i + T.val} R {R.s = R1.i} {E.val = R.s} {print(E.val)} $ | M.i=2 | $ | M -> epsilon {M.s = M.i} |
epsilon {T.val = M.s} {R1.i = R.i + T.val} R {R.s = R1.i} {E.val = R.s} {print(E.val)} $ | M.s=2 | $ | 匹配 epsilon |
{T.val = M.s} {R1.i = R.i + T.val} R {R.s = R1.i} {E.val = R.s} {print(E.val)} $ | $ | 执行 {T.val = M.s} | |
{R1.i = R.i + T.val} R {R.s = R1.i} {E.val = R.s} {print(E.val)} $ | T.val=2 | $ | 执行 {R1.i = R.i + T.val} |
R {R.s = R1.i} {E.val = R.s} {print(E.val)} $ | R1.i=1+2=3 | $ | R -> epsilon {R.s = R.i} |
epsilon {R.s = R.i} {R.s = R1.i} {E.val = R.s} {print(E.val)} $ | R.s=3 | $ | 匹配 esilon |
{R.s = R.i} {R.s = R1.i} {E.val = R.s} {print(E.val)} $ | $ | 执行 {R.s = R.i} | |
{R.s = R1.i} {E.val = R.s} {print(E.val)} $ | R.s = 3 | $ | 执行 {R.s = R1.i} |
{E.val = R.s} {print(E.val)} $ | R.s = 3 | $ | 执行 {E.val = R.s} |
{print(E.val)} $ | E.val=3 | $ | 执行 {print(E.val)} |
$ | $ | 匹配 $ | |
ACC |
若 E 是变量或常数,那么 E 的后缀表示就是本身
若 E 是形如 E 1 o p E 2 E_1 op E_2 E1opE2 的表达式,那么后缀表示是 E 1 ′ E 2 ′ o p E_1' E_2' op E1′E2′op
若 E 是形如 ( E 1 ) (E_1) (E1)的表达式,那么后缀表达式是 E 1 E_1 E1 的后缀表达式
示例
语法分析树是针对分析过程的,语法树是针对串的
DAG
(D
irected A
cyclic G
raph)t1 = y * z
t2 = x + t1
t 1 , t 2 t_1,t_2 t1,t2 是编译器产生的临时变量(的名字),用于保存中间结果。t1 = -b
t2 = c * d
t3 = t1 + t2
t4 = c * d
t5 = t3 + t4
a = t5
t1 = -b
t2 = c * d
t3 = t1 + t2
t4 = t3 + t2
a = t4
DAG的三地址代码更简洁一些1(每条指令的代价) + 源地址(source)模式的附加代价 + 目的地址(target)模式的附加代价
示例
MOV R0 R1
R0
的内容写到寄存器 R1
,由于寄存器的附加代价是0,并且目标地址和源地址都是寄存器,指令本身代价为 1,因此这条指令的代价是:1MOV R5 M
R5
的内容写到 M
,即内存中(Memory),其中源地址是寄存器,附加代价为 0,而目的地址为内存(绝对地址),附加代价为 1,指令本身代价为 1,因此这条指令的代价是:2ADD #1 R3
R3
中,常数的附加代价是 1,寄存器的附加代价是 0,指令本身的代价是 1,因此这条指令的代价是:2SUB 4(R0) *12(R1)
contetns( contens( 12 + contens(R1) ) )
减去 contents( 4 + contens(R0) )
c(R)
和 *c(R)
的附加代价都是 1,指令本身代价为 1,因此这条指令的代价是:3相同表达式的不同指令
a, b, c
都存在内存中MOV b R0
ADD c R0
MOV R0 a
代价是 6MOV b a
ADD c a
代价是 6R0, R1, R2
分别存放 a, b, c
的地址MOV *R1 *R0
ADD *R2 *R0
代价是 2R1, R2
存放 b, c
的值ADD R2 R1
MOV R1 a
代价是 3