感谢Bilibili University的 致爱意,语法分析部分她的讲解视频提供了很大的帮助
程序:一系列指令或语句,用来描述计算机依次要执行的一系列工作。本质上是描述一定数据的处理过程
程序的层次结构:
结构:基本符号(字母,数组,符号等)、单词、表达式、语句、分程序、程序
程序设计语言:所有该语言的程序的全体
编译程序:将程序翻译成汇编语言程序或机器语言程序(目标程序),然后再执行的翻译程序
主要过程:
语法分析之后产生语法单位;目标代码之前都是四元式
词法分析:从左至右读取字符流的源程序,识别单词并表示成机内表示
语法分析:依据源程序的语法规则把源程序的单词序列组成语法短语
语义分析:分析由语法分析器给出的语法单位的语义
中间代码生成:源程序的内部表示(三元式、四元式、逆波兰表达式)
代码优化:对代码进行等价变换提高执行效率[运行速度、节省存储空间](机器有关、机器无关)
目标代码生成
符号表管理:每一步都会用到
前端:词法、语法、语义、中间代码生成,与机器无关的代码优化
后端:与机器有关的代码优化、目标代码生成
便于程序移植
对源程序或源程序的中间表示从头到尾扫描一次
形式语言:只在语法意义下的语言
字母表 ∑ \sum ∑:元素的非空有穷集合
符号:字母表中的元素
文法:描述语言的语法结构的形式规则或语法规则
作用:用有穷集合表示无穷句子
文法四元组:
G
(
V
N
,
V
T
,
P
,
S
)
G(V_N,V_T,P,S)
G(VN,VT,P,S),简记为
G
[
S
]
G[S]
G[S]
V => W: V直接推导出W,W直接规约为V
句型 = { x ∣ S ⇒ ∗ x , 且 x ∈ V ∗ } \{x|S \stackrel{*}{\Rightarrow} x ,\ 且\ x\in V^* \} {x∣S⇒∗x, 且 x∈V∗},识别符号S也是句型
句子 = x , ( S ⇒ + x , 且 x ∈ V T ∗ ) x, (S \stackrel{+}{\Rightarrow} x ,\ 且\ x\in V_T^* ) x,(S⇒+x, 且 x∈VT∗), x x x仅由终结符号组成,句子也是句型
语言 L ( G ) = { x ∣ S ⇒ + x , 且 x ∈ V T ∗ } L(G) = \{x|S \stackrel{+}{\Rightarrow} x ,\ 且\ x\in V_T^* \} L(G)={x∣S⇒+x, 且 x∈VT∗},文法G的一切句子的集合
文法的等价: L ( G 1 ) = L ( G 2 ) L(G_1) = L(G_2) L(G1)=L(G2)
文法的类型:对任一产生式 α → β \alpha \rightarrow \beta α→β,
上下文相关: α 1 A α 2 → α 1 β α 2 \alpha_1A\alpha_2 \rightarrow \alpha_1\beta\alpha_2 α1Aα2→α1βα2, A ∈ V N , 其 他 是 V ∗ , β ≠ ϵ A\in V_N, 其他是V^*, \beta \ne \epsilon A∈VN,其他是V∗,β=ϵ
上下文无关: A → β A \rightarrow \beta A→β, A ∈ V N , β ∈ V ∗ A \in V_N, \beta \in V^* A∈VN,β∈V∗
最左推导:任何一步
α
⇒
β
\alpha \Rightarrow \beta
α⇒β 都是对
α
\alpha
α 中的最左非终结符进行替换
最右推导:任何一步
α
⇒
β
\alpha \Rightarrow \beta
α⇒β 都是对
α
\alpha
α 中的最右非终结符进行替换
最右推到被称为规范推导。规范推导得到的句型称为规范句型
文法二义性:一个文法存在某个句子对应两棵不同的语法树(最左最右推导的语法书不一样)
短语:内部节点的叶子结点
直接短语:直接长成的叶子结点
素短语:短语中(1)至少含有一个终结符(2)不含其它更小的素短语
句柄:最左最小的树对应的符号串。直接短语中最左
🌰1
🌰2
单词符号的种类:基本字、标识符、常数、运算符、界符(逗号、分号、括号、空白)
正规式和正规集:
正规集可以用正规表达式(正规式)表示
正规式是表示正规集的一种方法
一个字的集合是正规集当且仅当它能用正规式表示
ϵ
\epsilon
ϵ 和
ϕ
\phi
ϕ 都是
∑
\sum
∑ 上的正规式,它们所表示的正规集为
{
ϵ
}
\{\epsilon\}
{ϵ} 和
ϕ
\phi
ϕ
正规集为程序的单词集合
正规式为词法规则
确定有限自动机(DFA)
自动机五元式 M = ( S , ∑ , f , S 0 , F ) M = (S, \sum, f, S_0, F) M=(S,∑,f,S0,F)
S S S:有穷状态集
∑ \sum ∑:输入字母表
f f f:状态转换函数(单值映射)
S 0 S_0 S0:唯一初态
F F F:终态集(可空)
非确定有限自动机(NFA)五元式,区别是:
f f f:状态转换函数(非单值)
S 0 S_0 S0:初态集
结论:判定两个自动机等价性的算法是存在的
DFA最少化的基本思想:
把M的状态集划分成一些不想交的自己,使得任何两个不同子集的状态是可区别的(不等价的),而同一子集的任何两个状态是等价的
关键问题:
G = ( V T , V N , S , P ) G = (V_T,V_N,S,P) G=(VT,VN,S,P)是上下文无关文法
F i r s t ( α ) = { a ∣ α ⇒ ∗ a β , a ∈ V T , α , β ∈ V ∗ } First(\alpha) = \{a | \alpha \stackrel{*}{\Rightarrow} a\beta, a\in V_T, \alpha,\beta \in V^* \} First(α)={a∣α⇒∗aβ,a∈VT,α,β∈V∗}, 若 α ⇒ ∗ ϵ \alpha \stackrel{*}{\Rightarrow} \epsilon α⇒∗ϵ,则规定 ϵ ∈ F i r s t ( α ) \epsilon \in First(\alpha) ϵ∈First(α)
F o l l o w = a ∣ S ⇒ ∗ μ A β , a ∈ F i r s t ( β ) , μ ∈ V ∗ , β ∈ V + Follow = {a | S \stackrel{*}{\Rightarrow} \mu A\beta, a \in First(\beta), \mu \in V^*, \beta \in V^+ } Follow=a∣S⇒∗μAβ,a∈First(β),μ∈V∗,β∈V+
First集看产生式左部,Follow集看产生式右部
First:
Follow:(针对B)
🌰1:
例子2:
类似LL(K),向前查看K个符号
分析条件(判断一个文法是LL(1)文法):
引出Select集: A → α , A ∈ V N , α ∈ V ∗ A \rightarrow \alpha, A\in V_N, \alpha \in V^* A→α,A∈VN,α∈V∗
所以LL(1)文法的满足条件是:
对于每个非终结符 A 的任意两条产生式,都满足 S e l e c t ( A → α ) ∪ S e l e c t ( A → β ) = ϕ Select(A\rightarrow \alpha) \cup Select(A\rightarrow \beta) = \phi Select(A→α)∪Select(A→β)=ϕ
例子:
构造时,看First集,找对应的产生式写在对应的位置;如果有\epsilon,则看Follow集,将产生式写在对应位置
基本思想:从输入串开始,逐步进行“规约”,直到文法的开始符号。即从树末端开始,构造语法树。
规约:根据文法的产生式规则,把产生式的右部替换为左部符号。“移进-规约”思想
关键问题:精确定义“可规约串”这个直观概念
G G G是文法, S S S是开始符号, α β δ \alpha \beta \delta αβδ 是文法的一个句型,
如果有:$ S \stackrel{*}{\Rightarrow} \alpha A \delta$ , A ⇒ + β A \stackrel{+}{\Rightarrow} \beta A⇒+β, 则 β \beta β 是句型 α β δ \alpha \beta \delta αβδ 相对于非终结符A的短语
特别是如果有: A ⇒ β A \Rightarrow \beta A⇒β, 则 β \beta β 是句型 α β δ \alpha \beta \delta αβδ 相对于规则 A → β A \rightarrow \beta A→β 的直接短语
一个句型的最左直接短语称为该句型的句柄(唯一性)。对于规范句型来说,句柄的后面只能出现终结符。
利用句柄作为“可规约串”进行的规约过程为规范规约,也称最左规约,因为形式语言中最右推导常被称为规范推导,若文法是无二义的,则规范推导的逆过程必是规范规约
在一个句型对应的语法树中:
素短语:一个文法G的句型的素短语是指至少含有一个终结符,并且除它自身以外不再含任何更小的素短语
最左素短语:处于句型最左边的素短语
🌰:文法G(E):
(1) E -> E+T | T
(2) T -> T*F | F
(3) F -> P
↑
\uparrow
↑F | F
(4) P -> (E) | i
对于句型: T+F*P+i
短语:T, F, P, i, F*P, T+F*P, T+F*P+i (两代以上子树的叶子结点)
直接短语:T, F, P, i (两代子树的叶子结点)
句柄:T (最左直接短语)
素短语:F*P, i (最深短语)
最左素短语:F*P
FirstVT:第一个终结符
LastVT:最后一个终结符
算符优先分析法不是一种规范规约法
算符文法:如果它的任一产生式的右部都不含两个相继(并列)的非终结符,即不含形如 …QR… 形式的产生式右部
根据表达式:
例子
算符优先文法句型: N 1 a 1 N 2 a 2 . . . N n a n N n + 1 N_1a_1N_2a_2...N_na_nN_{n+1} N1a1N2a2...NnanNn+1,其中每个 a i a_i ai 都是终结符, N i N_i Ni 是可有可无的非终结符
一个算符优先文法G的任何句型的最左素短语是满足如下条件的最左子串:
N
j
a
j
N
j
+
1
a
j
+
1
.
.
.
N
i
a
i
N
i
+
1
,
其
中
a
j
−
1
⋖
a
j
,
a
j
≐
a
j
+
1
,
.
.
.
,
a
i
−
1
≐
a
i
,
a
i
⋗
a
i
+
1
N_j a_j N_{j+1} a_{j+1}...N_i a_i N_{i+1},\ 其中 a_{j-1} \lessdot a_j,\ a_j \doteq a_{j+1},...,\ a_{i-1} \doteq a_i,\ a_i \gtrdot a_{i+1}
NjajNj+1aj+1...NiaiNi+1, 其中aj−1⋖aj, aj≐aj+1,..., ai−1≐ai, ai⋗ai+1
算符优先分析算法:
维护一个算符哟选关系单调不减的的栈,若栈顶的第一个终结符 ⋗ \gtrdot ⋗ 当前面对的输入串字符,则弹出一段具有 ≐ \doteq ≐ 关系的栈中串。利用某一产生式进行规约,此产生式的右部从左至右和弹出串从下至上一一对应,终结符对终结符,非终结符对非终结符(仅要求对应的终结符相同)
算法完成工作时,符号栈S应呈现 #N#,N是一个非终结符,但其不一定是开始符号
算符优先分析比规范规约要快得多,因为跳过了所有单非产生式(形如 P->Q)
算符优先分析法的关键问题是 —— 寻找最左素短语
定义:是一种自底向上进行规范规约的语法分析方法
实质:是一个带先进后出存储器(栈)的确定有限状态自动机
L:从左到右扫描输入串
R:构造一个最右推导的逆过程
四个操作动作:移进、规约、接受、报错
把“历史”和“展望”抽象成了状态,再结合当前输入符号的现实信息,即可完成LR分析
LR文法:
LR文法:能够构造一张分析表,它的每个入口都是唯一确定的
LR(K)文法:能用一个每步顶多向前检查K个输入符号的LR分析器进行分析
并非所有上下文无关文法都是LR文法
LR文法不是二义的,二义文法肯定不会是LR的
字的前缀:字的首部,包括空串 ϵ \epsilon ϵ
活前缀:规范句型的一个前缀,这种前缀不含句柄之后的任何符号(这些符号必为终结符串)
规范规约过程中,保证分析栈中总是活前缀,就说明分析采取的移进/规约动作是正确的
项目:G的文法G的每个产生式的右部添加一个圆点称为G的LR(0)的项目
A → ϵ A \rightarrow \epsilon A→ϵ 只对应一个项目:$A \rightarrow \ \cdotp $
文法的LR(0)项目规范族:构成识别一个文法活前缀的DFA项目集的全体
规约项目(
⋅
\cdotp
⋅ 在最后):
A
→
α
⋅
A \rightarrow \alpha \cdotp
A→α⋅
接受项目(开始文法对应的):
S
′
→
α
⋅
S' \rightarrow \alpha \cdotp
S′→α⋅
移进项目(
⋅
\cdotp
⋅后面是终结符):
A
→
α
⋅
a
β
,
(
a
∈
V
T
)
A \rightarrow \alpha \cdotp a\beta, (a \in V_T)
A→α⋅aβ,(a∈VT)
待约项目(
⋅
\cdotp
⋅后面是非终结符):
A
→
α
⋅
B
β
,
(
B
∈
V
N
)
A \rightarrow \alpha \cdotp B\beta, (B \in V_N)
A→α⋅Bβ,(B∈VN)
有效项目:任何时候,分析栈中的活前缀 x 1 x 2 . . . x m x_1 x_2 ... x_m x1x2...xm 的有效项目集正是栈顶状态 S m S_m Sm 所代表的那个集合
改造文法,引入唯一的“接收态”产生式 S’ -> S
计算 Closure(I) 和 Go(I,X)
Go(I,x) = Closure(J),其中 J = {任何形如A-> α x ⋅ β \alpha x\cdotp \beta αx⋅β | A-> α ⋅ x β \alpha \cdotp x\beta α⋅xβ 属于I}
直观上说,若 I 是对某个活前缀 γ \gamma γ 有效项目集,那么Go(I,x)便是对 γ x \gamma x γx 有效的项目集
以 {Closure({S’->S})} 为状态0,利用Go函数吧项目集连成一个DFA转换图
按照以下规则构造 Action 子表和 Goto 子表
假定LR(0)规范族的一个项目集 I 中含有 m 个一进项目,同时含有 n 个规约项目,如果所有规约项目左部的非终结符的Follow集两两不相交(包括不得有两个Follow集合有#),且不与任何移进项目接受的终结符 a 相交,则隐含在I中的动作冲突可通过检查现行输入符号a属于上述n+1个集合中的哪个集合而获得解决
SLR(1)解决办法:冲突性动作的这种解决办法
构造分析表与构造LR(0)分析表仅在第(2)步有所不同,只将属于Follow(A)的终结符写入 γ j \gamma_j γj
每个项目的一般形式是 [ A → α ⋅ β , a 1 a 2 . . . a k ] [A \rightarrow \alpha\cdotp \beta, a_1a_2...a_k] [A→α⋅β,a1a2...ak]这样的一个项目称为一个LR(K)项目,项目中的 a1…ak称为向前搜索串/展望串
向前搜索串仅对规约项目有意义,即需要分裂LR(0)中的项目
Closure(I):
转换函数Go:
令I是一个项目集,X是一个文法符号,函数Go(I,x)定义为: G o ( I , x ) = C l o s u r e ( J ) Go(I,x) = Closure(J) Go(I,x)=Closure(J)
其中 J = { 任 何 形 如 [ A → α X ⋅ β , a ] 的 项 目 ∣ [ A → α ⋅ X β , a ] ∈ I } J = \{ 任何形如[A \rightarrow \alpha X\cdotp \beta, a]的项目 | [A \rightarrow \alpha \cdotp X \beta, a] \in I\} J={任何形如[A→αX⋅β,a]的项目∣[A→α⋅Xβ,a]∈I}
基本思想与构造LR(0)分析表相同,具体:
三种规范规约关系:
LR(1)状态比SLR多
L R ( 0 ) ⊂ S L R ⊂ L R ( 1 ) ⊂ 无 二 义 文 法 LR(0) \subset SLR \subset LR(1) \subset 无二义文法 LR(0)⊂SLR⊂LR(1)⊂无二义文法
上下文无关文法的基础上,为每个文法符号(终结符/非终结符)配备若干相关的“值”(称为属性)
属性
终结符只有综合属性,由词法分析器提供
非终结符既可有综合属性也可有继承属性,文法开始符号的所有继承属性为属性计算前的初始值
对出现在产生式右边的继承属性和出现在产生式左边的综合属性都必须提供一个计算规则。
属性计算规则中只能使用相应产生式中的文法符号的属性
出现在产生式左边的继承属性和出现在产生式右边的综合属性不由所给的产生式的属性计算规则进行计算,它们由其它产生式的属性规则计算或者由属性计算器的参数提供
语义规则所描述的工作可以包括:属性计算、静态语义检查、符号表操作、代码生成等
在语法树中,一个结点的综合属性的值由其子结点和它本身的属性值确定
一个结点的继承属性由此结点的父节点(或兄弟结点)和其本身的某些属性确定
依赖图:
每一个包含过程调用的语义规则引入一个虚综合属性
依赖图中如果属性b依赖于属性c,则从属性c的结点有一条有向边连到属性b的结点
良定义的属性文法:如果一属性文法不存在属性之间的循环依赖,则称该文法为良定义的