根据文法规则,从源程序单词符号串中识别出语法成分,并进行语法检查。
识别符号串S是否为该文法的某语法成分。
①自顶向下分析
基本思想:
②自底向上分析
基本思想:
对比:
类型 | 方法 | 主要问题 |
---|---|---|
自顶向下分析 | 递归子程序法、LL分析法等 | ①左递归问题:左递归(直接或间接)会 必然 导致分析过程出现死循环 ②回溯问题:试探失败时,需要将输入串指针和语法树都回溯到试探开始时的状态,这很影响效率 |
自底向上分析 | 算符优先分析法、LR分析法等 | ①如何识别句柄 ②二义性文法 |
给定符号串S,若预测它是某一语法成分,那么可根据该语法成分的文法,设法为S构造一棵语法树。
若成功,则S最终被识别为某一语法成分。即S∈L(G[Z]) 其中G[Z]为某语言成分的文法。
若不成功,则 S 不属于 L(G[Z]) 。
使用扩充的BNF表示来改写文法
通过以下两条规则,就能消除文法的 直接左递归,并且保证文法的等价性:
改写规则:
(1)提因子
每当出现形如 U∷= x y | x w |….| x z的规则,则将其改写为:U∷= x ( y | w |….| z )
其中若还有形如y = y1 y2, w = y1 w2的情况,则继续改写为 U∷= x ( y1 ( y2 | w2 ) |….| z )
注:
①若情况是 y x | w x |….| z x,则x 不是公因子,不进行提取。
②若有规则:U∷= x | x y,则应改写为:U∷= x ( y |ε),而不是U∷= x (ε| y)。要尽量 将ε安排为最后的选项。
(2)改写直接左递归
若有文法规则:U∷= x | y |…| z | U v,就改写为U∷= ( x | y |…| z ) { v }
若有规则:P∷= P α | β
则可改写为:P ∷= β P’ ,P’ ∷= β P’ | ε
方法:
1.把G的非终结符整理成某种顺序A1,A2,……An ,使得
A1::= δ1|δ2|……|δk
A2::= A1r……
A3::= A2u |A1 v……
…….
注:即在后面(按序号排)规则的右部中包含前面规则左部的非终结符。
2.根据算法:
3.化简步骤2得到的文法。
回溯:分析工作要部分地或全部地退回去重做叫回溯。
回溯的条件:文法中,对于某个非终结符号的规则其 右部有多个选择,并根据所面临的输入符号不能准确地确定所要的产生式,就可能出现回溯。
例如:
U::= α1 | α2 | α3
回溯的坏处:效率严重低下,只有在理论上的意义而无实际意义。
避免回溯的方法
定义:设文法G(不具左递归性),U∈Vn
U::= α1 | α2 | α3
FIRST(αi) = {a | αi => a…, a ∈Vt }
方法:
要求文法满足:FIRST(αi ) ∩ FIRST(αj ) =φ (i ≠ j)
即:对文法中的任意一个Vn,其规则右部有多个选择时,由各选择推出的终结符号串的 头符号集合 要两两不相交。
消除回溯的方法
1.改写文法
做法:对具有多个右部的规则 反复 提取左因子
2.超前扫描
做法:当文法不满足避免回溯的条件时,即各选择的首符号相交时,可以采用超前扫描的方法,即 向前侦察 各输入符号串的第二个、第三个符号来确定要选择的目标。
为了实现不带回溯的自顶向下分析,对文法需要满足两个条件:
1、文法是非左递归的;
2、对文法的任一非终结符,若其规则右部有多个选择时,各选择所推出的终结符号串的首符号集合要两两不相交。
解决问题的方法:
做法:对语法的每一个 非终结符 都编一个分析程序。当根据文法和当时的输入符号预测到要用某个非终结符去匹配输入串时,就调用该非终结符的分析序。
过程意义:递归子程序法对应的是 最左推导 过程
优点与缺点:
优点:
①结构、层次清晰,可读性好
②易于手工实现
③时空效率较高
主要缺点:更多的手工编写和调试工作
算法框图示例:
注:
①子程序出口前要读符号
②子程序定义时若需要调用其它子程序,调用前要先读符号。
LL(1)-自左向右扫描(L)、自左向右地分析和匹配输入串(L)、每进行一步推导,只需要向前查看一个输入符号(1)。
其分析过程也表现为 最左推导 的性质
此过程由三部分组成:分析表、执行程序 (总控程序)、符号栈(分析栈)
分析表示例:
执行程序分析过程:
设有文法G [ Z ] :
定义1 FitstVt集合:
定义2 FollowVt集合:
设α= X1 X2 … Xn , Xi∈Vn∪Vt
(1) 若Xi∈Vt,则 FIRST( Xi ) = { Xi }
(2) 若Xi∈Vn 且 Xi ∷= a…|ε, a∈Vt 则 FIRST( Xi ) = { a , ε}
(3) 若Xi∈Vn且Xi∷= y1 y2 …yk,则按如下顺序计算FIRST(Xi)
●若ε∈ FIRST(y1) 则将FIRST(y1) 加入 FIRST(Xi) ;
●若ε∈ FIRST(y1) 则将FIRST(y2) – {ε}加入FIRST(Xi)且若ε∈ FIRST(y2) 则将FIRST(y3) – {ε}加入FIRST(Xi)
…
ε∈ FIRST(yk-1) 则将FIRST(yk) – {ε}加入FIRST(Xi)
最后,若ε ∈FIRST(y1) ~ FIRST(yk) 则将ε加入FIRST(Xi)
这样,得到FIRST(Xi),即可求出FIRST(α)。
设S, A, B∈Vn,
算法:连续使用以下规则,直至FOLLOW集合不再扩大:
(1) 若S为识别符号,则把“ # ”加入FOLLOW(S)中;
(2) 若A∷=αBβ(β≠ε),则把FIRST(β)-{ε}加入FOLLOW(B);
(3) 若A∷=αB 或A∷=αBβ, 且β =*> ε则把FOLLOW(A)加入FOLLOW(B) 中去。
注意:FOLLOW集合中不能有ε!!!
基本思想:
当文法中某一非终结符呈现在栈顶时,根据当前的输入符号,分析表应指示要用该非终结符里的哪一条规则去匹配输入串(即进行下一步最左推导)。
算法:
LL(1)文法:对于能用上述算法构造分析表的文法我们称为LL(1)文法,即M [ A , a ] 只对应一条规则或Error。
定义:一个文法G,其分析表M不含多重定义入口(即分析表中无两条以上规则),则称它是一个LL(1)
对于某些文法(非LL(1)文法),有些M[A,a]中可能有若干条规则,这称为分析表的多重定义或者多重入口。
也就是说:如果G是左递归或二义的,那么M至少含有一个多重定义入口。因此,消除左递归和提取左因子将有助于获得无多重定义的分析表M。
反之,一个文法G的预测分析表M不含多重定义入口,当且仅当该文法为LL(1)的。
改写:
只有部分文法可以从非LL(1)文法改写为LL(1)文法。
LL分析的错误恢复:
当符号栈顶的终结符和下一个输入符号不匹配,或栈顶是非终结符A,输入符号a,而M [ A , a ]为空白(即error)时,则分析发现错误。
错误恢复的基本思想:跳过一些输入符号,直
到期望的同步符号之一出现为止。同步符号是指可重新开始继续分析的输入符号。
非终结符A的同步符号集:FOLLOW(A)∪FIRST(A)∪语句开头的关键字集合
终结符在栈顶而不能匹配:弹出该终结符并发出错误信息信息后继续分析。(把所有其他符号均作为该符号的同步集合元素)
基本算法思想:
若采用自左向右地扫描和分析输入串,那么自底向上的基本算法是:
从输入符号串开始,通过反复查找当前句型的句柄(最左简单短语),并利用有关规则进行规约。若能规约为文法的识别符号,则表示分析成功,输入符号串是文法的合法句子;否则有语法错误。
分析过程是 重复以下步骤 :
1、找出当前句型的句柄 x (或句柄的变形);
2、找出以 x 为右部的规则 X::= x ;
3、把 x 规约为X,产生语法树的一枝。
关键:找出当前句型的句柄 x (或其变形)
要点:设置符号栈,用来纪录分析的历史和现状 ,并根据所面临的状态,确定下一步动作是移进还是规约。
分析器:
分析过程:
注意:
这种方法我们是默认:
① 栈内符号串 + 未处理输入符号串 = 当前句型
② 句柄都在栈顶(未真正解决句柄的识别问题)
也就是说,移进-规约分析实际上是不靠谱 的,因为:
我们不能认为:对句型 xuy 而言,若有U∷= u,即U => u 就断定u是简单短语,u 就是句柄。而是要同时满足Z =*> xUy。
算符优先分析法是一种经典的自底向上分析法,简单直观,并被广泛使用。开始主要是对表达式的分析,现在已不限于此,可以用于一大类上下文无关文法(称为OPG)。
特点:仿效四则运算过程,预先规定相邻终结符之间的优先
关系,然后利用这种优先关系来确定句型的句柄(或句柄的变式),并进行规约。
分析器:
优先关系矩阵示例:
分析步骤:
(1) 确定终结符之间的优先关系,构造优先关系矩阵。
优先关系:
(2)根据优先关系矩阵,利用算法:
当栈顶项(或次栈顶项)终结符的优先级大于栈外的终结符的优先级则进行规约,否则移进。
(3)出错情况
重要说明:
(1)上述分析过程不一定是严格的最左规约(即不一定是规范规约)。也就是每次规约不一定是规约当前句型的句柄,而是句柄的变形,但也是短语。
(2)实际应用中,文法终结符间优先关系一般不用矩阵表示,而是采用两个优先函数来表示:
f — 栈内优先函数
g — 栈外优先函数
若 a < b 则令 f ( a ) < g ( b )
若a = b 则令 f ( a ) = g ( b )
若a > b 则令 f ( a ) > g ( b )
特点:
① 优先函数值不唯一。
② 优点:
• 节省内存空间。
若文法有n个终结符,则关系矩阵为n^2,而优先函数为2n。
• 易于比较:算法上容易实现。数与数比,不必查矩阵。
③ 缺点:可能掩盖某些错误。
条件:
①若文法中无形如U∷= …VW…的规则,这里V, W∈Vn,则称G为OG文法,也就是算符文法。
即:算符文法不允许两个非终结符相邻!
②在任意两个终结符之间,优先关系唯一,则称该文法为算符优先文法(OPG)。
三种可能的优先关系的条件:
构造FIRSTVT(U)的算法:
1)若有规则U∷= b…或U∷= V b… 则b∈FIRSTVT(U)(FIRSTVT的定义中一步推导)
2)若有规则U∷= V…且 b∈FIRSTVT(V), 则b∈FIRSTVT(U)(FIRSTVT的定义中多步推导)
有这两个集合后,按照以下规则确定这两种优先关系:
构造LASTVT(U)的算法:
1)若有规则U::=…a 或 U::=…aV,则a∈LASTVT(U)
2)若有规则U::=…V,且a∈LASTVT(V) 则a∈LASTVT(U)
构造优先关系矩阵的算法
(1)构造出FirstVt、LastVt集合。
(2)找出规则右部所有VtVn、VnVt的组合。
(3)对每个(2)中组合找出Vt和FirstVt集或LastVt集的关系。
(4)整理关系,填表。
先定义优先级,在分析过程中通过比较相邻运算符之间的优先级来确定句型的“句柄”并进行归约。
这里的“句柄”实际上叫做:最左素短语。
定义:文法G的句型的素短语是一个短语。它至少包含有一个终结符号,并且除它自身以外不再包含其它素短语。
个人理解:含有Vt的短语单元:不能从它里面截出更小的含Vt短语。
注意:素短语可以不是简单短语!
最左素短语
设有OPG文法句型为:
#N1 a1 N2 a2 …Nn an Nn+1#
其中Ni 为非终结符(可以为空),ai 为终结符。
注意:出现在 aj 左端和 ai 右端的非终结符号一定属于这个素短语,因为我们的运算是中缀形式给出的(OPG文法的特点)。
个人理解:如果不把这两个Vn纳入素短语,经多步规约到某一步后,必然会出现两个Vn相邻情况,且其他情况的短语都已经规约完成了,而OPG不允许有这种规则右部,无法继续规约。
分析过程:
基本部分是找句型的最左子串(最左素短语)并进行规约:
①当栈内终结符的优先级<或=栈外终结符的优先级时,移进;
②当栈内终结符的优先级>栈外终结符的优先级时,表明找到了素短语的尾,再往前找其头,并进行规约。
③接受条件为符号栈只剩#和开始符合,输入串只剩#。
LR分析:从左到右扫描(L)自底向上进行规约®(是规范规约,也即最右推导)是自底向上分析方法的高度概括和集中。(历史 + 展望 + 现状 => 句柄)
① 适合文法类足够大,适用于所有上下文无关文法
② 分析效率高
③ 报错及时
④ 可以自动生成
⑤ 但手工实现工作量大
① SLR分析表(简单LR分析表)
构造简单,最易实现,大多数上下文无关文法 都可以构造出SLR分析表,所以具有较高的实用价值。使用SLR分析表进行语法分析的分析器叫SLR分析器。
② LR分析表(规范LR分析表)
适用文法类最大,所有上下文无关文法 都能构造出LR分析表,但其分析表体积太大,实用价值不大。
③ LALR分析表(超前LR分析表)
这种表适用的文法类 及其实现上难易 在上面两种之间,在实用上很吸引人。使用LALR分析表进行语法分析的分析器叫LALR分析器。
LR分析器的构成
LR分析器有三部分: 状态栈、分析表、控制程序。
状态栈:
可进一步划分:
状态栈:
划分后的上部,放置分析器状态和文法符号。
S0, S1, …, Sm 状态
S0—初始状态
Sm —栈顶状态
栈顶状态概括了从分析开始到该状态的全部分析历史和展望信息。
符号串:
x1 x2… xm 其中:xi∈Vn∪Vt
划分后的下部,为从开始状态(S0)到当前状态(Sm)所识别出的 规范句型的活前缀。
规范句型前缀:将输入串的剩余部分与其连结起来就构成了规范句型。如: x1 x2 … xm ai… an为规范句型。
活前缀:若分析过程能够保证栈中符号均是规范句型的前缀,则表示输入串已分析过的部分没有语法错误,所以称为规范句型的活前缀。
规范句型的活前缀:
对于句型αβt,β表示句柄,如果αβ= u1 u2 … ur ,那么符号串u1 u2 …ui (1≤i≤r)即是句型αβt的活前缀。
个人理解:语法分析没有出错状态下的规范句型前缀就称为规范句型的活前缀。
分析表:
由两个矩阵组成,其功能是指示分析器的动作,是移进还是规约,根据不同的文法类要采用不同的构造方法。
a. 状态转移表 (GOTO表)
一个矩阵:
行—分析器的状态
列—文法符号(包括Vn和Vt)
GOTO[Si-1, xi] = Si
Si-1—当前状态(栈顶状态)
xi —新的栈顶符号
Si —新的栈顶状态(转移状态)
Si需要满足条件是:
若x1 x2 …. xi-1 是由 S0 到 Si-1 所识别的规范句型的活前缀,则 x1 x2 …. xi 是由 S0 到 Si 所识别的规范句型的活前缀。
实际上:状态转移函数GOTO是定义了一个以文法符号集为字母表的有穷自动机,该自动机识别文法所有规范句型的活前缀。
b. 分析动作表(ACTION表)
ACTION[ Si , a ] =分析动作 a∈Vt
分析动作:
① 移进 (shift)
ACTION[ Si , a ] = s
动作:将 a 推进栈,并设置新的栈顶状态为 Sj 。Sj =GOTO[ Si, a ],并将指针指向下一个输入符号。
② 规约 (reduce)
ACTION[ Si, a ] = rd
d:文法规则编号(d) A→β
动作:将符号串β(假定长度为n ) 连同状态从栈内弹
出,再把 A 推进栈,并设置新的栈顶状态为Sj。
Sj = GOTO[ Si-n , A ]
③ 接受(accept)
ACTION[ Si , # ] = accept
④ 出错 (error)
ACTION[ Si , a ] = error a∈Vt
控制程序:执行分析表所规定的动作,对栈进行操作。
1、 根据栈顶状态和现行输入符号,查分析动作表(ACTION表),执行由分析表所规定的操作;
2、并根据GOTO表设置新的栈顶状态(即实现状态转移)。
由分析过程可以看到:
(1) 每次规约总是规约当前句型的句柄,是规范规约!
(2) 分析的每一步栈内符号串均是规范句型的活前缀,且与输入串的剩余部分构成规范句型。
方法:
(1)根据文法构造识别 规范句型活前缀 的有穷自动机DFA:
①构造DFA:
DFA 是一个五元式 M = ( S, V, GOTO, S0, Z )
S:有穷状态集
在此具体情况下,S = LR(0)项目集规范族。
项目集规范族:其元素是由项目所构成的集合。
V:文法字汇表
S0:初始状态
Z:终态集合 Z = S - { S0 }
GOTO:状态转移函数
GOTO[Si , x]=Sj(Si, Sj∈S Si, Sj为项目集,x∈Vn∪Vt)
表示当前状态 Si 面临文法符号 x 时,应将状态转移到Sj 。
构造方法:
一、 确定 S 集合,即LR(0)项目集规范族,同时确定S0
二、确定状态转移函数GOTO
②构造LR(0)
LR(0)是DFA的状态集,其中每个状态又都是项目的集合。
项目:文法 G 的每个产生式(规则)的右部添加一个圆点就构成一个项目。
例1:
产生式:A→ X Y Z
项 目:
A→ . X Y Z
A→ X .Y Z
A→ X Y. Z
A→ X Y Z .
例2:
产生式:A→ε
项 目: A→.
项目的直观意义:指明在分析过程中的某一时刻的已经规约部分和等待规约部分。
构造方法:
①对于一个文法,如果能够构造一张分析表,使得它的每个入口均是唯一确定的,则我们将把这个文法称为LR文法。
②并非所有的上下文无关文法都是LR文法。但对于多数程
序语言来说,一般都可用LR文法描述。
定义:假若一个文法G的拓广文法G’的活前缀识别自动机中的每个状态(项目集)不存在下述情况:
若项目集[ A→α•Bβ ]属于 I 时,则[ B→• γ ] 也属于I。
把FIRST(β)作为用产生式归约的搜索符(称为向前搜索符),即用产生式B→γ归约时查看的符号集合(用以代替SLR(1)分析中的FOLLOW集),并把此搜索符号的集合也放在相应项目的后面,这种处理方法即为LR(1)分析方法。
LR(1)项目集族的构造:
针对初始项目S’→•S, # 求闭包后再
用转换函数逐步求出整个文法的LR(1)项目集族。
1)构造LR(1) 项目集的闭包函数
a) I 的项目都在closure(I)中;
b) 若A→α•Bβ, a属于closure(I),B→γ是文法的产生式,
β∈V*,b∈first(βa),则B→• γ, b也属于closure(I);
c) 重复b)直到closure(I)不再扩大为止。
2) 转换函数的构造
GOTO(I, X) = closure(J)
其中:I为LR(1)的项目集,X为一文法符号
J = {任何形如A→αX•β, a的项目 | A→α•X β, a属于I }
LR(1)分析表的构造算法:
LR(1)分析法的特点:
• 可适用的文法范围最大。
• 每个SLR(1)文法都是LR(1)文法,但反之不成立!
• LR(1)项目集的构造对某些项目集的分裂可能使状态数目剧烈地增长。
• 对LR(1)项目集规范族合并同心集,若合并同心集后不产生新的冲突,则为LALR(1)项目集。
• 相应的分析方法即为LALR(1)分析法。
合并同心集的几点说明
• 同心集合并后心仍相同,只是超前搜索符集合为各同心集超前搜索符的和集;
• 合并同心集后转换函数自动合并;
• LR(1)文法合并同心集后只可能出现归约-归约冲突,而没有移进-归约冲突;
• 合并同心集后可能会推迟发现错误的时间,但错误出现的位置仍是准确的。