第六章 属性文法和语法制导翻译
这章介绍语义分析及翻译的问题。
一,属性文法
属性文法是在上下文无关文法的基础上为每个文法符号(终结符或非终结符)配备若干个相关的“值”(称为属性)。
属性:代表与文法符号相关的信息,和变量一样,可以进行计算和传递。例:类型、值、代码序列、符号表内容等
属性计算的过程即是语义处理的过程,对于文法的每一个产生式配备一组属性的计算规则,则称为语义规则。
1,属性分为综合属性和继承属性。
综合属性:用于“自下而上”传递信息;在语法树中,一个结点的综合属性的值,由其子结点的属性值确定;
S—属性文法:仅仅使用综合属性的属性文法。
继承属性:用于“自上而下”传递信息;在语法树中,一个结点的继承属性由此结点的父结点和/或兄弟结点的某些属性确定。
,2,语义规则:在一个属性文法中,对应于每个产生式A->α都有一套与之相关联的语义规则,每条语义规则的形式为:
b:=f(c1,c2,…,ck)
这里f是一个函数,而且或者
(1)b是A的一个综合属性并且c1,c2,…ck是产生式右边文法符号的属性;或者
(2)b是产生式右边某个文法符号的一个继承属性并且c1,c2,…ck是A或产生式右边任何文法符号的属性
在这两种情况下,我们都说属性b依赖于属性c1,c2,…,ck.
注:(1)终结符只有综合属性,它由词法分析器提供
(2)非终结符既可以有综合属性也可以有继承属性,文法开始符号的所有继承属性作为属性计算前的初始值。
(3) 产生式右边符号的继承属性和产生式左边符号的综合属性都必须提供一个计算规则
(4) 产生式左边符号的继承属性和产生式右边符号的综合属性不由所给的产生式的属性计算规则进行计算,它们由其它产生式的属性规则计算
举例: 考虑非终结符A,B和C,其中,
A有一个继承属性a和一个综合属性e
B有综合属性b
C有继承属性c
产生式 A → BC 应该有计算c和e的规则
C.c := B.b + 1
A.e := A.a + B.b
其中属性A.a和B.b在其他地方计算。
解释:根据 产生式右边符号的继承属性和产生式左边符号的综合属性都必须提供一个计算规则,而A.e正是产生式左边符号的综合属性,C.c正是产生式右边符号的继承属性。而A.a和B.b是产生式左边符号的继承属性和右边符号的综合属性。
综合属性例:
考虑如下属性文法,它用作台式计算器程序,对每个 非终结符E、T和F都有一个综合属性——val,是一个整数值。
每个产生式左边的综合属性val都是由右边的计算出来的。
对此3*5+4的计算过程:
解释:首先考虑最低最左边的内部节点,对应产生式F->digit,相应的语义规则是F.val=digit.lexval,因为digit.lexval=3,所以F1.val=digit.lexval=3,依次递推直到求出表达式的值
继承属性例:
说明语句的文法
D → T L
T → int
T → real
L → L1,id
L → id
type为类型描述T的综合属性
in为变量表L的继承属性
解释:为了确定id1,id2,id3的类型,先求出根的左子节点的属性T.type,然后每项向下计算根的右子树三个L节点的属性值L.in,按红箭头方向计算。和综合属性的区别就是它这是自上而下的。
二,基于属性文法的处理方法
过程:对单词符号串进行语法分析,构造语法分析树,然后根据需要遍历语法树并在语法树的各节点处按语义规则计算。
这种由源程序的语法结构所驱动的处理办法就是语法制导翻译法。
如果在一棵语法树中一个结点的属性b依赖于属性c,那么这个结点处计算b的属性规则必须在确定c的语义规则之后使用;
在一颗语法树中的结点的继承属性和综合属性之间的相互依赖关系可以用称作依赖图的一个有向图来描述。
1,依赖图:在为一棵语法树构造依赖图以前,我们为每一个包含过程调用的语义规则引入一个虚综合属性b,这样把每一个语义规则都写成
b:= f(c1,c2, …ck)
依赖图中为每一个属性设置一个结点,如果属性b依赖属性c,则从属性c的结点有一条有向边连到属性b的结点。
例:产生式 AXY的语义规则
(1)A.a:=f(X.x, Y.y)
(2)X.i:=g(A.a,Y.y)
如果一属性文法不存在属性之间的循环依赖关系,那么该文法为良定义的。为了设计编译程序,我们只处理良定义的属性文法。
循环依赖:
例如p,c1,c2都是属性,若有如下求值规则
p:=f1(c1)、c1:=f2(c2)、c2=f3(p),
就无法对p求值.
属性的计算次序
一个有向非循环图的拓扑序是图中结点的任何顺序m1,m2, …mk,使得边必须是从序列中前面的结点指向后面的结点。也就是说,如果mimj是mi到mj的一条边,那么在序列中mi必须出现在mj之前。
一个依赖图的任何拓扑排序都给出一个语法树中结点的语义规则计算的有效顺序。这就是说,在拓扑排序中,在一个结点上,语义规则b:=f(c1,c2,…ck)中的属性c1,c2…ck在计算b以前都是可用的。
2,抽象语法树
在抽象语法树中,操作符和关键字都不作为叶结点出现,而是把它们作为内部结点,即这些叶结点的父结点。
产生式S→if B then S1 else S2抽象语法树表示:
3*5+4的抽象语法树:
三,S-属性文法的自下而上计算
S-属性文法只含综合属性。
例:
输入 3 * 5 + 4n
该产生式中Xj左边符号X1, X2, …, Xj-1的属性;A的继承属性
例:变量类型声明的语法制导定义是一个L属性定义
T. type是综合属性
2,翻译模式
翻译模式是语法制导定义的一种便于翻译的书写形式。其中属性与文法符号相对应,语义规则或语义动作用花括号{ }括起来,可被插入到产生式右部的任何合适的位置上。
例: 一个简单的翻译模式
E→TR
R→addop T {print(addop.lexeme)}R1|ε
T→num{print(num.val)}
把带加号和减号的中缀式变成后缀式
把语义动作看成终结符号
设计翻译模式(根据语法制导定义)
条件:语法制导定义是L-属性定义
保证语义动作不会引用还没有计算的属性值。
(1)只需要综合属性的情况
(2)既有综合属性又有继承属性
(1)只需要综合属性的情况
为每一个语义规则建立一个包含赋值的动作,并把这个动作放在相应的产生式右边的末尾。
例如: 产生式 语义规则
T->T1*F T·val:=T1 ·val*F ·val
翻译模式
T->T1*F { T·val:=T1 ·val*F ·val}
(2)既有综合属性又有继承属性的情况
①产生式右边的符号的继承属性必须在这个符号以前的动作中计算出来。
②一个动作不能引用这个动作右边符号的综合属性。
③产生式左边非终结符号的综合属性只有在它所引用的所有属性都计算出来以后才能计算。计算这种属性的动作通常可放在产生式右端的未尾。
例如:下面的翻译模式不满足要求:
S->A1A2 {A1·in:=1; A2 ·in:=2}
A ->a { print(A ·in) }
in是继承属性
输入串aa的语法树
3,自顶向下翻译
1.从翻译模式中消除左递归
对于一个翻译模式,若采用自顶向下分析,必须消除左递归和提取左公因子,在改写基本文法时考虑属性值的计算。
2.
左递归翻译模式
A→A1Y{A.a:=g(A1.a,Y.y)}
A→X {A.a:=f(X.x)} (1式)
每一个文法符号都有一个综合属性,用相应的小写字母表示,g和f是任意函数。
消除左递归,文法转换成
A→X R
R→Y R|ε
第七章 语义分析和中间代码产生
1,语义分析的任务
审查每一个语法结构的静态语义,即验证语法正确的结构是否有意义。
如:赋值语句:x:=x+y,左边变量类型与右边变量类型是否一致。
在语义正确的基础上生成一种中间代码或目标代码。
自顶向下的语法制导翻译
二,中间代码产生
1、逆波兰表示法
波兰表示是一种既不须考虑优先关系、又不用括号的一种表示表达式的方法(前缀式)。
现在我们要介绍的刚好是另一种波兰表示形式,称为后缀式,即运算符在后。
例: a+b → ab+
a*(b+c) → abc+*
-a+b*c → a@bc*+
2、图表示法
抽象语法树:
无循环有向图(DAG):
已知抽象语法树求DAG图:合并公共结点
3,三元式
1.三元式由三个部分组成:
算符:OP
第一运算分量:ARG1
第二运算分量:ARG2
2.各种语句都可表示成一组三元式
例1: OP ARG1 ARG2
x+y*z (1) * y z
(2) + x (1)
在三元式的基础上附加一张指示器表─间接码表,按运算的先后顺序列出有关三元式在三元式表中的位置。这种表示方法称为间接三元式。
3.四元式
一个四元式是一个带有四个域的记录结构:op,arg1,arg2及result。它实际上就是一条三地址的指令。
例:A+B*(C-D)-E/F↑G的四元式为:
OP ARG1 ARG2 RESULT
① - C D T1
② * B T1 T2
③ + A T2 T3
④ ↑ F G T4
⑤ / E T4 T5
⑥ - T3 T5 T6
有时将四元式表示成更直观的形式-三地址代码
三地址代码形式:
x:=a op b (赋值形式)
与赋值语句的区别:其右边最多只能有一个运算符。
如:四元式(<,B,D, T1),三地址码T1:=B<D
三,常用语句的翻译
1,说明语句
为局部名字建立符号表条目
为它分配存储单元
符号表中包含名字的类型和分配给它的存储单元的相对地址等信息
1.简单说明句:用一个基本字来定义
一串名字,其语法描述一般为:
D→integer namelist∣real namelist
namelist→namelist,i∣i
2.数组说明
例如,array[l1:u1,l2:u2,…,ln:un]
di=ui-li+1所求地址:
D=CONSPART+VARPART
CONSPART=a-C
C=(…((l1d2+l2)d3+l3)d4+…)+ln-1)dn+ln
3,赋值语句的翻译
1)简单算术表达式的赋值语句:
不考虑数组元素、记录、函数的引用等情况。
假如所有分量都是相同类型的情况,如都是整型。可用如下的二义文法来进行描述:
S→id:=E
E→E1+E2∣E1*E2∣(E1)∣-E1∣id
2)类型转换
我们可以把类型信息反映到运算符中,例如用+i,*i表示定点+、*,用+r,*r表示浮点+、*。有的程序设计语言允许混合运算,有 的不允许。如果不允许,则发现有类型不相同的运算分量就应该报错。如果允许,就要进行类型转换。
整、实运算要全部转成实型:给每个非终结符添加一个类型信息,如我们用E.MODE表示E的类型,其值为r或者i。
例:利用翻译模式,给出赋值语句 a:=b*-c+b*-c 的三地址代码序列。
T1=-c
T2=b*T1
T3=-c
T4=b*T3
T5=T2+T4
a=T5
3)数组元素的赋值
(1) 数组元素的地址计算公式
若数组A的元素存放在一片连续单元里,则可以较容易的访问数组的每个元素。假定数组的每个元素的宽度为w,则一维 数组A[i] 这个元素的起始地址为:
base + (i – low)*w
其中,low为数组下标的下界, base是分配给数组的相对地址,即base为A的第一个元素A[low]的相对地址。
base + (i – low)*w 可整理为: i*w + (base –low*w)
其中:
(1) i*w 是随数组下标变量而变化的部分,记为VARPART ;
(2)(base – low*w)是在数组中不变化的常数记为CONSPART
( 2) 二维数组
若二维数组A按行存放,则可用如下公式计算A[i1,i2]的相对地址:
base + (( i1 – l1)*d2 + i2 – l2) *w
其中,l1、l2分别为i1、i2的下界;di界差。若ui为i的上界,则di=ui – li +1.
假定i1,i2是编译时唯一尚未知道的值,我们可以重写上述表达式为:( base –( (l1 *d2) + l2) *w)+ ( (i1*d2) + i2) *w
CONSPART VARPART
其中:前一项子表达式( base – ((l1 *d2) + l2) *w )的值是可以在编译时确定的记为常数CONSPART。
后一子项随i1, i2 而改变是一个变数记VARPA
4) 含数组元素的赋值语句的翻译
数组的翻译方案是:
产生两组计算数组元素地址的四元式:
一组是计算VARPART,并将计算结果放入临时变量T1,
另一组是计算CONSPART,并将计算结果放入临时变量T2,同时用T2[T1]表示数组元素的地址。
例: 令A是一个10*20的数组即 d1=10, d2=20,求:
(1) 赋值语句 X:= A[I, J ]四元式序列。
(2) 赋值语句A[ I+2 , J+1 ] := M+N四元式序列。
解:(1)
( * ,I, 20 , T1 )
(+ , J , T1 , T1 )
( - , A , 21 , T2 )
( =[ ] , T2[T1] , , T3)
( := , T3 , , X )
解:(2)
( + , I , 2 , T1 )
( + , J , 1 , T2 )
( * , T1, 20 , T3 )
( + , T2 , T3 , T3 )
( - , A, 21 , T4 )
( + , M, N , T5 )
( [ ]=, T5, , T4[T3] )
4,控制流语句的翻译
仿照算术表达式的翻译来进行。
例如 A∨B∧C=D可翻译成如下四元式序列:
(=,C,D,T1)
(∧,B,T1,T2)
(∨,A,T2,T3)
1) 条件语句中布尔表达式的翻译
出现在条件语句if E then S1 else S2
中的布尔表达式E,它的作用仅在于控制对S1或S2的选择,亦即提供“真”“假”出口,所以其值无需一直保留。
A∨B 可理解为 if A then true else B
A∧B 可理解为 if A then B else false
┐A 可理解为 if A then false else true
可以把作为控制条件的任何布尔表达式表示成仅含下列三种形式的四元式的序列:
(jnz,a,--,p) 表示 if a goto p
(jrop,x,y,p) 表示 if x rop y goto p
(j,--,--,p) 表示 goto p
例: if A∨B∧C=D then S1 else S2 的四元式:
1.(jnz,A,--,7)
2.(j,--,--,3)
3.(jnz,B,--,5)
4.(j,--,--,p+1)
5.(j=,C,D,7)
6.(j,--,--,p+1)
7.(S1的四元式序列
… … )
p.(j,--,--,q)
p+1.(S2的四元式序列
… … )
q. … …
2)标号和无条件转移的翻译
(1)对于说明性出现的标号,很容易处理:
L: S
当这种语句被处理之后,标号L被称为“定义了”的。也就是,在符号表中,标号L的“地址”栏将登记上语句S的第一个四元式的地址(编号)。
(2)对于先定义后应用的无条件转移(向后转移的goto L ),也很容易处理。对L查表得到它的定义地址p,就可生成goto L的四元式(j,--,--,p)。
(3)对于先应用后定义的情况(前向转移goto L ):
拉链返填:把所有以L为转移目标的四元式串在一起。链的首地址放在符号表中L的“地址”栏中。
回填:先产生暂时没有填写目标标号的转移指令,对于每一条这样的指令作适当的记录,一旦目标标号被确定下来,再将它“回填”到相应的指令中。
3)循环与分情况语句的翻译
(1)循环语句
大多数程序语言中都有如下形式的循环句:
S→for i:=E1 step E2 until E3 do S1
有条件循环语句和计数循环语句等:
例while a<b do
if c<d then
x:= y+z
else
x:=y-z
四元式: L1 : if a<b goto L2
goto Lnext
L2 : if c<d goto L3
goto L4
L3 : t1 := y+z
x:=t1
goto L1 (L5)
L4 : t2:=y-z
x:=t2
(L5) goto L1
Lnext:
以上是第六章和第七章的重点,虽然有很多所学内容的删减。这是编译过程中的重要环节,至此对整个编译过程从词法分析、语法分析到语义分析和中间代码产生有了整体认识,虽然没有优化,也完成了主要步骤。之后会有一篇总结。