1.5k likes | 1.71k Views
第四章 语法分析 语法分析是编译程序的核心部分、语法分析的作用是识别由词法分析给出的单词符号序列是否是给定文法的正确句子(程序), 自顶向下分析法也就是从文法的开始符号出发企图推导出与输入的单词串完全相匹配的句子,若输入串是给定文法的句子,则必能推出,反之必然出错。自顶向下分析法又可分为确定的和不确定的两种,确定的分析方法需对文法有一定的限制,但由于实现方法简单、直观,便于手工构造或自动生成语法分析器,因而仍是目前常用的方法之一。不确定的方法即带回溯的分析方法 ( 又称回溯法 ) ,这种方法实际上是一种穷举的试探方法,因此效率低,代价高,因而极少使用。.
E N D
第四章 语法分析 语法分析是编译程序的核心部分、语法分析的作用是识别由词法分析给出的单词符号序列是否是给定文法的正确句子(程序), 自顶向下分析法也就是从文法的开始符号出发企图推导出与输入的单词串完全相匹配的句子,若输入串是给定文法的句子,则必能推出,反之必然出错。自顶向下分析法又可分为确定的和不确定的两种,确定的分析方法需对文法有一定的限制,但由于实现方法简单、直观,便于手工构造或自动生成语法分析器,因而仍是目前常用的方法之一。不确定的方法即带回溯的分析方法(又称回溯法),这种方法实际上是一种穷举的试探方法,因此效率低,代价高,因而极少使用。
4.1 自顶向下的语法分析 4.1.1自顶向下的分析思想 不确定的自顶向下分析思想主要是带回溯的自上而下的分析方法,所谓带回溯的自顶而下的分析方法是对任何输入串试图用一切可能的办法,从文法符号开始符号(根结点)出发,自上而下,从左到右地为输入串建立分析树。或者说,为输入串寻找一个最左推导。这种。这种过程本质上是一种试探过程,是反复使用不同的产生式谋求匹配输入串的过程。 例:设有文法G[S]: S→aBC B→ib|b C→DE|FG|c D→d E→eh F→de G→t 假定输入串为 abdet
显然上述分析法中不能有形如P→Pα的规则,也不能有对某一非终结符P存在P=+=>Pα,即不能有规则左递归和文法左递归。 确定的自顶向下分析方法,首先要解决从文法的开始符号出发,如何根据当前的输入符号(单词符号)唯一地确定选用哪个产生式替换相应非终结符往下推导,或构造一棵相 应的语法树,现举例说明: 例:若有文法G1[S]: S→pA S→qB A→cAd A→a 若输入串W=pccadd,自顶向下的推导过程为 S=>pA=>pcAd=>pccAdd=>pccadd
例:若有文法G2[S]为: S→Ap S→Bq A→a A→cA B→b B→dB 当输入串W=ccap,那么试图推出输入串的推导过程为:S=>Ap=>cAp=>ccAp=>ccap
例:若有文法G3[S]: S→aA S→d A→bAS A→ε 若输入串为W=abd,则试图推导出abd串的推导过程为: S=>aA=>abAS=>abS=>abd
4.1.2 左递归和回溯性 1.左递归 左递归在自顶向下的分析技术中是有害的,为此使用自顶向下的分析方法的文法中不能出现左递归,因此需要消去左递归。如果对于某非终结符A存在着推导A=+=>Aα,则称文法左递归或间接左递归;如果存在规则A→Aα,则称规则左递归或直接左递归。对于规则左递归是可以消除的,而对文法左递归只能对满足一定条的文法进行消去。 (1)规则左递归的消除 a.改写规则成右递归 把 E→E+T|T 改写成 E→T+E|T 虽然在这里消去了左递归并不改变语言,但改变了结合性。 b.引进新的文法符号把E→E+T|T T→T*F|F F→(E)|i 改写成 E→TE’ E’→+TE’|ε T→FT’ T’→*FT’|ε F→(E)|i
一般地: A→Aα1|Aα2|Aα3|···|Aαm|β1|β2|β3|···|βn 改写成: A→β1 A’|β2 A’|β3 A’|···|βnA’ A’→ α1 A’|α2 A’|α3 A’|···|αm A’|ε 例:消去S→SS*|SS+|a中的左递归。 解:S→aS’ S’→ S*S’| S+S’|ε
(2)间接左递归的消除 如果一个文法是无环的(即P=+=>P)和没有ε产生式,那么可用下列算法消除。 a.以任意次序排列非终结符A1,A2,A3,···,An b.for(i=1;i<=n;i++) { for(j=1;j<=i-1;j++) 用产生式Ai→δ1γ|δ2γ|δ3γ|···|δkγ代替每个形式为 Ai→Ajγ的产生式其中Aj→δ1|δ2|δ3|···|δk是所有当前 Aj 产生式; 消去Ai产生式中的直接左递归 } c.除去那些从开始符号出发永远无法到达的非终结符的产生规则。
例:消去下列文法的左递归: S→Aa|b A→Ac|Sd|ε 解:设A1=S A2=A 则 S→Aa|b A→Ac|( Aa|b)d|ε S→Aa|b A→A(c|ad)|bd|ε S→Aa|b A→bdA’|A’ A’→cA’|adA’|ε 2.回溯性 在自顶向下的语法分析中,当前面临非终结符P如存在规则P→α1|α2|α3|···|αn,当前输入符号为a时,究竟选用哪一个候选式,如采用回溯则效率太低,现如能改变文法使每个候选式没有相同的“头符号”,则就能根据当前输入符号a决定选用那一个产生式。
例:S→if E then S|if E then S else S|b 改写成: S→if E then S S’|b S’→else S|ε 一般地 A→δβ1|δβ2|δβ3|···|δβn|γ 改写成: A→δA’|γ A’→β1|β2|β3|···|βn 例:消去文法G[S]: S→aBC B→ib|b C→DE|FG|c D→d E→eh F→de G→t 的回溯性 解:S→aBC B→ib|b C→dC’|c C’→eE’ E’→h|G G→t
例:若有文法G[S]: S→aSd S→Ac A→aS A→b 解:S→aSd S→aSc S→bc S→aSS’ S→bc S’→d|c 但要注意不是所有的文法都能通过提左公因子消去回溯性的。 例:若有文法G[S]: S→Ap|Bq A→aAp|d B→aBq|e 解:上述文法的语言: L[G]={andpn+1或ameqm+1|m,n≥0} 由于不知道具体句子中m和n那一个大,也就无法知道应提多少个a作为左因子
递归下降分析法 递归下降分析法又称递归子程序法简称递归下降法,它是一种无回溯的自顶向下分析技术,它的实现思想是让一个识别程序由一组过程(或函数)组成,其中每个过程对应于文法的一个非终结符号,由于文法的递归定义,故这些过程 往往是递归的,相应的程序称为递归下识别程序。 例:对于文法G[E]: E→E+T|T T→T*F|F F→(E)|i 消去左递归和提左共因子后: E→TE’ E’→+TE’|ε T→FT’ T’→*FT’|ε F→(E)|i 其递归子程序的流程图为:
其递归子程序为: void E() {T();E’(); } void E’() {if(下一个符号==’+’) {取下一个符号(); T();E’(); } }
void T(); { F();T’(); } void T’() {if(下一个符号==’*’) {取下一个符号(); F();T’(); } }
void F() { if(下一个符号==‘i’) 取下一个符号(); else if(下一个符号==‘(’) {取下一个符号();E(); if(下一个符号==‘)’) 取下一个符号(); else 出错(); } else 出错(); }
在具体实现中,可以将写成拓广的BNF,如下: E→T{+T} T→F{*F) F→(E)|i 其递归子程序如下: void E() {T(); while(下一个符号==’+’) {取下一个符号(); T(); } } void T() {F(); while(下一个符号==’*’) {取下一个符号(); F(); } }
void F() { if(下一个符号==‘i’) 取下一个符号(); else if(下一个符号==‘(’) {取下一个符号();E(); if(下一个符号==‘)’) 取下一个符号(); else 出错(); } else 出错(); }
递归下降分析法的优点: (1)方法简单 (2)能灵活地在各过程中添加语义动作 (3)当能用某种程序设计语言写出所有这些过程或函数(包括递归过程或函数)时,可以由该语言的编译系统来生成整个识别程序。
4.1.4 预测分析法 递归子程序是由一组递归过程组成的,它的实现极大程度地取决于递归过程的实现,如递归过程的允许嵌套调用层数,但某些程序设计语言不允许递归,也有些程序设计语言递归的嵌套层数受限制,为此引进了一种新的自顶向下的分析方法——预测分析法。 预测分析法也是一种无回溯的自顶向下的分析技术,当然一般情况下,其文法也满足LL(1)文法的条件。 所谓LL(K)分析法,这里的第一个L表示从左到右扫描输入符号串,第二个L表示分析过程中采用的是最左推导。括号中的1表向前(向右)看K个符号。
1.LL(1)分析器的逻辑结构和工作过程 一个LL(1)分析器是由一个总控程序,一个输入带、一张分析表和一个栈组成的,如图所示:
(1)分析表 在LL(1)分析器中使用了一张分析表。用M[A,a]形式的矩阵(或二维数组)表示,其中A 是文法的非终结符号,a是文法的终结符号或“#”(“#”是为分析方便而引入的一个特殊符号,把它作为输入符号串的结束符)。分析表矩阵元素 M[A,a]表示当前非终结符A,面临输入符号a时按存放关于符A的产生式展开,如表中元素为空白时表示出错。 (2)分析栈 用于存放分析过程中的文法符号。分析栈初始化时,在栈底压入一个“#”,然后再压入文法的开始符号,表示试图将开始符号推导出输入符号串。
(3)总控程序 总控程序的功能是依据分析表、分析栈和输入符号串的状态进行分析和识别。它在任何时候都是根据当前分析栈的栈顶文法符号X和当前的输入符号a来决定采取相应的动作,从而达到语法分析的目的。 总控程序的具体算法如下: 分析开始时进行有关的初始化工作:把“#”及文法的开始符号S依次压入分析栈,并把各指示器的指针调整至起始位置。 设分析到的某一步,分析栈当前的栈顶符号为X,输入串分析指针当前指向的符号为a,则: a.若X∈Vt ∪{#} ①X=a=“#”,表示分析成功,停止分析过程。 ②X=a≠“#”,则从栈顶弹出X,输入串指向下一个字符
③X≠a,则表示发现错误,调相应的出错处理程序进行处理。③X≠a,则表示发现错误,调相应的出错处理程序进行处理。 b.若X∈Vn:则根据X与a查分析表M,根据M[X,a]中的内容决定: ①若M[X,a]中为一个规则,则将X从分析栈中弹出,并将此规则右部的符号序列按倒序压入分析栈(若规则为X→ε,则仅将X从栈中弹出)。 ②若M[X,a]中为空白,则表示出错,调用相应的出错处理程序处理。
例:E→TE’ E’→+TE’|ε T→FT’ T’→*FT’|ε F→(E)|i 的分析表如下:
步骤 栈 剩余输入串 输出 0 #E i*(i+i)+i# E→TE’ 1 #E’T i*(i+i)+i# T→FT’ 2 #E’T’F i*(i+i)+i# F→i 3 #E’T’i i*(i+i)+i# i匹配 4 #E’T’ *(i+i)+i# T’ →*F T’ 5 #E’T’F* *(i+i)+i# *匹配 6 #E’T’F (i+i)+i# F→(E) 7 #E’T’)E( (i+i)+i# (匹配 8 #E’T’)Ei+i)+i# E→TE’ 9 #E’T’)E’Ti+i)+i# T→FT’
10 #E’T’)E’T’Fi+i)+i# F→i 11 #E’T’)E’T’ii+i)+i# i匹配 12 #E’T’)E’T’ +i)+i# T’→ε 13 #E’T’)E’ +i)+i# E’→+TE’ 14 #E’T’)E’T+ +i)+i# +匹配 15 #E’T’)E’T i)+i# T→FT’ 16 #E’T’)E’T’F i)+i# F→i 17 #E’T’)E’T’i i)+i# i 匹配 18 #E’T’)E’T’ )+i# T’→ε 19 #E’T’)E’ )+i# E’→ε
20 #E’T’) )+i# )匹配 21 #E’T’ +i# T’→ε 22 #E’ +i# E’→ +TE’ 23 #E’T+ +i# +匹配 24 #E’T i# T→FT’ 25 # E’T’ F i# F→i 26 # E’T’ i i# i 匹配 27 # E’T’ # T’→ε 28 # E’ # E’→ε 29 # # 接受
2.预测分析表的构造 由于所有的LL(1)分析器的输入带、栈和总控程序都是一样的,唯一区别的是分析表。下面介绍分析表的构造算法。 定义4-1: FIRST(α)={a|α=+=>a...,a∈Vt} 特别地,若α=*=>ε,则规定ε∈FIRST(α) 如果非终结符A的所有选择的首符集两两不相交,即A的任何两个不同的选择αi和αj有FIRST(αi)∩ FIRST(αj)=Φ时,要求A匹配输入串时,A就能根据它所面临的第一个输入符号a,准确地指派某一个选择前支执行任务,这个选择就是那个终结首符属于的α。如果ε∈FIRST(α),会使问题稍为复杂。由于某个非终结符A如要推导成ε,那么当输入符号a不是A中的符号而是A后面的符号,为此定义FOLLOW集合。 定义4-2 FOLLOW(A)={a|S=*=>...Aa...,a∈Vt} 若S=*=>...A,则规定#∈FOLLOW(A)
如果,A的任何两个不同的选择αi和αj不同时能推导出ε,不失一般性设αi=*=>ε。也能根据其FIRST(αi)和FIRST(α)∪FOLLOW(A)决定选择那一个产生式。 定义4-3 对于产生式A→α,若α=*=>ε, 则SELECT(A→α)=(FIRST(α)-{ ε})∪FOLLOW(A),否则SELECT(A→α)= FIRST(α) 定理 一个上下文无关文法是LL(1)文法的充分必要条件是,对每个非终结符A的任何两个不同产生式,A→αi和A→αj,满足 SELECT(A→αi)∩SELECT(A→αj)=Φ 显然αi和αj不同时为ε
LL(1)文法的性质: (1)任何二义性文法都不是LL(1)文法,任何LL(1)文法都不是二义性的 (2)若一个文法中含有左公因子或左递归,则该文法必然不是LL(1)文法,但不含左公因子和左递归的文法不一定是 LL(1)文法。 另外,存在算法能判定一个文法是否是LL(1)文法;也存在能判定二个LL(1)文法是否能产生相同的语言;但不存在能判定一个上下文无关的文法是否能变换成等价的LL(1)文法。
FIRST集合构造算法: 对于每一个文法符号X∈Vt∪Vn,构造FIRST(X),可重复应用下列步骤直到再无终结符号或ε能被添入到任何FIRST集合中为止。 (1)若X∈Vt,则FIRST(X)={X} (2)若X∈Vn,且有产生式X→a...则把a加入到FIRST(X)中;若X→ε也是一条产生式,则把ε也加到FIRST(X)中 若X→Y...是一个产生式且Y∈Vn,则把FIRST(Y)中的所有非ε元素都加到FIRST(X)中,若X→Y1Y2Y3...Yk是一个产生式,Y1Y2Y3...Yi-1都 是非终结符,而且,对于任何j,1≤j≤i-1, FIRST(Yi)都含有ε(即Y1Y2Y3...Yi-1=*=>ε),则把FIRST(Yi)中的所有FIRST(Yi)中的所有非ε元素加到FIRST(X)中去;特别是,若所有的FIRST(Yi)均有ε,j=1,2,3...,k,则把ε加到FIRST(X)。
符号串X1X2X3...Xn的FIRST集合的算法: 把FIRST(X1)的非ε元素加到FIRST(X1X2X3...Xn)中,如果ε在FIRST(X1)中,那么加入FIRST(X2)中全部非ε元素;如果FIRST(X1)和FIRST(X2)中都有ε则把FIRST(X3)中全部非ε元素加到FIRST(X1X2X3...Xn)中,以此类推,最后对所有的i,有ε属于FIRST(Xi),那么把ε加入FIRST(X1X2X3...Xn)中。
对非终结的FOLLOW算法: 对于文法G的每个非终结符A构造FOLLOW(A)的办法是重复应用下列步骤直到再无终结符号或#能被添入到任何FOLLOW(A)中: (1)对于文法的开始符号(识别符号)S,置#于 FOLLOW(A)中; (2)若A→αBβ是一个产生式,则把FIRST(β)中的非ε的一切符号加至FOLLOW(B)中; (3)若A→αB是一个产生式,或A→αBβ是一个产生式,而β=*=>ε(即ε∈FIRST(β)),则把FOLLOW(A)加入FOLLOW(B)中
SELECT构造算法: 对于每个产生式A→α构造SELECT(A→α),当ε∈FIRST(α),则SELECT(A→α)=(FIRST(α)-{ε})∪FOLLOW(A),否则SELECT(A→α)= FIRST(α) 例:求文法G[S]:S→AB S→bC A→ε A→b B→ε B→aD C→AD C→b D→aS D→c的FIRST、FOLLOW和SELECT集。 FIRST(S)={a,b,ε} FIRST(A)={b,ε} FIRST(B)={a,ε} FIRST(C)={a,b,c} FIRST(D)={a,c}
FIRST(AB)={a,b,ε} FIRST(bC)={b} FIRST(ε)={ε} FIRST(b)={b} FIRST(aD)={a} FIRST(b)={b} FIRST(aS)={a} FIRST(c)={c} FOLLOW(S)={#} FOLLOW(A)={a,#,c} FOLLOW(B)={#} FOLLOW(C)={#} FOLLOW(D)={#}
SELECT(S→AB)={a,b,#} SELECT(S→bC)={b} SELECT(A→ε)={a,c,#} SELECT(A→b)={b} SELECT(B→ε)={#} SELECT(B→aD)={a} SELECT(C→AD)={a,b,c} SELECT(C→b)={b} SELECT(D→aS)={a} SELECT(D→c)={c}
例:构造文法G[E]: E→TE’ E’→+TE’|ε T→FT’ T’→*FT’|ε F→(E)|i 的FIRST、FOLLOW、SELECT集 解:FIRST(E)={(,i} FIRST(E’)={+,ε} FIRST(T)={(,i} FIRST(T’)={*,ε} FIRST(F)= {(,i} FOLLOW(E)={#,)} FOLLOW(E’)={#,)} FOLLOW(T)={#,),+} FOLLOW(T’)={#,),+} FOLLOW(F)={#,),+,*}
SELECT(E→TE’)={(,i} SELECT(E’→+TE’)={+} SELECT(E’→ε)= {#,)} SELECT(T→FT’)={(,i} SELECT(T’→*FT’)={*} SELECT(T’→ε)= {#,),+} SELECT(F→(E))= {(} SELECT(F→i)={i}
构造预测分析表构造算法: (1)先求出每个产生式的SELECT集 (2)对于每个终结符或#用a表示。 若a∈SELECT(A→α),则把A→α放入M[A,a]中。 (3)把所有无定义的M[A,a]标记为出错,用空白表示。 例:试构造上述文法的预测分析表。 解:先求FIRST、FOLLOW和SELECT集(略)
二义性文法的应用 虽然任何二义性文法都不是LL(1)文法,但具体应用中,可根据实际情况删减,也是能达到分析目的的。 例:G[S]:S→if E then S S’|a S’→else S| E→b 解: FIRST(S)={if} FIRST(S’)={else,ε} FIRST(E)={b} FOLLOW(S)={else,#} FOLLOW(S’)={else,#} FOLLOW(E)={then} SELECT(S→if E then S S’)={if} SELECT(S→a)={a} SELECT(S’→else S)={else} SELECT(S’→ε)={else,#} SELECT(E→b)={b} 则分析表为
可以根据实际情况在M[s’,else]中删除S’→ε即可。可以根据实际情况在M[s’,else]中删除S’→ε即可。
4.2 自底向上的语法分析 所谓自底向上的分析方法就是从输入符号出发逐步归约成识别符号. 4.2.1自底向上的分析法概述 自底向上的分析法的思想方法主要是从左到右扫描输入符号串,从语法树的角度出发也就是从末端结点出发向根结点方向往上构造语法树,也就是说自底向上的分析方法是一个不断归约的过程。要实现自底向上的分析方法需要解决两个问题: (1) 确定句型中将要归约的符号串 (2) 如果该子串能归约多个非终结符号,需决定归约成那一个符号 由于栈在计算机中实现比较方便,在语法分析中往往使用栈,为更好地识别出输入串的结束,也引进了特殊符号#,把它压入栈底以及放在输入符号后面,如: 栈 输入 # a1a2a3a4...an#
这样可以采用“移入—归约”法,这种方法每移入一个字符判断是否可以归约,若能归约则归约到不能归约为止,否则再移入下一个字符。由于在“移入—归约”中每次归约的是最左边的非终结符,应该就是最左归约即最右推导的逆过程,那么在没有二义性的前提下其最左归约是唯一的,也就是句柄是唯一的。如果在分析过程式中发现错误(找不到貌似句柄的符号串)或者呈下列情形结束 栈 输入 #S # 其中S是识别符号。 例:设G[S]:S→aAcBe A→b A→Ab B→d 识别符号串abbcde
1. 简单优先分析法概述 简单优先分析法是按照每个文法符号之间的优先关系确定句柄的,这样如何找到这种优先关系也就是这种识别方法的关键。 优先关系 定义4-4 定义优先关系的定义: X〧Y 表示X和Y的优先级相等 X·>Y 表示X优先级大于Y的优先级 X<·Y 表示X优先级小于Y的优先级 注意:〧 ·> <·与数学中的=,>,< 不同 定理: X〧Y 当且仅当G中存在产生式规则A→···XY··· X<·Y 当且仅当G中存在产生式规则A→···XB···,且B=+=>Y··· X·>Y 当且仅当G中存在产生式规则A→···BD···,且B=+=>···X D=+=>Y···