语法分析器是根据源语言的文法构造出来的。...
DESCRIPTION
语法分析器是根据源语言的文法构造出来的。 用于程序语言的编译程序构造中的一些典型的语法分析方法及分析器的生成器 Yacc。 语法分析方法:自顶向下分析:递归预测分析和 LL(1) 分析;自底向上分析:算符优先分析和 LR 分析 。. 4 . 1 语法分析器的作用 4 . 2预测分析器 4.2.1预测分析 4.2.2递归预测分析器的构造 4 .2.3非递归预测分析器的构造— LL(1) 4 . 3 书写文法 4 . 3 . 1消除左递归 4 . 3 . 1提取左因子 4.4自顶向下的带回溯的分析方法简介. 4.1语法分析器的作用. - PowerPoint PPT PresentationTRANSCRIPT
1
2
语法分析器是根据源语言的文法构造出语法分析器是根据源语言的文法构造出来的。来的。
用于程序语言的编译程序构造中的一些用于程序语言的编译程序构造中的一些典型的语法分析方法及分析器的生成器典型的语法分析方法及分析器的生成器YaccYacc。。
语法分析方法:自顶向下分析语法分析方法:自顶向下分析 :: 递归预测递归预测分析和分析和 LL(1)LL(1) 分析;自底向上分析分析;自底向上分析 :: 算符算符优先分析和优先分析和 LRLR 分析。分析。
3
4.1 语法分析器的作用4.2 预测分析器 4.2.1 预测分析 4.2.2 递归预测分析器的构造 4 .2.3 非递归预测分析器的构造— LL(1)
4.3 书写文法 4.3 .1 消除左递归 4.3 .1 提取左因子4.4 自顶向下的带回溯的分析方法简介
4
4.1 语法分析器的作用
词 法
分析器
语 法
分析器源程序
单词符号
取下一个单词符号
分析树
语 法
分析器w S w
* r m
* l m
语 法
分析器w S w
自顶向下分析 自底向上分析
5
构造推导的关键问题
在构造最左推导的过程中 , 面对当前读入的单词符号和当前被替换的非终结符两者 , 应该选择这个非终结符的那条候选式去替换它( 推导 ); 主要找出选择一个非终结符号的候选式的方法 ;
在构造最右推导的过程中 , 面对当前读入的单词符号 , 已分析过的符号串中是否已构成一个产生式的右部(可归约),即句柄。如果已构成句柄 , 即用相应的产生式左部( 非终结符号 ) 去替换它 ( 归约 ) ,寻找句柄的方法。
6
二 . 构造最左推导( aabbaa) (1. 自顶向下) SaASa A SbA SS ba
A
S
a S
bS A a
a b a
aSbAS
aabAS
aabbaS
aabbaa
aAS
S
7
二 . 构造最右推导 (2. 自底向上) SaAS a A SbA SS ba
A
S
a S
bS A a
a b a
aAa ( a)
aSbAa (SbA)
aSbbaa (ba)
aabbaa (a)
aAS (aAS)
S
8
4.2 预测分析器 4 .2 .1 预测分析 预测分析的原理 4 .2 .2 递归预测分析器的构造 用一组递归过程实现预测分析,产生式的状态转换图可作为编写递归过程的兰图。 4 .2 .3 非递归预测分析器的构造— LL(1)
对于每一步分析,分析表项M[A,a]=‘A’
表示:面对非终结符号 A 和向前看符号 a 应选择产生式 A 进行分析。
9
4 .2 .1 预测分析
自顶向下分析是从文法的开始符号出发,试构造出一个最左推导,从左至右匹配输入的单词符号串。如果在每步推导中,面对被替换的非终结符号(不妨称为 A )和当前从左至右读入输入串读到的单词符号(又称向前看符号,不妨称为 a),如果 A 的定义(无 ε- 产生式) A→α1 |α2 |… |αn 中,只有αi( 1≤i≤n) 推导出的第一个终结符号是 a ,那么,我们就可以用产生式 A→αi 构造最左推导,即用 αi 替换 A。
10
例 4.1 下面文法生成 pascal 类型的一个子集 type Simple
id (4.1)
array[ simple ]of type
Simple integer
char
num dotdot num
array [ num dotdot num ] of integer
11
array [ num dotdot num ] of integer typearray[simple] of type { 注 :A=type,a=array, type→array[simple ] of type} array[num dotdot num] of type { 注 :A=simple, a=num simple→num dotdot num } array[num dotdot num] of simple { 注 :A=type,a=integer , type→simple } array[num dotdot num] of integer{ 注 :A=simple,a=integer ,simple→integer }
12
例 4.1 ,在推导过程中,完全可以根据当前的向前看符号决定选择哪个产生式往下推导,因此,分析过程是完全确定的。这种分析称为自顶向下的预测分析。 原因在于,文法中, AVN,
A12... n
i,j(1i,jni<>j), 从 i 推导出来的第一个终结符号集合(称为 FIRST( i) )和从 j 推导出来的第一个终结符号集合(称为 FIRST(
j) )不相交,即, FIRST( i) FIRST(
j)=
13
定义 4.1 令 G[S]=(VT,VN,S,P), 则
FIRST()=a a… a VT
例 4.2 文法 G[S] ,其产生式如下 :
S→aA|d A→bAS|ε ( 4.2)
若 w=abd ,则构造最左推导的过程如下 :
S aA { 注: A=S, a=a, S→aA}
abAS { 注: A=A, a=b, A→ bAS}
abS { 注: A=A, a=d , A→ε}
abd { 注: A=S, a=d , S→d}
*
14
考虑第三步推导: abAS abS (A)
d 是句型 abAS 中紧跟随非终结符 A 的终结符号,把紧跟随非终结符 A 的终结符号集记作 Follow( A),其定义如下:
定义 4.2 令 G[S]=(VT,VN,S,P), 则
FOLLOW(A)=aS … Aa…,
a VT, A VN
若 S ...A, 则规定 $∈Follow(A) ,‘ $’作为输入串的结束符号( Follow(A) 的作用) 。
+
*+
+
15
4.2.2 递归预测分析器的构造
文法( 4.1) 的预测分析器,由图 4.3 中所示的一组递归过程组成,这组递归过程的执行,将实现根据文法( 4.1) 对任一句子 w 进行预测分析。因此,这个分析器称为递归预测分析器。其中,过程 type 和过程 simple 是相应于文法( 4 . 1 )的非终结符号 type和simple 的 , 而过程 match 是为了简化过程type 和过程 simple 的代码所附设的。如果过程 match 的参数 t 与向前看符号 a 是匹配的,则进展到下一个输入符号。
16
PROCEDURE match(t:token);
BEGIN
IF lookahead=t
THEN lookahead :=nexttoken
ELSE error
END;
17
PROCEDURE type;
BEGIN
IF lookahead in{integer,char,num}
THEN simple ELSE
IF lookahead =‘’ THEN BEGIN
match(‘’); match(id) END ELSE
IF lookahead =array THEN BEGIN
match(array); match(‘[‘) ; simple;
match(‘]’); match(of); type END
ELSE error
END;
18
PROCEDURE simple; BEGIN IF lookahead=integer THEN match(integer) ELSE IF lookahead=char THEN match(char) ELSE IF lookahead =num THEN BEGIN match(num); match(dotdot); match(num) END ELSE error END;
19
构造方法: A VN, 构造一个递归程 , 不妨命名为 A 。过程 A 根据当前向前看符号 a处于 A 的哪个候选式的 FIRST 集合中,就选择那个候选式进行分析。根据选择的候选式,从左至右,若是终结符号,则和向前看符号匹配;若是非终结符号,则调用相应过程;若 aFOLLOW(A), 则选择 A。
使用状态转换图构造递归预测分析器
每个产生式右部是 VT V∪ N {∪ } 上的正规表达式,由此画出相应的识别器,称作状态转换图,以此作为编写递归预测分析器的蓝图。
20
给定文法 G ,状态转换图的画法如下:
A VN
1. 创建一个初始状态和一个终结状态 ; 2 .对于每一个产生式 A X1X2,…, Xn,
创建一条从初始状态到终结状态的路径, 箭弧分别标记以 X1, X2 …, , Xn。例 4 . 3 (4.3) G[E]=( VT={+, * ,(,), id},
VN={E, E', T, T', F} ,E, P) P : E→TE' E'→+TE'ε T→FT' T'→*FT' ε F→(E) id
21
0 1 2
3 4 5 6
7 8 9
10 11 12 13
14 15 16 17
E :
E’ :
T :
F :
T’ :
T
T
F
F
E
T’
T’
E’
E’
+
*
( )
id
图 4.4 文法 4.3 的转换图
22
预测分析器的工作: 从开始符号的状态图开始,若处于状态s,从 s 有弧到达状态 t 。若当前向前看符号是a, a和某标记匹配,则分析进入 t 状态且读入下 一 个输入符号;若 aFIRST(A), 则转到 A 状态图,分析到达 A 状态图的接受状态后,分析进入 t状态;若 aFOLLOW(A), 则选弧。
状态图的确定性问题:
根据上述选择方法,若能选择唯一,则是确定的;否则,是非确定的。
23
0 1 2
3 4 6
E :
E’ :
T
T
E’
图 4.5 简化转换图
5+ E’
3 4 5+ T
6
3 4+
T
6
0 3 4T
T
+
6
E : 0 3 6T
+
E’ :
24
图 4.5 算术表达式的简化转换图
0 3 6E:T
+
7 8 13F*
T:
14 15 16 17F:E( )
id
25
PROCEDURE E:
BEGIN T;
WHILE lookahead=‘+’ DO
BEGIN match(‘+’); T END
END;
PROCEDRUE T: BEGIN F; WHILE lookahead=‘*’ DO BEGIN match(‘*’); F END END;
26
PROCEDURE F:
BEGIN
IF lookahead=‘(’
THEN BEGIN
match(‘(’); E; match(‘)’)
END
ELSE IF lookahead=id
THEN match(id)
ELSE error
END;
27
作业:
4.1
4.2
4.3 (a) (c )
4.4 自己看
28
4.2.3 非递归预测分析器的构造
一 . 基本思想
二 . 非递归预测分析器的模型和分析演示
三 . 非递归预测分析器的控制程序
四 . 计算 FIRST 和 FOLLOW 集合
五 . 非递归预测分析表的构造
六 . LL(1) 文法
七 . 在预测分析法中的错误处理示例
29
一 . 基本思想
预测分析是在每步推导中 , 对被替换的非终结符号 A 和当前向前看符号 a 能选择A 的某条产生式进行推导。非递归预测分析的基本思想是,根据文法 G ,构造一张分析表 M ,表中元素M[A, a]存放的,要么是被选择的产生式(正确分析情况);要么是出错处理程序入口(分析出现错误)。整个分析是在分析表 M 的驱动下完成的。本节主要围绕 LL( 1 )分析表展开讨论。
30
id + * ( ) $E E TE’ E TE’
E’ E’ +TE’ E’ E’
T T FT’ T FT’
T’ T’ T’ *FT’ T’ T’
F F id F id
id + id * id $输入
预测分析控制程序
$
EE’
TT’
Fid
E’
T
+
T’
Fid
ETE’
FT’E’
idT’E’
idE’
id+TE’
id+FT’E’
id+idT’E’
栈
二 . 非递归预测分析器的模型
31
三 . 非递归预测分析器的控制程序 置 ip指向 w$ 的第一个符号;令 x 为栈顶符号, a 是 ip 所指向的符号 . push(st,$);push(st,s);REPEAT IF xVT{$}
THEN IF x=a THEN { pop(st); ip:=ip+1} ELSE error( ) ELSE IF M[x,a]=xy1y2…yk
THEN {p0p(st);push(st,yk);…push(st,y1);
write(xy1y2…yk) }
ELSE error( ) UNTIL x=$
32
四 . 计算 FIRST和 FOLLOW 集合
目的:构造文法 G 的分析表 M ,实际上,构造预测分析。 G[Z]: Z d XYZ Y c
X Y a (4.4)
(VTVN)*, =XYZ, X , Y , FIRST()必包含 FIRST(Z) 。同样, =AXYt
则 tFOLLOW(A) 。必须注意能产生空串的符号,这样的符号称为 nullable ,若 A ,则 nullable(A)=true。
*
*
*+
+
+
33
算法 4.2 计算 nullable,FIRST和FOLLOW 初始化 : 除 FOLLOW(S)={$}外 , FIRST 和 FOLLOW 全为空集 . nullable 全为 false. FOR aVT DO FIRST(a)={a} ; REPEAT FOR A Y1Y2…Yn P DO { IF i(1i n) nullable(Yi)=true THEN nullable(A)=ture; FOR i:=1 TO n DO { IF (i=1) or (1 j i-1)nullable(Yj)=true THEN FIRST(A):=FIRST(A)FIRST(Yi); 计算 FOLLOW 集 }} UNTIL FIRST,FOLLOW,nullable 不再增大
34
IF YiVN THEN
{ IF (i=n)orj(i+1j n)nullable(Yj)=true
THEN FOLLOW(Yi):=FOLLOW(Yi)
FOLLOW(A);
IF Yi+1 VT THEN Yi+1 FOLLOW(Yi)
ELSE
FOR k:=i+1 TO n DO
IF (k=i+1)or j(i+1j k-1)nullable(Yj)=true
THEN FOLLOW(Yi):=FOLLOW(Yi)
FIRST(Yk); }
35
例 4.5 把算法 4.2 应用于文法 G[E]( 4.3) ,
得到下面的计算结果。nullable FIRST FOLLOW
E false (,id ),$
E’ true + ),$
T false (,id +,),$
T’ true * +,),$
F false (,id *,+,),$
VN
36
五 . 非递归预测分析表的构造 在对文法 G 的每个非终结符号 A 及其任意候选 α都构造出 FIRST( α )和FOLLow( A )之后,用它们来构造 G 的分析表 M[A, a]。
算法 4 . 3 非递归预测分析表的构造 1 .对文法 G 的每个产生式 A 执行第 2步 和第 3 步; 2 . 对每个终结符号 a FIRST∈ (),把 A 加至 M[A,a] 中; 3. 若 α ε ,则对任何 b FOLLOW∈ ( A), 把 A 加至 M[A,b] 中 ; 4 . 把所有无定义的 M[A,a] 标上错误标记。
*
37
六 . LL(1) 文法 对于某些文法, M[A,a] 可能含有多重定义。例 4.7 文法( 4.5), 它的分析表如表 4 . 4 所示。其中 M[ S’, e]包含两个产生式 S’eS 和 S’ 。这是来自文法( 4 . 5 )的二义 性 , FOLLOW( S’) ={e, $ } 反应了这一点。G[S]: S iEtSS’a S’ eS Eb (4.5)a b e i t $
S S a S iEtSS’
S’ S’ eS,S’ S’
E E b
38
一个文法 G, 若它的分析表 M 不含多重定义入口 , 则被称为 LL(1) 文法。 LL(1) 中的第一个“ L”意味着自左而右地扫描输入 , 第二个“ L”意味着生成一个最左推导 ,并且“ 1”意味着为做出分析动作的决定,在每一步利用一个向前看符号 , 一个文法 G是 LL( 1 )的,当且仅当对于G 的每一个非终结符号 A 的任何两个不同产生式 Aαβ , 下面的条件成立 :1 . FIRST( α ∩) FIRST( β)= Φ ;它们不 应该都能推出空字 ε;2 .假若 β*ε, 那么 , FIRST( α ∩ ) FOLLOW( A)= Φ.
39
七 . 在预测分析法中的错误处理示例 下面两种情况下发现源程序的语法错误: 1 .栈顶上的终结符号与下一个输入符 号不 匹配; 2. 分析表入口M[A,a] 为空。 处理方法:跳过剩余输入符号串中的若干符号,使栈和剩余输入符号串能重新协调地同步工作。在表中加入M[A,FOLLOW(A)]:=“synch” 。 在分析中查到入口M[A, a] ,若是空白,则跳过输入符号 a ;若是 synch ,则从栈中将非终结符号 A弹出。如果栈顶的终结符号与输入符号不匹配,则将此终结符号从栈中弹出。
40
4.3 书写文法 通常使用的,不是 LL(1) 的,也不能用于递归预测分析器的构造。通过改写,使之适合于自顶向下分析。
4.3 .1 消除左递归 1 . 左递归的定义 2 . 左递归文法不适于构造自顶向下分析 3 . 消除直接左递归 4 . 消除间接左递归
4.3 .2 提取左公因子
41
4.3 .1 消除左递归 1 . 左递归的定义 一个文法 G ,若存在推导 A A , 其中 , A V∈ N,∈ (VT V∪ N)* ,则称 G 是左递归的。 存在上述推导的原因是文法 G 中存在左递归规则。例如,描述表达式常用的语法G[E] ,其产生式如下 :
E→E+TT
T→T*F F ( 4.6)
F→(E) id
++
42
二 . 左递归文法不适于用来构造自顶向下分析 这种规则更简单的代表是 S→Sa|b
( 1) FIRST( Sa ∩) FIRST( b ≠) Φ。 ( 2 )由 S 产生的句子是 {ban|n≥0} 。例如,对 于输入 baaa$ ,从左到右扫描输入串,开 始,向前看符号是 b ,此时,选用 S 的 哪 条候选式?若选用 S→b ,则肯定构造不 出 S baaa ;若选用 S→Sa, 则面对向前看 符号 b ,下次仍选用 S→Sa ,不知何时选 用 S→b 。构造分析树的过程如图 4.9 所示。
+
43
S
S
S a
a
图 4.9 含直接左递归文法的分析树结构
用这样的规则构造递归预测分析的过程,那么,一进入这种过程不匹配任何输入符号,直接执行递归调用,形成自己调用自己的死循环。
44
三 . 消除直接左递归 AA ,(VTVN)*
A A’ A’ A’ A *
文法( 4.6 ) 文法( 4.3)
EE+TT E TE’
T T*F F E’ +TE’
F (E) id T FT’
T’ *FT’
F (E) id
+
45
直接左递归的一般形式
A A1 A2... An 1 2... m
A 1 A’ 2A’ ... mA’
A’ 1A’ 2A’ ... n A’
四 . 消除间接左递归 间接左递归的例子 S Aa b A Ac Sd (4.7) S Aa Sda 算法 4.4 要求无环路 A A 和无 - 产生式。
+
46
算法 4.4 消除左递归1.AVN, 按任一顺序排列成 A1, A2, …, An.
2. for i:= 1 t0 n do
for j:=1 to i-l do
{ if Ajδ1|δ2|…|δk , Ai Ajγ P
then 把 Ai Ajγ改写成
Ai δ1γ|δ2γ|…|δkγ
消除关于 Ai- 产生式的直接左递归性 }
3 .化简由( 2 )所得到的文法。
,
47
例 4.9 G[s]: S Acc A Bb b B Sa a 非终结符号排序为 B,A,S A (Sa a)b b 把 B 的产生式代入 A 中 A Sa bab b S (Sa bab b)c c 把 A 的产生式代入 S 中 S Sa bcabc bc c S abcS’ bc S’cS’ 消除直结左递归 S’ abcS’ A Sa bab b A 和 B 的产生式是多余的 B Sa a
48
S Ac c A Bb b B Sa a 非终结符号排序为 S,A, B B ( Acc ) a a 把 S 的产生式代入B 中 B Acac a a B (Bbb)cacaa 把 A 的产生式代入 B 中 B Bbcabcacaa B bcaB’caB’aB’ 消除直结左递归 B’ bcaB’ S Ac c 最后 G[s] 的产生式 A Bb b B bcaB’caB’aB’ B’ bcaB’
49
4.3.2 提取左因子 文法 G的产生式 A, 若 1 , 1 , 1 和 1 从左端开始有相同的子串,这个子串称为和的公共左因子。显然,含有公共左因子的规则 A 不能用来构造预测分析。为此,用提取左因子来改写这样的规则。例如, stmtif expr then stmt else stmt改写 if expr then stmt stmtif expr then stmt ( else stmt )成: stmtif expr then stmt S’ S’ else stmt
**
50
若 A 1 2
改写成 : AA‘ A‘ 12
例 4.11 G[S]: S aSdAc A aS b (4.9) 把 A的产生式代入 S中: S aSdaScbc 提取左因子: S aSS’bc S’ dc A的产生式是多余的。 G[S]: SApBq A aAp d B aBq c (4.10)
51
G[S]( 4.10 )在有限步骤内不能改写成无公共左因子的文法。
一般来说,程序语言的语法,经消除左递归和提取左因子后,能采用自顶向下的预测分析方法构造它的语法分析器。因为一般能被变换成 LL(1) 文法,只有个别结构是 LL(2) 文法。
对含有 LL(2) 语法结构的文法,采用递归的预测分析方法比 LL(1) 分析方法灵活,因为递归的预测分析方法在分析 LL(2) 结构时 ,能方便地向前多看一个符号,选取需要的产生式。
52
4.4 自顶向下的带回溯的分析方法简介
并不是每个文法经改写后都可采用自顶向下的预测分析方法。例如 G[S](4.11), 这个文法产生偶数个 a的所有串 。 其产生式如下: S→aSa|aa (4.11) 面对 w ,扫描程序从左至右读 w ,即使往后多看 n个符号,一般来说也不知道选择哪个候选式能正确分析下去。不得已,只好采用试探法,不妨先选择最长的候选式进行推导,如不行,再选用另一个候选式。设w=aaaa$, 分析过程如下 :
53
步骤 最左推导 和输入串匹配 第 1 步 SaSa w=aaaa$
第 2 步 aaSaa w=aaaa$
第 3 步 aaaSaaa w=aaaa$
第 4 步 aaaaSaaaa w=aaaa$ 至此,发现分析选用的候选式不对,回退到第 3步选用S→aa 进行分析。 第 5 步 aaaaaaaa w=aaaa$
第 6 步 aaaaaa w=aaaa$ 回退到第 2步
第 7 步 aaaaaa w=aaaa$ 回退到第 1步 成功
54
上述分析称为带回溯的自顶向下分析。对于 w ,从文法的开始符号出发,反复使用不同的产生式谋求匹配输入串。当用某个非终结符号的候选式进行匹配失败时,删除这个失败分析建立的分析树分支并回头查输入符号,以便与其它候选式匹配。
为每个非终结符号设计一个递归布尔函数过程完成这种回溯分析。在进入过程未分析前,要保存当前分析格局,包括输入符号串指针,使用规则指针,分析树结构等。一旦发现它的某个候选式与输入串匹配,就用这个候选式去扩展分析树,并返回“真”值;否则,恢复进入过程时保存的分析格局,并返回“假”值。 另外,考虑加上语义分析,就会更麻烦,在保存和恢复现场格局方面,还要包括语义方面的工作,像符号表中的信息和生成代码的现场格局等。 这种带回溯的自顶向下分析技术,效率很低,代价极高,因此,它只有理论上的意义。
55
作业:
4. 6 试从文法 G[s] S→(L)|a L→ L, S|S 中消除左递归。并为之构造一个递归预测分析器和 LL(1) 分析表。请说明句子 (a,(a, a))在 LL(1) 分析器上的的动作。(参考129页表 4 . 3 ) 4. 9 对于文法 G[bexpr] bexpr→bexpr or bterm | bterm bterm→bterm and bfactor | bfactor bfactor→not bfactor|(bexpr) |true |false 构造一个递归预测分析器。
56
4.8 (a) 计算下面文法 G[S]的 nullable, FIRST 和 FOLLOW 集 S→uBDz B→Bv|w D→EF E→y |ε F→x |ε (b) 构造这个文法的 LL(1) 分析表。 ( c ) 给出这个文法不是 LL(1) 的证据。 (d) 尽可能少的修改这个文法,使其成为 能产生相同语言的 LL(1) 文法。
57
4.5 自底向上分析 把一个输入符号串逐步归约到文法的开始符号。 这种方法的大致过程是,用一个栈,把输入符号一个一个地移进到栈里,当栈顶形成某个产生式的右部( 句柄)时,把栈顶的这一部分替换成 ( 归约为 )它的左部符号。称作“移进—归约”分析。 4.5.1 规范归约 句柄
4.5.2 “移进—归约”分析的栈实现
58
4.5.1 规范归约 归约
G=(VT,VN,S,P),α, β ∈(VT∪VN)*,A→β∈P,αAw αβw 。归约的过程是,已知 αβw和产生式 A→β ,用产生式 A→β 左部 A 替换 αβw中的 β ,得到符号串 αAw 。 从输入符号串出发 ,每次从被归约的句型中找到一个产生式的右部 ,用其左部替换之,得到新的句型 ,直至归约到文法的开始符号。 因为从左至右读入输入符号串,自然在被归约的句型中找最左边的某个产生式的右部(句柄)进行归约。
59
例 4.12 G[S](4.12), 其产生式如下: ① S→aABe ②A→b ③A→Abc ④B→d (4.12) 输入串 abbcde aAbcde aAde aABe S SaABe aAde aAbcde abbcde
a b b c d e abbcde
A aAbcde
AaAde
B
S
aABe
S
60
例 4.12 G[S](4.12), 其产生式如下: ① S→aABe ②A→b ③A→Abc ④B→d (4.12) 输入串 abbcde aAbcde aAde aABe S SaABe aAde aAbcde abbcde
a b b c d e abbcde
A aAbcde
AaAde
B
S
aABe
S
62
S
A
w
Aw w
已知 w ,分析已识别出,产生式 A 的右部,再看待输入串 w 的最左边符号,用 A 替换得到 Aw; 自上而下分析是从 w A ,看FIRST(), w A w
63
定义 4.3 假定是文法 G 的一个句子。称右句型序列 n , n-1,…, 1, 0 是 的一个规范归约,如果序列满足 1. n= , 0=S ; 2.i(0 ≤ i < n ), i i+1
规范归约是关于的一个最右推导的逆过程。如果文法 G 是无二义的,那么,规范推导 ( 最右推导 )的逆过程必是规范归约 ( 最左归约 )。 βw 表示一个规范句型 , 是在 β 归约之前进行的规范归约得到的结果 , (VTVN)* ,w VT
* 。句柄的“最左”特征使得在移进 -归约方法中,它处于符号栈的栈顶。
rm
64
二义性文法存在规范归约不唯一的句子。例如,文法 G[E]:
E→E+ E E*E (E) id 句子 id+ id*id 有二个不同的最右推导 : EE+E EE*E E+ E*E E *id E+ E*id3 E+E*id E+id2*id3 E+id*id id1+id2*id3 id1+id2*id3 句型 E+ E*id3 中 ,句柄不唯一 。 规范归约的中心问题是:如何寻找或确定 一 个句型的句柄 。
65
4.5.2 “ 移进 - 归约”分析法的栈实现
“移进一归约”分析器使用一个栈和一个存放输入符号串 w的缓冲器。分析器的初始状态为 : 栈 输入 $ w $ 工作过程:自左至右把串 w 的符号一一移进栈里,一旦栈顶形成句柄时,就进行归约。这种归约可能持续多次,直至栈顶不再呈现句柄为止。然后,继续向栈里移进符号,重复这个过程,直至最终形成如下格局: 栈 输入 $ S $
66
G[s]: SaAcBe A bAb B d
步骤 栈 输入 动作(1) $ abbcde $ 移进(2) $a bbcde$ 移进(3) $ab bcde$ 归约,A b(4) $aA bcde$ 移进(5) $aAb cde$ 归约,A Ab(6) $aA cde$ 移进(7) $aAc de$ 移进(8) $aAcd e$ 归约,B d(9) $aAcB e$ 移进(10) $aAcBe $ 归约,S aAcBe(11) $S $ 接受
67
“移进 -归约”分析对符号栈的使用有四类操作:移进、归约、接受和出错处理。规范句型 ( 右句型 )的“活前缀” , 定义如下 :定义 4 . 4 一个规范句型的一个前缀,若不含句柄之后的任何符号 ,则称它为该规范句型的一个活前缀。 分析过程的每一步骤,栈里的文法符号串加上剩余输入符号串恰好是一个规范句型。而且栈里的文法符号串正好是这个句型的一个活前缀。如在表 4.7(a) 的前三步中可以看到, a及 ab都是符号串 abbcde 的活前缀。 “移进 -归约”分析识别规范句型的活前缀。
68
4.6 算符优先分析法 概述 一 . 算符文法的定义 二 . 算符优先分析法的基本思想4.6.1 利用算符优先关系寻找右句型的可归 约串
4.6.2 算符优先关系表的构造
4.6.3 优先函数
总结
69
一 . 算符文法的定义 定义 4.5 设 G 是一个文法 ,如果 G 中不存在形如 A及 A→BC 的产生式 ( 其中A,B,CVN ,, (VN VT)*且其中不含有相邻非终结符号) ,即 G 中没有右部为或右部具有相邻非终结符号的产生式 ,则称 G 为算符文法。 G[E]: E→E+ E|E-E|E*E|E/E|EE|(E)|-E|id 是算符文法。 (4.13) E→EAE|(E)|-E|id A→+ |- |*|/| 不是算符文法。因右部 EAE具有相邻的非终结符号。
70
二 . 算符优先分析的基本思路 由于文法 (4. 13)是一个二义文法,它的句子往往有不同的规范推导,按传统的习惯规定优先级从高到低为:乘幂运算符,乘、除运算符,加、减运算符;同级运算符服从左结合原则;有括号时,先括号内后括号外。 文法的句子 id+ id- id*(id+ id) 的归约过程为: (1)id+ id- id*〔 id+ id ) (2) E+ id- id*(id+ id ) (3) E+ E- id*(id+ id ) (4) E- id*(id+ id ) (5) E-E*(id+ id) (6) E-E*(E+ id)
71
(7) E-E*(E+ E) (8) E- E*(E) (9) E- E* E (10) E- E (11) E
这个归约过程是唯一的 。上述归约过程中起决定作用的是相邻两个终结符号之间的优先关系 。一旦确定了这种优先关系,就可以借助这种关系去寻找可归约串并进行归约。 终结符号 a与 b 之间的优先关系有三种: a b 表示 a的优先级低于 b a b 表示 a的优先级等于 b a b 表示 a的优先级大于 b
72
注意:
1. 算术关系“ <”,“=” 和“ >” 与优先关系具有十分不同的性质。例如, a< ·b 并不一定意味着 b·>a ,例如: + <· (,( <· + 。 2. 决定优先关系方法:
( a ) 直观方法:代数规则; ( b )对于一个无二义性文法,有机械方法。
73
4.6.1 利用算符优先关系寻找右句型的可归约串 算符文法右句型的形式为(可以证明) β0a1β1a2β2…anβn 其中, βiVN{}, an VT 。假设在 ai和ai+1之间三个关系 <·,=, ·> 中至多有一个成立。进而,$作为每一个右句型符号串的左右分界符,算符文法右句型的形式为: $ β0a1β1a2β2…anβn $并规定, ai, $ <· ai, ai ·> $。 在句型中加入优先关系,例如: id+id*id $ <·id ·> + <·id ·> * <·id ·> $句型中 <· 和 ·>之间的符号串是待归约的符号串。
.
74
找右句型的可归约串 的方法 $ <·id ·> + <·id ·> * <·id ·> $ 1· 找可归约串 的右端; 2 · 找可归约串 的左端; 3 · 归约 使用下面的优先关系表,分析过程如下:
id + * $id · · ·+ · · · ·* · · · ·$ · · ·
75
栈 关系 输入 动作$ <· id+id*id$ 移进 $ id ·> +id*id$ 归约 $ E < · +id*id$ 移进$ E+ < · id*id$ 移进$ E+id · > *id$ 归约$ E+E < · *id$ 移进$ E+E * < · id$ 移进$ E+E * id · > $ 归约$ E+E * E · > $ 归约$ E+E · > $ 归约$ E $ 接受
76
算法 4 . 5 算符优先分析法 方法: if (ab) or (ab) then begin /* 移进 * / 把 b 推入栈中; 使 ip前进到下一个符号; end if a·b then /* 归约 * / repeat 从栈中弹出符号 until 栈顶终结符号最近弹出的终结 else error 符号
77
算法中,每一个归约串中至少包含一个终结符号,用到了一个重要的概念和结论。 定义 4 . 5 设 G 是一个算符文法, β 是句型δ 关于 A 的短语 ( 即有 S αAδ且 A β )且 β 至少含有一个终结符号,并且除自身之外不再含有任何更小的 带有终结符号的短语,则称 β 是句型 αβδ 关于 A 的素短语。所谓最左素短语是指处于句型最左边的那个素短语。 设 G 是一个算符文法,如果 G 中任何两个终结符号之间至多有一种优先关系存在,则是一个算符优先文法。 算符优先文法句型的最左素短语是唯一的。
* +
78
句柄和素短语的区别:G[E]:EE+TT E E+E E*E (E) id T T*FF F (E) id
E
E + T
* FT
F id
id
T
F
id
E
E + E
*E E
id id
id
79
4.6.2 算符优先关系表的构造
一 .直观方法:代数规则
( 1) id 是最基本的运算量
( 2 )一目运算符号减,例如,
-id-id
( 3)是右结合。
二 .形式方法 ( 本节总结时有简单介绍)
80
+ - * / id ( ) $+ - * / id ( ) $
表 4.9 优先关系表
81
4.6.3 优先函数 为了节约存储空间和便于执行比较运算 ,用两个优先函数 f和 g ,它们是从终结符号映射到整数的函数。对于终结符号 a和 b选择 f和 g,使之满足: 1.当 a< ·b 时 , f(a)< g(b); 2. 当 a = b时 , f(a)= g(b); 3. 当 a ·> b 时 , f(a) > g(b)。 于是 a和 b 之间的优先关系可以由比较f(a) 与 g(b) 的大小来决定。 损失 :错误检测能力降低,例如, id ·>id 不存在 , f(id) >g(id) 可比较。
·
82
表 4.9 对应的优先函数:
+ - * / ( ) id $
f 2 2 4 4 4 0 6 6 0
g 1 1 3 3 5 5 0 5 0
1) 构造优先函数的算法不是唯一的。
2 ) 存在一组优先函数,那就存在无穷组优先函数。
83
算法 4.5 从优先关系构造优先函数
方法: 1.aVT{$}, 建立两个符号 fa和ga;
2. 若 a = b, 则把 fa和 gb 分在一组; 3 . a, b VT,
若 a b ,则从 fa至 gb 画一条弧; 若 a b ,则从 gb至 fa 画一条弧 ;
4 . 若图中无环,则存在优先函数, f(a)和 g(b)等于从 fa 和 gb 出发的 最长路径。
.
84
id + * $id + < <
* <
$ < < <
gid fid
f* g*
g+ f+
f$ g$
id + * $
f 4 2 4 0
g 5 1 3 0
85
总结: 1. 算符优先分析法能方便地构造表达式的语法分析器,分析速度也比较快; 2. 诊查错误的能力较弱,适用的范围小; 3. 形式化方法求优先关系简介
优先关系定义: 设是 G 不含 - 产生式的算符文法, a,bVT,
1) a = b A…ab... P 或 A…aQb... P
.
86
2) a < b A…aR... P
且 (R b… 或 R Qb… )
3) a >b A…Rb... P
且 (R …a 或 R …aQ )
a < FIRSTVT(R); LASTVT(R) >b
+
+
+
+
G[E]: EE+TT T T*FF F (E) id + < FIRSTVT(T)
LASTVT(T) >*
E
E + T
*T F
idid
id
87
作业: 4.14 4.15 4.16(b)
88
4.7 LR 分析器
序
4.7 .1 LR 分析器的逻辑结构及工作过程
4.7.2 SLR 分析表的构造
4.7.3 LR ( 1 )分析表的构造
4.7.4 LALR(1) 分析表的构造
4.8 LR 分析对二义文法的应用
4.9 分析器的生成器 yacc
89
序
LR(k) 分析技术。这里的“ L” 是指从左至右扫描输入符号串,“ R” 是指构造一个最右推导的逆过程,“ k” 是指为了作出分析决定而向前看的输入符号的个数。
LR 分析方法是当前最广义的无回溯的“移进 - 归约”方法。根据栈中的符号串和向右顺序查看输入串的 k(k0) 个符号,就能唯一确定分析器的动作是移进还是归约,以及用哪个产生式进行归约。 LR(k) 分析技术 knuth 于 1965年首先提出
90
来的。
优点:适用范围广;分析速度快;报错准确。
构造分析器的工作量很大,不大可能手工构造;用软件工具 yacc-Yet Another Compiler Compiler,Bell,1974. LR(0) SLR(1) LR(1) LALR(1)
91
4.7.1 LR 分析器的逻辑结构及工作过程 一个输入、一个输出、一个栈、一个驱动程序和一张分析表
id+id*id$
Sm
Xm
Sm-1
Xm-1
…S0
LR驱动程序
动作 转移action goto
输出
92
分析表: 移进 ai 和 s=goto[sm,ai] 进栈action[sm,ai]= 归约 rj : AXm-r+1Xm-r+2…Xm
接受 s=goto[sm-r , A]
出错G[E’]:
(0) E’ E (1) E E +T
(2) E T (3) T T*F
(4) T F (5) F (E)
(6) F id
93
表 4.11 文法( 4.22 )的 SLR 分析表 id + * ( ) $ E T F
0 s5 s4 1 2 31 s6 acc2 r2 s7 r2 r23 r4 r4 r4 r44 s5 s4 8 2 35 r6 r6 r6 r66 s5 s4 9 37 s5 s4 108 s6 s49 r1 s7 r1 r1
10 r3 r3 r3 r311 r5 r5 r5 r5
94
LR 分析器的工作过程
格局:(栈中符号序列,剩余输入符号串)
开始:( s0, a1 a2 ……an$)
中间: ( s0x1s1x2s2…xmsm ,aiai+1…an$)
x1x2…xmaiai+1…an 是一个右句型。
1. 若 action[sm.ai]=si ,
则把 ai,si=action[sm ,ai] 推进栈 .
格局:( s0x1s1x2s2…xmsm aisi , ai+1…an$)
95
2. 若 action[sm.ai]=r (Axm-r+1xm-r+2…xm) , 则
格局:( s0x1s1x2s2…xm-rsm-r As , ai ai+1…an$)
其中, s=goto[sm-r ,A]
3.若 action[sm.$]=accep, 则分析结束。
4 .若 action[sm,ai]=error, 则转出错处理程序。
下面,显示 id+id*id的 LR 分析过程:
96
栈 输入 动作0 id+id*id$ s5 0id 5 +id*id$ r6 Fid 0F3 +id*id$ r4 TF
0T2 + id*id$ r2 ET
0 E1 +id *id$ s6
0E1+6 id *id$ s5
0E1+6 id5 *id$ r6 Fid
0 E1+6 F3 *id $ r4 TF
0E1+6T9 *id $ s7
0E1+6T9*7 id $ s5
0E1 +6T9*7 id5 $ r6 Fid
97
栈 输入 动作0E 1+6T9*7 F10 $ r3 TT*F
0E1+6T9 $ r1 EE+T 0E1 $ accep
0 5
3
2
1 6 9 7 10
id
F
T
E + T * F
id idF
分析表 4.11 是识别文法 G[E]活前缀的有限自动机。
98
4.7.2 SLR 分析表的构造
根椐文法 G ,构造识别文法 G 的所有活前缀的 DFA m ,根椐 DFA m 构造分析表。分析表是 DFA m 的一种描述形式。
一 . 项目
识别活前缀的 DFA m 每个状态是一个项目集。
S A , VT
活前缀: 的前缀是右句型 的活前缀。
*rm rm
99
活前缀和句柄的关系:1. 活前缀不含有句柄的任何符号, ;2. 活前缀含有句柄的部分符号, 1 ;3. 活前缀已含有句柄的全部符号, 。 识别活前缀的自动机处于的格局:
0
q0 q1
q
0 q q2
1
100
用圆点“”表示识别一个产生式右部符号到达的位子,若有规则 AXYZ, 则有下面四个项目: A XYZ A a,aVT 移进项目 A X YZ A B,BVN
A X Y Z 待归约项目 A X YZ A, A A 归约项目 S´S, S´S 接受项目以上项目称作 LR(0) 项目。
101
二 . 有效项目集和转移函数
定义 4.6( 识别活前缀的有效项目 ) 如果存在一个规范推导
S αAw αβ1β2w
项目 A→β1·β2 对识别活前缀 γ=αβ1 是有效的。
有下面的结论: 若项目 A→α·Bβ 对识别活前缀 =δα 是有效的且若 B→ηP ,则项目 B→·η 对识别活前缀 =δα也是有效的。
*rm
rm
102
推导如下:
若项目 A→α·Bβ 对识别活前缀 =δα 是有效,则存在一个规范推导
S δAw δαBβw
设 βw xw , 则对任何 B→ηP ,有
S δAw δαBβw
δαBxw δα η xw
则 B→·η 对识别活前缀 =δα 也是有效的。
A→α·Bβ 和 B→·η 在同一个项目集中。
*rm
rm
*rm
*rm
rm
*rm
rm
103
识别文法 G 的某个活前缀 γ 的所有有效项目组成的集合称为 γ 的有效项目集。文法 G 的所有有效项目集组成的集合称为 G 的 LR(0) 项目集规范族。 定义 4.8 设 I是文法 G 的一个 LR(0) 项目集合 , closure(I) 是从 I出发用下面三个规则构造的项目集 : 1. I中每一个项目都属于 closure(I) 。 2 .若项目 A→α·Bβ closure(I)且B→ηP 则将 B- ·η加进 closure(I) 中。 3.重复执行 (2) 直到 closure(I) 不再增大为止。 显然对任何有效项目集 I,都有 I= closure(I) 。
104
定义 4.8 ( 转移函数 )
若 I是 G 的一个 LR(0) 项目集 , X {VTVN}
go(I,X)=closure(J) 其中 , J={A→αX·β| 当 A→α·Xβ I时 } go(I,X) 称为转移函数。项目 A→αX·β 称为A→α·Xβ 的后继。
I: A→α·Xβ J: A→αX·βX
I 中项目 A→α·Xβ 是识别某个活前缀 γ=δα 的有效项目,则有规范推导:
105
S Aw Xw
J中项目 A→αX·β 是活前缀 γX 的有效项目。 算法 4.8 计算 closure(I)和 go(I,x) FUNCTION closure(I:SET OF item):SET OF item; BEGIN REPEAT FOR A→αBβ I FOR B→ η P I:=I∪{B→ η} UNTIL I 不再增大 ; Return (I) END; {A→αBβ 是项目集 I的核项目 }
*rm rm
106
FUNCTION go(I:SET OF item; x∈{VT∪VN})
:SET OF item; VAR J:SET OF item; BEGIN J:={ }; FOR A→αXβ I J:=J∪{A → αX β} Return (closure(J)) END;
107
LR(0) 项目集规范族的构造 PROCEDURE items(G ); BEGIN C:={closure( S → S) }; REPEAT FOR IC 和 X {VT V∪ N} 把 go(I,X) 加入到 C中 UNTIL C 不在增大 END; DFA m= (VT V∪ N {S ∪ }, Q{ 项目集规范族 }, q0= closure{S → S},Q,=go(I,X) )它识别文法 G 的所有活前缀。
108
三 . 例示 LR(0) 分析表的构造 例 4.17 ( 0) S´S (1) S aA (2) S bB (3) A cA (4) A d (5) B cB (6) B d
I0
S ´SS aAS bB
aS aAA cAA d
S ´SS
b
S bBB cBB d
c A c AA cAA d
c
图 4.17
A d
dd
I1
I2
I3
I4
I10
109
根据图 4.17的 DFAm ,其 LR(0) 分析表如下:
a b c d $ S A B
0 S2 S3 1
1 acc
2 S4 S10 6
3 S5 S11 7
10 r4 r4 r4 r4 r4
110
结论 : 对于拓广文法 G 的每一个活前缀 ,识别它的有效项目集恰好是从它的识别活前缀的 DFA 的初态出发经过 道路所到达的那个状态所代表的项目集。事实上,在任何时候分析栈中的活前缀 X1X2…Xm 的有效项目集恰恰是栈顶状态 Sm 所代表的那个集合。这也表示栈顶状态体现了栈里一切有用的信息。 对同一个活前缀存在若干不同的项目对它是有效的,而且它告诉我们应做的事情可能各不相同,互相冲突。这种冲突通过向前看几个输入符号或许能够解决。
111
四 . SLR 分析表的构造 有的文法,识别它的活前缀的 DFA 的状态集中,有的状态的项目集中含有冲突项目。
例: (0) E ´E (1) E E+T (2) E T (3) T T*F (4) T F (5) F (E) (6) F id
E ´EE E+TE TT T*FT FF (E)F id
112
E ´EE E+TE TT T*FT FF (E)F id
E
E ´E E E+T
T
E T T T*F
(
F ( E) E E+TE TT T*FT F F (E) F id
I0
I1
I2
I6
F T F I3
F idid I5
T I2
F I3
id I5
(
E E+ TT T*FT F F (E) F id
+
*T T* F F (E) F id
I7
F (E ) E E+T
EI8
T E E+ T T T*F
I9
F I3id
I5
(
FT T* F
I10id
I4
( I4
*I7
)
F (E)
+ I6
I11
113
I1:E´E I2: E T I9: E E+T E E+T T T *F T T *F
I={X b , A , B }
若 {b}FOLLOW(A) FOLLOW(B)=则,面对当前读入符号 a ,状态 I 的解决方法: 1. 若 a=b, 则移进。 2. 若 a FOLLOW(A), 则用 A 进行归约。 3. 若 a FOLLOW(B), 则用 B 进行归约。 4. 此外,报错。这种解决方法是比较简单的,因此称作 SLR分析,由此构造的分析表,称作 SLR 分析表。
114
对于表达式文法的例子, FOLLOW 集如下:
E’ $E $ ) +T $ ) + *F $ ) + *
五 . 非 SLR(1) 文法
例 4.19
(0) S´S (1) S L = R (2) S R
(3) L * R (4) L id (5) R L
I1:{ E´E EE+T}I2: {ET T T *F}I9:{E E+T T T *F}
115
S´·S S · L = R S · R L · * R L · id R · L
I0 S
S´S ·I1
L S L · = R R L ·
I2
R S R · I3
*L * · R R · L L · * R L · id
I4
id
L id ·I5
=S L = · RR · L L · * R L · id
I6
RL * R · I7
L R L ·
*
idI8
R
S L = R · I9
LII8
* II4
idII5
FOLLOW(R)={=,$},非 SLR 文法。
116
作业: 4.24
4.25
4.21
117
4.7.3 LR(1) 分析表的构造
SL=RR L
I2
A Ik
aFOLLOW(A)
S Aw w
FOLLOW(A)比上面推导中 w 的第一个符号集要大,可能不存在下面的推导:
S Aax ax (w ax)*r m
r m
*r m
r m
r m
118
栈中已识别出一个 L ,此时, a 是“ =” ,不能用“ RL” 进行归约,因为不存在“ R=” 的归范句型。 解决方法:使每个项目中含有更多的展望信息,使得能确切知道何时能进行归约。一 . LR(k) 项目 形式: [A , a1a2…ak]
,移进或待归约项目, a1a2…ak 不起作用。对归约项目 [A, a1a2…ak] ,仅当前输入符号串开始的前 k 个符号是 a1a2…ak
时,才能用 A 进行归约。 a1a2…ak 称向前搜索符号串。
119
*r m
r m
二 . LR(1) 的有效项目集和转移函数
定义 4.10 (LR(1) 的有效项目 )
如果存在一个规范推导
S δAw w
其中或w的第一个符号为 a,或w=ε而 a为$。一个 LR(1) 项目 [A→α·β,a] 对活前缀γ= 是有效的。 例 4 . 17 考虑文法 G: S→CC C→cC|d
*r m
r m
120
因为有规范淮导
S ccCcd cccCcd
故项目[ C→c·C,c] 对活前缀 ccc 是有效的。
若项目 [ A→α·Bβ,a] 对活前缀 γ=δα 是有效的,则存在一个规范推导 S δAax δαBβax
假定 βax by ,则对每一个形如B→η 的产生式我们有规范推导
S Bby by
*r m
r m
*r m
r m
*r m
*r m
r m
121
于是 , 项目[ B→·η,b] 对于活前缀 γ=δα 也是有效的。注意到 b 或者是从 β 推出的第一 个终结符号,或者 β ε而 b= a 。这两种可能性
结合在一起,则 b∈FIRST(βa)。 定义 4.12 设 I是 G 的一个 LR(1) 项目集, closure(I) 是从 I出发用下面三个规则构造的项目集 : 1 .每一个 I中的项目都属于 closure(I)。 2 .若项目[ A→α·Bβ, a]属于 closure(I) 且 B→η P, 则对任何 b∈FIRST(βa), 把 [ B→·η,b]加进 closure(I) 中。 3.重复执行 (2) 直到 closure(I) 不再增大为止。
*r m
122
定义 4 . 13 设 I是 G 的一个 LR(1) 项目集, X 是一个文法符号,定义 go(I, X) = closure(J) , 其中 J={[A→ X·β,a] | 当[ A→ ·Xβ,a] I时 } 算法 4.10 计算 LR(1)的 closure(I)和go(I,x) FUNCTION closure(I:SET OF item):SET OFitem; { REPEAT FOR 任一 [A→ · Bβ,a] IN I FOR 任一 B→η IN P FOR 任一 b IN FIRST(βa) I:=I∪{[B→ ·η,b]} UNTIL I 不再增大 ; Return (I) ; }
123
FUNCTION go(I:SET OF item;x∈{VT∪VN}): VAR J:SET OF item ; SET OF item; { J:={ }; FOR [A→α·Xβ,a] IN I J:=J∪{[A→αX·β,a]} Return (closure(J)) ;} 算法 4.11 LR(1) 项目集规范族的构造方法:
PROCEDURE items(G´); { C:={closure( {[ S’→·S, $ ]}) }; REPEAT FOR 项目集 I∈ C 和 x {V∈ T V∪ N} 把 go(I,X) 加入到 C 中 UNTIL C 不在增大 ;}
124
三 . 例示 LR(1) 项目集及规范族的构造
G(S´): (0) S´S (1) S CC
(2) C cC (3) C d
I0
[S´S, $]若 [A ,a] I
且 B P
则 [B,b] I
其中 b FIRST(a)
[S CC,$]
[C cC,c/d][C d,c/d]
125
[S´S,$][SCC,$][CcC,c/d][Cd,c/d]
I0 S[S´S,$]
I1
C[SCC,$][CcC,$][Cd,$]
I2
c[CcC,c/d][C cC,c/d] [Cd,c/d]
I3d
[Cd,c/d]I4
C
[SCC,$] I5
c[CcC,$][CcC,$] [Cd,$]
I6
C[CcC ,$] I9
d
[Cd,$] I7
cd
cC
[CcC ,c/d] I8
d
126
五 . 构造 LR(1) 分析表c d $ S C
0 S3 S4 1 21 acc2 S6 S7 53 S3 S4 84 r3 r3
5 r1
6 S6 S7 97 r3
8 r2 r2
9 r3
127
作业: 4.30 4.31
128
4.7.4 LALR 分析表的构造 LALR(lookahead-LR)技术。这种方法在实际中是经常使用的。 定义 4.14 如果两个 LR(1) 项目集去掉搜索符之后是相同的 ,则称这两个项目集具有相同的心 (core) 。 一个心就是一个 LR(0) 项目集。 定义 4.15 除去初态项目集外,一个项目集的核 (kernel) 是由此集中那些圆点不在最左端的项目组成。 LR(1) 初态项目集的核含有也仅含有 [S'→·S,$] 。
129
[S´S,$][SCC,$][CcC,c/d][Cd,c/d]
I0 S[S´S,$]
I1
C[SCC,$][CcC,$][Cd,$]
I2
c[CcC,c/d][C cC,c/d] [Cd,c/d]
I3d
[Cd,c/d]I4
C
[SCC ,$] I5
c[CcC,$][CcC,$] [Cd,$]
I6
C[CcC ,$] I9
d
[Cd,$] I7
cd
cC
[CcC ,c/d] I8
d
130
改进思路:
合并同心集可达到缩小构造分析表的状态数目;利用核代替项目集可以达到缩小项目集所占用的存储空间。介绍两种方法:
第一种方法:对于 G ,构造 LR(1) 项目集规范族 (DFA) ,然后,合并同心集,若合并后的同心集中没有移进归约冲突,则用其构造 LR 分析表 , 这种分析表称作 LALR 分析表。
图 4.27 中
I3 和 I6 I4和 I7 I8和 I9
131
[S´S,$][SCC,$][CcC,c/d][Cd,c/d]
I0 S[S´S,$]
I1
C[SCC,$][CcC,$][Cd,$]
I2
c[CcC,c/d][C cC,c/d] [Cd,c/d]
I3d
[Cd,c/d]
I4
C
[SCC ,$]I5
c[CcC,$][CcC,$] [Cd,$]
I6
C[CcC ,$] I9
d[Cd,$] I7
cd
cC
[CcC ,c/d] I8
d
c[CcC,c/d/$][CcC,c/d/$][Cd,c/d/$] [Cd,c/d/$]
I47
[CcC ,c/d/$] I89
I36
I47
132
讨论:1. 由于 go(I, X)仅仅依赖于 I的心,因此 LR(1) 项目集合并后的转换函数 go(I, X) 随 自身的 合并而得到。2. 动作 action 应当进行修改,使得能反映各被 合并集合的既定动作。3. 把同心的项目集合并为一,有可能导致冲突, 这种冲突不会是移进 - 归约冲突 ;但可能引起 归约 -归约冲突。 Ik:{[A,u1] [Ba,b] } au1= Ij:{[A,u2] [Ba,c] } au2= Ikj:[A,u1/u2] [Ba,b/c] a{u1, u2}=
133
例:下面文法是 LR(1) 的,但不是 LALR(1)的。
S´ S S aAd bBd aBe bAe A c B c
[S´ S, $] [S aAd, $][S bBd, $][S aBe, $][S bAe, $]
[S´ S ,$]
[S a Ad, $][S a Be, $][A c, d][B c, e]
aS
[S b Bd, $][S b Ae, $][A c, e][B c, d]
b
A [S a A d, $]B [S a B e, $]c
[A c , d] [B c , e]
[A c , e][B c , d]
c
134
LR(1)和 LALR(1) 分析上的差别
输入: ccd$
LR: 0 c 3 c 3 d 4 报错
LALR: 0 c 36 c 36 d 47
0 c 36 c 36 C 89
0 c 36 C 89
0 C 2 报错
135
LALR 分析表的有效构造方法 用 LR(0) 的项目集的核项目代替项目集,为每个核项目配上搜索符号 ,得到 LALR 项目集族的每个项目集的核项目。 可行性 1. 用项目集的核构造 go函数 若 [BX, b ] Ikernel X{XTXN}
则 go(I,X) kernel : = [BX, b ]
若 [BC, b ] Ikernel
且 C A AXP
则 [AX, a] go(I,X) aFIRST(b)
*rm
136
C A 存在规则链 :
C C11 C1 C22 Cn A n
2. 用项目集的核构造 action 表 (a) action[I, a] :=rA A Ikernel
若 [BC, b ] Ikernel
且 C A aFIRST(b)
则 action[I, a] :=r A
存在规则链 : C C11
C1 C22 Cn A n A
*rm
*rm
137
(b) 若 [BC, b ] Ikernel
且 C a (最后一步不使用 - 产生式)
则 action[I, a] := S go(I,a)
C a P 或
C C11 C1 C22 Cn a
例示构造方法 G[S´]: (0) S´S (1) S L=R
(2) S R (3) L *R (4.23)
(4) L id (5) R L
*rm
138
G[S´]: (0) S´S (1) S L=R (2) S R (3) L *R (4) L id (5) R L 1. 构造 G[S´]的 LR(0) 项目集的核
S ´S
I0S
S ´S I1
SL=RR L L
I2S R
R
I3
L * R*
I4
Lid
id
I5
= S L= R I6
R L * R I7
RL L I8*
id
RS L= R I9
L* id
I5
139
2. 根据 I 的核和 X, 确定 go(I,X) 的核项目的搜索 符号。它分成自生的还是传播的。 看 用项目集的核构造 go函数的过程: 若 [BX, b ] Ikernel X{XTXN}
则 go(I,X) kernel : = [BX, b ]
项目 [BX, b ] 把 b传播给 [BX, b ]。 若 [BC, b ] Ikernel
且 C A AXP
则 [AX, a] go(I,X) aFIRST(b) 若,则 [AX, a] 中的 a 是自生的; 若 = ,则 [AX, a] 中的 a= b 是传播的。
*rm
140
项目 [S´S, $] 中的 $ 是自生的。
算法 4.11 确定搜索符的方法 (# 是测试符号) FOR BK DO { J ´:=closure({[B, #]}); IF [AX, a] J ´且 a# THEN go(I, X)K 中 [AX, a] 的 搜索符 a 是自生的。 IF [AX, #] J ´ THEN 搜索符从 I 中的 B传播到 go(I, X)K中 AX }
141
例示算法 4.11 G[S´]: S´S SL=R
SR L*R Lid R L
[S´S , #]
[SL=R , #]
[SR , #]
[L*R , =/#]
[Lid , =/#]
[R L , #]
S[S´S , #]
[SL =R , #][R L , #]
L
R[SR , #]
* [L*R , =/#]
id[Lid , =/#]
142
S ´S
I0S
S ´S I1
SL=RR L L
I2S R
R
I3
L * R*
I4
Lid
id
I5
= S L= R I6
R L * R I7
RL L I8*
id
RS L= R I9
L* id
I5
3-4 根据( 2 )得到的信息,开始,给每个核项目加上自生的搜索符;然后,反复传播搜索符,直到每个核项目的搜索符不再增大为止。
$
=
=
$
$$
$
$
$
=
=
$
$
$
$
143
4.8 LR 分析方法对二义文法的应用
E→E+ E|E*E|(E)|id (4. 25)
E´EE E+EE E*E E (E)E id
E´EE E +EE E *E
E
E ( E)E E+EE E*E E (E)E id
(
E id id
id
(
+ E E+ EE E+EE E*E E (E)E id
E
E E+ E E E +EE E *E
144
LR(k)和 LL(k) 的比较
1. A1 2
LL(k) 根据 FIRST(i) 确定使用哪条产生式;而 LR(k) 是在识别出整个后 ,再往后看 k 个符号 ,然后确定使用哪条产生式。
w
A
w
A
145
LL(k) 文法都是 LR(k) 文法。2. 都能用形式化方法实现;3. 非 LR 结构 例: L={wwR w{a,b}*}
G[S]: SaSa bSb 4. LR(k) 分析用手工构造是不可能的。类 Pascal 语言的 LR(1) 分析表,估计要数千 个状态;由于有软件工具, LR 分析受到 广范重视。
146
4.9 分析器的生成器 Yacc
一 . 用生成器 Yacc 构造翻译器的过程
Yacc编译器
yacc 源程序translate.y
y.tab.c
C编译器y.tab.c a.out
a.out源程序 输出
147
二 . Yacc 源程序有三部分组成 声明 %% 翻译规则 %% C写的支持例程
三 . 例 4.21 台式计算器 G[E]: EE+T T T T*F F F (E) digit 读入一个整表达式,计算它的值并输出。
148
%{# include <ctype.h>%}% token digit
%%line : expr´\n´ {printf(“%d\n”,$1);} ;expr : expr ´+´term {$$=$1+$3;} : term ;
149
term : term ´*´facter {$$=$1*$3;} : facter ;facter : ´(´expr ´)´ {$$=$2;} : digit ;%%yylex(){ int c; c=getchar(); if (isdigit( c ){ yylval=c- ´0´; return digit ; } return c ; }
150
声明部分 有任选的两节。 第一节处于分界符%{和%}之间,它是一 些普通的 C的声明; 第二节是文法记号的声明。
翻译规则部分 每条翻译规则由一个文法产生式和有关的语义动作组成。
支持例程部分 一些 C写的支持例程。例:词法分析器,错误恢复例程等。
151
总结:
自顶向下分析
递归预测分析(递归子程序法)
非递归预测分析—— LL(1)
注意:首先消除左递归和提取左公因子。
自底向上分析
算符优先分析
LR 分析 : SLR(1), LR(1), LALR(1)
152
LL(0) LR(0)
SLR
LALR
LR(1)LR(k)
LL(1)
LL(k)
Unambiguous Grammars Ambiguous Grammars
文法类的谱系
153
作业: 4.30
4.31 试构造 LALR( 1 )分析表。
4.21(e)