语法制导的三地址代码生成器.docx
《语法制导的三地址代码生成器.docx》由会员分享,可在线阅读,更多相关《语法制导的三地址代码生成器.docx(17页珍藏版)》请在冰豆网上搜索。
语法制导的三地址代码生成器
实验二:
语法制导的三地址代码生成器
一教学重点与实现的关键技术
1.1自顶向下(top—down)分析概述
自顶向下分析法包括:
递归子程序法和预测分析法(LL
(1))。
自顶向下就是从文法的开始符号出发,向下推导,推出句子。
这种方法是带“回溯”的。
其主旨是:
对任何输入串,试图用一切可能的办法,从文法开始符号(根)出发,自顶向下地为输入串建立一棵语法树。
或者说,为输入串寻找一个最左推导。
这种分析过程本质上是一种试探过程,是反复使用不同产生式谋求匹配输入串的过程。
例:
G为:
S→xAyA→**|*,输入串:
x**y
SxAy
x**y
在子树A通过试探匹配后,才进行下一个符号y的匹配。
实现这种自顶向下的带回溯试探法的一个简单途径是让每个非终结符对应一个递归子程序。
每个子程序可作为一个布尔过程。
一旦发现它的某个侯选与输入串相匹配,就用这个侯选去扩展语法树,并返回“真”值;否则,保持原来的语法树和IP值不变,并返回“假”值。
这种分析法有许多困难和缺点。
首先,是文法的左递归问题。
其次,回溯会碰到一大堆麻烦事情。
第三,在上述的自顶向下分析过程中,当一个非终结符用某一候选匹配成功时,这种成功可能是暂时的。
第四,当最终报告分析不成功时,难于知道输入串中出错的确切位置。
1.2LL
(1)分析法技术
自顶向下分析方法不允许文法含有任何左递归。
为构造不带回溯的自顶向下分析算法,首先要消除文法的左递归性,并找出无回溯的充分必要条件。
LL
(1)分析法主要用于消除左递归和克服回溯的方法。
1.2.1左递归的消除
直接左递归AAα
间接左递归A+Aα
左递归的消除方法:
将A→Aα|β替换为A→βA′和A′→αA′|ε
例:
表达式文法直接左递归的消除
例:
间接左递归的消除
S→Ac|cA→Bb|bB→Sa|a
将B的定义代入A产生式得:
A→Sab|ab|b
将A的定义代入S产生式得:
S→Sabc|abc|bc|c
消除直接左递归:
S→abcS’|bcS’|cS’
S’→abcS’|ε
删除“多余的”产生式:
A→Sab|ab|b和B→Sa|a
结果:
S→abcS’|bcS’|cS’
S’→abcS’|ε
消除左递归的一般方法:
用产生式组
Aβ1B|β2B|…|βnB
Bα1B|α2B|…|αnB|ε
替换产生式组
A→Aα1|Aα2|…|Aαn|β1|β2|…|βm
其中:
B为新变量,相当于A’
消除左递归的算法:
Ⅰ、把文法G的所有非终结符按任一种顺序排列成A1,A2,……,An;按此顺序执行;
Ⅱ、FORi:
=1tonDO
Begin
FORj:
=1toi-1DO
把形如AiAjγ的规则改写成
Aiδ1γ│δ2γ│……│δkγ。
其中Ajδ1│δ2│……│δk是关于Pj的所有规则;
消除关于Pi规则的直接左递归性
END
Ⅲ、化简由(Ⅱ)所得的文法。
即去除那些从开始符号出发永远无法到达的非终结符的产生规则,为非终结符编号,再采用代入法将间接左递归变为直接左递归,消除直接左递归。
1.2.2消除回溯、提左因子
欲构造行之有效的自顶向下的分析器,必须消除回溯。
为了消除回溯就必须保证:
对文法的任何非终结符,当要用它去匹配输入串时,能够根据它所面临的输入符号准确地指派它的一个侯选去执行任务,并且此侯选的工作结果应是确信无疑的。
也就是说,若此侯选获得成功匹配,那么,这种匹配决不会是虚假的;若此侯选无法完成匹配任务,则任何其它侯选也肯定无法完成。
但是,如何把一个文法改造成任何非终结符的所有侯选首符集两两不相交呢?
其办法是,提取公共左因子。
例:
if语句的原始文法
S→ifEthenS
|ifEthenSelseS
|other
遇到if时难以判断用哪一个产生式进行匹配(推导)
存在左因子ifEthenS
提取作因子:
S→ifEthenSS’|other
S'→ε|elseS
左因子提取方法:
将形如A→αβ1|αβ2|…|αβn|γ1|γ2|…|γm
的规则改写为A→αA'|γ1|γ2|…|γm和A'→β1|β2|…|βn
1.2.3候选式的确定与回溯(Backtracking)
当要进行某个语法变量的推导时,希望能够根据当前符号确定候选式。
如果有几个候选式(右部)左端第一个符号相同,则分析器无法根据当前输入符号选择产生式,只能试探。
因此,希望寻找一类文法,我们可以方便地根据当前输入符合确定正确的候选式。
为了描述方便,我们定义文法符号的FIRST(首符号集)和非终结符的FOLLOW(后续符号集):
FIRST集:
对于α∈(VT∪VN)*定义:
α的首符号集
FIRST(α)={a|α*a…,a∈VT*}
FOLLOW集:
对于A∈VN定义A的后续符号集:
FOLLOW(A)={a|S*…Aa…,a∈VT}
求FIRST(X)的算法:
1)对x∈VT,FIRST(x)={x};
2)对X∈VN,取FIRST(X)的初值:
{a|X→a…∈P};X→εP
FIRST(X)=
{a|X→a…∈P}∪{ε};X→ε∈P
3)对X∈VN,重复如下过程,直到所有FIRST集不变
若X→Y…∈P,且Y∈VN,
则FIRST(X)=FIRST(X)∪(FIRST(Y)-{ε});
若X→Y1…Yn∈P,且Y1...Yi-1*ε,
则对k=1到i-1:
FIRST(X)=FIRST(X)∪(FIRST(Yk)-{ε});
若Y1...Yn*ε,
则FIRST(X)=FIRST(X)∪{ε}
求FOLLOW(A)的算法:
对于所有非终结符,重复进行以下计算
1)将#加入到FOLLOW(S)
#为句子的结束符
2)若A→αBβ,
则FOLLOW(B)=FOLLOW(B)∪FIRST(β)–{ε}
3)如果A→αB或A→αBβ,且β*ε,A≠B,
则FOLLOW(B)=FOLLOW(B)∪FOLLOW(A)
1.2.4LL
(1)分析条件
对G的任意变量A,A→α1|α2|…|αn是所有A产生式,它们满足下列条件,则称G是LL
(1)文法:
Ⅰ、FIRST(αi)∩FIRST(αj)=Φi≠j
Ⅱ、且当ε∈FIRST(αj)时,FOLLOW(A)∩FIRST(αi)=Φ
LL
(1)文法是自顶向下方法能够处理的一类文法:
第一个L表示从左向右扫描输入符号串;第二个L表示生成最左推导;1表示读入一个符号可确定下一步推导。
例:
表达式文法是LL
(1)文法
E→TE'
E'→+TE'|ε
T→FT'
T'→*FT'|ε
F→(E)|id
考察
E':
+不在FOLLOW(E')={),#}
T':
*不在FOLLOW(T')={+,),#}
F:
(和id不同
例:
非LL
(1)文法的不确定性
对文法
S→cAdA→ab|a
输入cad的分析
不确定性的解决方法:
1)采用回溯算法
2)将非LL
(1)文法改写为等价的LL
(1)文法
3)无法改写时,增加其它的判别因素
1.3递归子程序法
当一个文法满足LL
(1)条件时,我们就可以为它构造一个不带回溯的自顶向下分析器(又称递归下降分析器),这个分析器是由一组递归过程组成的,每个过程对应文法的一个非终结符。
这种分析方法称为递归子程序法。
具体做法为:
对应每个语法变量设置一个处理子程序:
即对于每条产生式规则:
A→X1X2…Xk…Xn
当遇到Xk是终极符号时直接进行匹配
当遇到Xk是语法变量时就调用Xk对应的处理子程序
要求处理子程序是可以递归调用的
例如:
简单算术表达式的分析器
E的子程序(E→T(+T)*)
procedureE;
begin
T;T的过程调用
whilelookhead='+'do
begin当前符号等于+时
match(‘+’);处理终结符+
TT的过程调用
end
end;lookhead:
当前符号
实现递归子程序的一个有效工具是状态转换图。
语法分析器和词法分析器的状态转换图不同,每个非终结符对应一个状态转换图,边上的标记是记号和非终结符。
记号上的转换意味着如果该记号是下一个输入符号,就应进行转换。
非终结符A上的转换是对与A对应的过程的调用,从文法构造语法图,对每个非终结符A执行如下操作:
创建一个开始状态和一个终止状态(返回状态)。
对每个产生式A→X1X2 …Xn,创建一条从开始状态到终止状态的路径,边上的标记分别为X1,X2,…,Xn。
开始,分析器进入状态图的开始状态,输入指针指向输入符号串的第一个符号。
如果经过一些动作后,它进入状态s,且从状态s到状态t的边上标记了终结符a,此时下一个输入符又正好是a,则分析器将输入指针向右移动一位,并进入状态t。
另一方面,如果边上标记的是非终结符A,则分析器进入A的初始状态,但不移动输入指针。
一旦到达A的终态,则立刻进入状态t,事实上,分析器从状态s转移到状态t时,它已经从输入符号串“读”了A(调用A对应的过程)。
最后,如果从s到t有一条标记为ε的边,那么分析器从状态s直接进入状态t而不移动输入指针。
递归子程序法小结:
1)构造文法
2)改造文法:
消除二义性和左递归、提取左因子
3)求每个候选式的FIRST集和每个变量的FOLLOW集
4)检查是不是LL
(1)文法,不是LL
(1),说明文法的复杂性超过自顶向下方法的分析能力,需要附加新的“信息”
5)按照LL
(1)文法画语法图
6)化简语法图
7)按照简化后的语法图,为每个非终结符设置一个分析子程序。
事实上,如果有一个恰当的文法,可以直接根据每个语法变量的产生式设计相应的程序。
递归子程序法优点:
1)直观、简单、可读性好
2)便于扩充
递归子程序法缺点:
1)递归算法的实现效率低
2)处理能力相对有限
3)通用性差,难以自动生成
实验二的语法分析部分建议学生采用递归子程序法实现。
当第二个实验被分解为两个实验时,则先采用递归子程序法实现一个自顶向下的语法分析器。
1.4预测分析法
实现LL
(1)分析的另一种有效方法是预测分析法。
一个预测分析器的组成为:
一个通用的控制算法;一个分析栈,#为栈底符号;一个输入缓冲区,#为输入串结束符;一个统一形式的分析表M;(不同语言使用内容不同的分析表)。
主要通过一张分析表和一个分析栈进行联合控制。
其模型如下:
分析方式:
输入指针指向输入串的第一个字符,分析栈中存放栈底符号#和文法的开始符号S。
根据栈顶符号A和读入的符号a,查看分析表M,以决定相应的动作,其关键是分析表的构造。
预测分析表的构造算法:
1)对于每一产生式A→α,执行2)和3);
2)对于FIRST(α)中的每一终结符a,将A→α填入M[A,a];
3)如果ε属于FIRST(α),则对FOLLOW(A)中的每个符号b,将A→α填入M[A,b];若ε属于FIRST(α),且#在FOLLOW(A),则将A→α填入M[A,#];
4)将所有无定义的M[A,b]标上错误标志
预测分析法的步骤:
1)构造文法。
2)改造文法:
消除二义性、消除左递归、提取左因子。
3)求每个候选式的FIRST集和变量的FOLLOW集。
4)检查是不是LL
(1)文法,若不是LL
(1),说明文法的复杂性超过自顶向下方法的分析能力,需要附加新的“信息”。
5)构造预测分析表。
6)实现预测分析器。
出错处理问题:
对语法变量A,如果M[A,a]无定义,并且a属于FOLLOW(A),则增加M[A,a]为“同步点”(synch)。
预测分析法的优点:
1)效率高
2)便于维护、自动生成
由于我们建议学生采用递归子程序法自己手工地实现一个自顶向下的语法分析器,关于预测分析器的设计实现细节就不在这里赘述。
1.5属性文法和语法制导翻译
目前实际应用中比较流行的语义描述和语义处理的方法主要还是属性文法和语法制导翻译方法。
属性文法是在上下文无关文法的基础上,为每个文法符号(终结符或非终结符)配备若干相关的“值”(称为属性),是Knuth在1968年提出的。
这些属性代表与文法符号相关信息,例如它的类型、值、代码序列、符号表内容等等。
属性与变量一样,可以进行计算和传递。
属性文法的特点:
(1)是一种接近形式化的语义描述方法
(2)长于描述静态语义、短于描述动态语义
(3)每个语法符号有相应的属性符号
(4)每个产生式有相应的计算属性的规则
(5)属性变量:
=属性表达式
举例:
产生式属性(计算)规则/语义规则
E→E1+E2E.val:
=E1.val+E2.val
E→E1*E2E.val:
=E1.val*E2.val
E→(E1)E.val:
=E1.val
E→idE.val:
=id.val
属性文法的定义:
三元组:
A=(G,V,F)
G是上下文无关文法
V属性的有穷集
F关于属性的计算规则
属性及其计算规则:
语义信息作为终结符和非终结符的属性。
语义分析为产生式相关的属性计算:
每个产生式设置语义规则,描述各属性的关系——计算规则。
属性的设定:
根据文法符号的语义,为文法符号设置属性,用来表示文法符号的语义。
终结符使用单词的属性(id.val)
保留字:
if,begin,function,……
常数:
40.12,232,80,“TCP/IP”
标识符:
sum,tcc,id
语法变量根据实际需要设定属性
表达式E:
E.type,E.val
例如:
计算器的属性文法要完成一个输入表达式值的计算和显示——翻译
用文法描述要完成的动作:
L→E
E→E1+T|T
T→T1*F|F
F→(E)|digit
如何描述翻译过程?
请看如下属性文法:
L→Eprint(E.val)(L的虚属性)
E→E1+TE.val:
=E1.val+T.val
E→TE.val:
=T.val
T→T1*FT.val:
=T1.val*F.val
T→FT.val:
=F.val
F→(E)F.val:
=E.val
F→digitF.val:
=digit.lexval
其中,lexval是单词digit的属性
下面简述一下属性的分类
继承属性
设A→X1X2…Xn为一个产生式,Xi的属性
Xi.in=f(c,c1,c2,…,ci-1)
c,c1,c2,…,ci-1是A,X1,X2,…,Xi-1的属性叫做继承(Inherited)属性
综合属性
设A→X1X2…Xn为一个产生式
A.s=f(c1,c2,…,ck)
c1,c2,…,ck是X1,X2,…,Xn的属性和A的继承属性
A.s是从其子结点的属性值计算出来的这种属性叫做综合(Synthesized)属性。
固有属性
语言中的标识符、常数(数值的、符号的)、常量,它们的属性是用户给定的、不变的。
T→intT.type:
=integer
固有(Inherent)属性(单词属性),归类于综合属性。
属性的计算:
综合属性是自底向上按照语义规则来计算各结点的综合属性值——在归约时进行计算
继承属性是根据依赖关系决定计算顺序的任意一个拓扑排序(TopologicalSort)。
固有属性在词法分析时计算。
例:
3*5+4的分析树与属性计算:
语法制导翻译
属性加工的过程即是语义处理的过程。
对于文法的每个产生式都配备了一组属性的计算规则,称为语义规则。
语法制导翻译是在进行语法分析的同时,完成相应的语义处理。
例如:
E→E1+E2E.val:
=E1.val+E2.val
如何根据被识别出的语法成分进行语义处理?
语义——可以看成是相应文法符号的属性。
(1)对应每一个产生式编制一个语义子程序,当一个产生式获得匹配时,调用相应的语义子程序实现语义检查与翻译。
例如:
E→E1+TE.val:
=E1.val+T.val
T→T1*FT.val:
=T1.val*F.val
F→idF.val:
=id.val
以上翻译模式适应在完成归约的时候进行。
(2)在产生式的右部的适当位置,插入相应的语义动作,按照分析的进程,执行遇到的语义动作
D→T{L.in:
=T.type}L
T→int{T.type:
=integer}
T→real{T.type:
=real}
L→{L1.in:
=L.in}L1,id{…}
以上翻译模式适应在进行推导时完成对应语法基本分析方法(Top-down、Bottom-up)
1.6实现的关键技术提示
对于“语法制导的三地址代码生成器的编制”实验而言,除了能正确地画出语法图并化简,求出FIRST,FOLLW集外,在实现过程中还要想方设法保证输出的产生式规则(三地址代码)严格按照自顶向下、从左至右的顺序进行,为了达到这一要求,需要在每次调用与某个语法变量相应的处理程序时,一进入过程就输出该规则(生成相应的三地址代码),而在有些情况下,为了正确地选择匹配的规则(生成相应的三地址代码)必须连续向前看若干的符号(token),这时应想到如何把分析过的符号暂时保存起来(事实上栈是解决这类问题的良好选择)。
这些难点的最终解决,正体现了计算机学科的魅力所在,它要求每个从事这项工作的人“既要想得对,也要做得到”。
二具体实验要求
2.1实验目的
掌握计算机语言的语法分析器设计与属性文法应用的实现方法。
2.3实验内容
编制一个能够进行语法分析并生成三地址代码的微型编译程序。
2.4实验要求
1考虑下述语法制导定义中文法,采用递归子程序法,改写文法,构造语法分析器;
2考虑下述语法制导定义中语义规则,改写语法分析器,构造三地址代码生成器。
产生式
语义规则
S→id=E
S.code=E.code||gen(id.place’:
=’E.place)
S→ifCthenS1
C.true=newlabel;C.false=S.next;
S1.next=S.next;
S.code=C.code||gen(E.true’:
’)||S1.code
S→ifCthenS1elseS2
C.true=newlabel;C.false=newlabel;
S1.next=S2.next=S.next;
S.code=C.code||gen(E.true’:
’)||S1.code
||gen(‘goto’,S.next)||gen(E.false’:
’)||S2.code
S→whileCdoS1
S.begin=newlabel;C.true=newlabel;
C.false=S.next;S1.next=S.begin;
S.code=gen(S.begin’:
’)||C.code||
gen(E.true’:
’)||S1.code||gen(‘goto’S.begin);
C→E1>E2
C.code=E1.code||E2.code||
gen(‘if’E1.place’>’E2.place’goto’C.true)||
gen(‘goto’C.false)
C→E1C.code=E1.code||E2.code||
gen(‘if’E1.place’<’E2.place’goto’C.true)||
gen(‘goto’C.false)
C→E1=E2
C.code=E1.code||E2.code||
gen(‘if’E1.place’=’E2.place’goto’C.true)||
gen(‘goto’C.false)
E→E1+T
E.place=newtemp;
E.code=E1.code||T.code||
gen(E.place’:
=’E1.place’+’T.place)
E→E1-T
E.place=newtemp;E.code=E1.code||T.code||
gen(E.place’:
=’E1.place’-’T.place)
E→T
E.place=T.place;E.code=T.code
T→F
T.place=F.place;T.code=F.code
T→T1*F
T.place=newtemp;
T.code=T1.code||F.code||
gen(T.place’:
=’T1.place’*’F.place)
T→T1/F
T.place=newtemp;T.code=T1.code||F.code||
gen(T.place’:
=’T1.place’/’F.place)
F→(E)
F.place=E.place;F.code=E.code
F→id
F.place=id.name;F.code=‘‘
F→int8
F.place=int8.value;F.code=‘‘
F→int10
F.place=int10.value;F.code=‘‘
F→int16
F.place=int16.value;F.code=‘‘
2.5实验环境
★PC微机
★DOS操作系统或Windows操作系统
★TurboC程序集成环境或VisualC++程序集成环境
2.6实验步骤
1考虑给定的文法,消除左递归,提取左因子。
2编制并化简语法图
3编制递归子程序的算法
4编制各个递归子程序函数
5连接实验一的词法分析函数scan(),进行测试
6设计三地址代码生成的数据结构和算法
7将各个递归子程序函数改写为代码生成函数
8编制测试程序(main函数)
9调试程序:
输入一个语句,检查输出的三地址代码
2.7基本测试数据
输入数据例:
while(a3+15)>0xadoifx2=07thenwhiley正确结果:
等效的三地址代码序列
L1:
t1:
=a3+15
ift1>10gotoL2
gotoL0
L2:
ifx2=7gotoL3
gotoL1
L