文章目录
语法分析器
语法分析器作用
- 从词法分析器获得单元的序列,确认该是否可以由语言文法生成
- 对于语法错误的程序,报告错误信息
- 对于语法正确的程序,生成分析树 (简称语法树 )
语法分析的任务
- 从词法分析中获得的每个属性字(token)在语句或程序中是什么作用
- 检查语句或程序是否符合程序语言的语法
语法分析器分类
- 自顶向下语法分析器(处理LL文法):从语法分析树的根部开始构语法分析树,推导式
- 自底向上语法分析器(处理LR文法):从语法分析树的叶子开始构语法分析树,规约式
上下文无关文法与正则文法
一个上下文无关文法(CFG)包括四部分:
- 终结符号(terminal)的集合T
- 非终结符号(nonterminal)的集合N
- 唯一的开始符号S(S ∈ N)
- 若干以下形式的产生式(production):X
→
Y
1
Y
2
.
.
.
Y
n
\rightarrow Y_1Y_2...Y_n
→Y1Y2...Yn
其中X∈N 且Y ∈ T ∪ N ∪ { ε } T\cup N \cup \{ \varepsilon \} T∪N∪{ε}
区别: 正则表达式无法定义
L
=
{
a
n
b
n
∣
n
≥
1
}
L=\{ a^nb^n| n\geq1 \}
L={anbn∣n≥1},上下文无关语言可以:S
→
\rightarrow
→ 0 EXPR 1
EXPR
→
\rightarrow
→ 0 EXPR 1 | ε
NFA转为上下文无关文法
- 右线性文法:A → \rightarrow →aB , A → \rightarrow →a ,A → ε \rightarrow \varepsilon →ε
- 左线性文法:A → \rightarrow →Ba , A → \rightarrow →a ,A → ε \rightarrow \varepsilon →ε
推导与规约
- 推导(derivation):从开始符号开始,每一步推导就是用一个产生式的右方取代左端的非终端符号
- 最左推导:每步推导都替换最左边的非终结符号
- 最右推导:每步推导都替换最右边的非终结符号
二义性
- 文法的二义性:如果对于一个文法,存在一个句子,对这个句子可以构造两棵不同的分析树,那么我们称这个文法为二义的.
- 原因:最左推导和最右推导可以构造出两颗不同的分析树
- 消除二义性例子
递归下降分解
- 消除二义性(只有一棵分析树),消除左递归(消除A=>Aa),提取左公因子
- 判断是否为LL(1),是则提取预测分析表,不是则不用递归下降来实现语法分析
自顶向下无法处理左递归的情况,需要消除左递归;自底向上可以处理左递归
消除左递归
- A → A a ∣ B \rightarrow Aa|B →Aa∣B可以消除为: A → B A ′ A \rightarrow BA' A→BA′, A ′ → a A ′ ∣ ε A' \rightarrow aA'| \varepsilon A′→aA′∣ε
- 例子
- 多步左递归:由于两步或多步推导而产生的左递归(S->A, A->S),化为只有一条左递归
提取左公因子
- 方法:对每个非终结符号A找出它的可选产生式的最长公共前缀
- 例子
LL(n)
当前面步骤完成后,利于A → A b c ∣ B c a \rightarrow Abc |Bca →Abc∣Bca,我们仍然是不知道要选择哪个产生式,但是我们可以偷看输入符号,偷看后帮助我们来选定产生式,LL(1)表示偷看1位,LL(n)表示偷看n位
LL(1)
- FIRST集
-
first(a)表示可以从a推导得到的串的首符号的集合
-
求first(X)算法
- 如果X是终结符号,first(X)={X},否则
- X → Y 1 Y 2 . . . Y i \rightarrow Y_1Y_2...Y_i →Y1Y2...Yi,first( Y i Y_i Yi)也属于first(X),如果i可能为 ε \varepsilon ε,first( Y i + 1 Y_{i+1} Yi+1)也属于first(X), ε \varepsilon ε也属于first(X)
-
求右边产生式的first( X 1 X 2 . . . X n X_1X_2...X_n X1X2...Xn)
- 加入first( X 1 X_1 X1)非 ε \varepsilon ε的符号
- 如果first( X 1 X_1 X1)有 ε \varepsilon ε, first( X 2 X_2 X2)非 ε \varepsilon ε的符号也加入以此类推
- FOLLOW集
- follow(A):跟在A右边的终结符号的集合,可以帮助我们选择恰当的产生式
- 求follow算法
- 将右端结束标记$加入follow集
- 如果存在产生式 A → a B β A \rightarrow aBβ A→aBβ, 那么first(β)的所有非 ε \varepsilon ε 都加入到follow(B)中
- 如果存在产生式 A → a B β A \rightarrow aBβ A→aBβ或 A → a B A \rightarrow aB A→aB, 且first(β)包含 ε \varepsilon ε,那么follow(A)的所有符号 都加入到follow(B)中
- LL(1)定义:第一个L表示输入字符串从最左开始扫描,第二个L表示得到的推导是最左推导
对文法的任意两个不同的产生式 A → α ∣ β A \rightarrow α|β A→α∣β,
- 不存在终结符号a可以使得α和β都可以推导出以a开头的串,α和β最多只有一个可以推导出空串
- 如果β可以推导出空串,那么a不能推导出以follow(A)中让你和终结符号开头的串
等价于
- first(α) ∩ \cap ∩ first(β)= ϕ \phi ϕ
- ε \varepsilon ε∈first(β),那么first(α) ∩ \cap ∩ follow(A)= ϕ \phi ϕ
- 构造预测分析表M步骤: 对每个产生式A → \rightarrow →a
- 对first(A)的每个终结符号a,将产生式A → \rightarrow →a 加入到分析表M【A,a】
- 如果first(A)中有 ε \varepsilon ε ,那么将follow(A)中的每个符号b都将产生式 A → ε A \rightarrow \varepsilon A→ε加入到预测分析表M【A,b】中
例子
判断是不是LL(1)文法:LL(1)文法没有二义性,如果是二义文法,预测分析表中【X,i】会出现两个产生式
- 分析表驱动的预测分析表:输入缓冲区为w$,压入栈中为S$, 根据预测分析表M,S遇到每个w是进行匹配还是输出产生式,直到栈中为空或报错
例子
自底向上语法分析(串w规约为文法开始符号S)
- 归约:一个与某产生式体相匹配的特定子串被替换为该产生式头部的非终结符号
- 句柄:如果S=>aAw=>aβw,那么紧跟a之后的β就是A → \rightarrow →β的一个句)
如果文法没有二义性,那么每个句型都有且只有一个句柄
句柄右边只有终结符号(小写)
句子是不包含非终结符号的句型,句型可能包含非终结符号或终结符号或空串
- 移入规约分析
- 使用栈保存归约/扫描移入的文法符号:开始是$——输入w$——结束$S
- 移入:将下一个输入符号移入到栈顶
- 归约:将句柄归约到对应的非终结符号(A → \rightarrow →β)
- 规约分析例子:
- id为句柄,(F → i d 1 \rightarrow id_1 →id1),将id归约
- 移入下一个输入符号 i d 2 id_2 id2
LR(n)
- L表示最左扫描,R表示反向构造出最右推导,n表示最多向前看n个符号
LR语法分析表
- LR语法分析表的结构:
-
ACTION表项:状态1,2,,,i; 终结符号a,,,
- 移入:移入状态j,把j压入栈,将a移入
- 归约 A → β A \rightarrow β A→β:将栈顶的β(句柄)归约到A,根据GOTO表项压入新状态
- 接受:接受输入完成分析acc
- 报错:输入发现语法错误err
-
GOTO表项:[ I i I_i Ii,A]= I j I_j Ij,遇到非终结符号A从状态i跳到状态j
- 例子分析
- 输入移入id(状态0),根据分析表跳到状态 s 5 s_5 s5
- 根据 r 6 : F → i d r_6:F \rightarrow id r6:F→id归约成F,根据GOTO[0,F]跳转状态3
- 根据 r 4 : T → F r_4:T \rightarrow F r4:T→F归约成T,根据GOTO[0,F]跳转状态2
- 继续直到E$,acc
- LR(0)
(1)增广文法
(2)项集闭包CLOSURE:如果 A → α ⋅ B β A\rightarrow α\cdot Bβ A→α⋅Bβ表示希望接下来的串是由Bβ推导出的串,首先是B推导出来的字串,因此加上B的各个产生式对应的项
- 如果 A → α ⋅ B β A\rightarrow α\cdot Bβ A→α⋅Bβ,B的产生式项有 B → γ B\rightarrow γ B→γ,但 B → γ B\rightarrow γ B→γ不在CLOSURE(I)中就加入CLOSURE(I)中
- 将I中的各个项加入到CLOSURE(I)中
(3)项集规范族构造例子
(4)根据规范LR(0)构造LR(0)自动机
- 初始状态为CLOSURE({ S ′ → ⋅ S S'\rightarrow \cdot S S′→⋅S})对应的项集
- 每个项集对应LR(0)自动机的一个状态
- 如果GOTO(I,X)=J,那么从I到J有一个标号为X的转换
(5)根据LR(0)自动机分析
- 从 I 0 I_0 I0开始取id,根据自动机跳转到状态5( I 5 I_5 I5)
- I 5 I_5 I5 : F → ⋅ i d F\rightarrow \cdot id F→⋅id,知道句柄归约为F,返回 I 0 I_0 I0跳转 I 3 I_3 I3
- I 3 I_3 I3: T → F ⋅ T\rightarrow F\cdot T→F⋅,知道句柄归约为T,返回 I 0 I_0 I0跳转 I 2 I_2 I2
- 移入下一个 ∗ * ∗,根据 I 2 I_2 I2中知道跳转 I 7 I_7 I7
- 移入下一个 i d id id,根据 I 7 I_7 I7中知道跳转 I 5 I_5 I5
- I 5 I_5 I5 : F → ⋅ i d F\rightarrow \cdot id F→⋅id,知道句柄归约为F,返回 I 7 I_7 I7跳转 I 10 I_{10} I10
- I 10 I_{10} I10 : T → T ∗ F ⋅ T\rightarrow T*F\cdot T→T∗F⋅,知道句柄归约为T,返回 I 2 I_2 I2
-
I
2
I_2
I2 :
E
→
T
⋅
E\rightarrow T\cdot
E→T⋅,知道句柄归约为E,返回
I
0
I_0
I0 接受
LR(0)中可能存在归约与移入的冲突,所以引入SLR确定应该进行移入还是归约,例如存在E’->E(应该进行归约),E->E+n(应该进行移入),所以遇到E时应该进行归约还是移入,引入SLR1当$的时候归约,+时候移入,只是状态表中多了个接受状态acc
- SLR(1)
S表示简单,L表示最左扫描,R表示反向构造出最右推导,n表示最多向前看n个符号
(1)基本思想:要把α归约成A,后面必须是follow(A)中的终结符号,否则只能移入
(2)构造语法分析表算法:
(3)判断是不是SLR(1):构造出来的项集是否有冲突(某个时刻存在两个有效项要求执行不同的动作)
例子: S → L ⋅ = R 和 R → L ⋅ \rightarrow L\cdot=R 和R \rightarrow L\cdot →L⋅=R和R→L⋅,一个遇到L后要求移入( β 2 β_2 β2为空),一个要求归约( β 2 β_2 β2不为空)
有效项:存在一个推导过程S到αAw=> α β 1 β 2 αβ_1β_2 αβ1β2,那么说 A → β 1 ⋅ β 2 A \rightarrow β_1 \cdot β_2 A→β1⋅β2是可行前缀 α β 1 αβ_1 αβ1的有效项
如果 A → β 1 ⋅ β 2 A \rightarrow β_1 \cdot β_2 A→β1⋅β2是可行前缀 α β 1 αβ_1 αβ1的有效项:
如果 β 2 β_2 β2不为空的时候说明句柄尚未出现在栈中,应该移入
如果 β 2 β_2 β2等于空的时候说明句柄已经出现在栈中,应该归约
SLR(1)引入LR(1)是因为SLR中可能也存在着两种冲突,第一种是移入与归约的冲突(与LR(0)中的不一样)例如I->if S ; I->S else S, 而且follow(I)={$,else},此时遇到else应该进行归约,但是下面 I->S else S遇到else应该移入所以冲突;
另一种冲突是归约与归约冲突如A->a,B->a;此时应该归约为哪个
- LR(1):精确的说明了应该何时归约
L表示最左扫描,R表示反向构造出最右推导,n表示最多向前看n个符号
(1) [A → α ⋅ β , a \rightarrow α\cdotβ,a →α⋅β,a]:a表示要向前看的符号,按照A $\rightarrow $αβ进行归约