中间代码生成(2)

2019-01-07 12:17

字符串、数字、类型、内存单元或其他对象。分析树节点上属性的值由该节点所用产生式的语义规则来定义。节点的综合属性是通过分析树中其子节点的属性值计算出来的;而继承属性值则是由该节点的兄弟节点和父节点的属性值计算出来的。 [2]语法制导定义的形式:

在语法制导定义中,每个产生式A --> a都有一个形如 b := f(c1,c2,?,ck)的语义规则集合与之相关联,其中f是函数,且满足下列两种情况之一:

i) b是A的一个综合属性,且c1,c2,?,ck是该产生式文法符号的属性

ii) b是产生式右部某个文法符号的一个继承属性,且c1,c2,?,ck也是该产生式文法

符号的属性。

对这两种情况都称为属性b依赖于属性c1,c2,?,ck。有时,语法制导定义中的某个规则的目的就是为了产生副作用,比如输出某个字符串或者操作某个结构等。这种语义规则一般被写成过程调用或程序段。在针对语法制导定义的翻译中,把副作用看作非终结符的一个虚综合属性值处理会比较方便。

为了便于理解,以下给出一个台式计算器程序的语法制导定义

表1 台式计算器程序的语法制导定义

产生式 L ? En E ? E1 + T E ? T T ? T1 * F T ? F F ? (E) F ? digit

该定义将一个整数值综合属性val与每个非终结符E,T和F联系起来。对每个以E,T和F为左部的产生式,语义规则从产生式右部非终结符的val值计算出产生式左部非终结符的val值。

记号digit具有综合属性lexval,其值由词法分析器提供,而产生式L?En所对应的语义规则只是打印E所产生的算术表达式的值的过程,我们可以认为该规则为非终结符L定义了一个虚属性。

3. 翻译模式

语法制导定义只是给出了属性值的计算标准,并没有给出属性值的计算顺序。实际上,在一个产生式中,右端非终结符的综合属性、继承属性、左端符号的继承属性的计算顺序是有要求的。否则,则会出现引用尚未得到的值的翻译错误。由语法制导定义可以得到某种特定的翻译模式,这个翻译模式不仅包含了语法制导定义的属性关系,还包括了计算它们的顺序。当得到一个具体的翻译模式后,编码将变得非常简单。

print(E.val) E.val := E1.val + T.val E.val := T.val T.val := T1.val * F.val T.val := F.val F.val := E.val F.val := digit.lexval 语义规则 [1]L属性定义

在介绍翻译模式之前,先介绍一种属性定义:L属性定义。它的特点是其属性总可以用深度优先顺序来计算。基于带回溯的递归下降分析法的语法分析器和L属性结合,能够对词法记号流进行自顶向下的一遍翻译。

L属性的规范化定义是:一个语法制导定义是L属性定义,如果对每个产生式A?X1X2?Xn,其右部符号Xj(1<= j <= n)的每个继承属性仅依赖于下列属性: i) 产生式中Xj左边的符号X1,X2,?,Xj-1的属性。 ii) A的继承属性。

只含有综合属性的语法制导定义也是L属性定义,因为L属性定义只规定了继承属性。 [2]翻译模式

翻译模式就是将文法符号同属性相关联的上下文无关文法,而且包含在{ }中的语义动作可以插入产生式右部的任何一个位置。本文所考虑的翻译模式可以同时具有综合属性和继承属性。

下面是一个简单的翻译模式,它把带有加号和减号的中缀表达式翻译成后缀表达式。

表2 一个简单的翻译模式

原文法定义 E ? T R R ? addop T R1 | ? E ? T R R ? addop T {print(addop.lexeme)} R1 | ? T ? num

下图是输入串9-5+2的分析树,每个语义动作都作为产生式左部所对应节点的子节点。实际上,我们将语义动作看成终结符,对确定动作何时执行来说,它是一种方便的助记符号。图中用具体的数字和加(减)运算符代替了记号num和addop。当以深度优先顺序执行下图中的动作时,其输出为95-2+。

E

翻译模式 T ? num {print(num.val)} T R

{print(‘9’)} 9

-

T

{print(‘-’)}

R

5

{print(‘5’)}

+ 2

T {print(‘+’)} {print(‘2’)}

R

?

图1 9-5+2的带有动作的分析树

在构造翻译模式时,要注意,如果同时存在继承属性和综合属性,则 (1)产生式右部符号的继承属性必须在这个符号以前的动作中计算出来。 (2)一个动作不能引用该动作右边符号的综合属性。

(3)产生式左部非终结符的综合属性只有在其引用的所有属性都计算出来以后才能计算。计算该属性的动作通常放在产生式右部的末尾。

从一个L属性语法制导定义,我们总能构造出满足上述3个条件的翻译模式。 4. 语法树

语法树是分析树的压缩形式,对表示语言的结构很有用。如产生式S ? if B then S1 else S2 在语法树中可能出现为:

if-then-else

S1

B

S2

图2 if-then-else语句的语法树

在语法树中,运算符和关键字不再是叶节点,而是作为内部节点成为分析树中叶节点的父节点。语法树的另一个简化是单产生式(形如A ? B)链可能消失

mknode(head, descent1, descent2, ?)和mkleaf(ID, id.place)是基于翻译模式构造语法树的两种动作。前者建立一个树节点,如mknode(+, a, b)表示a+b的结构;后者建立一个叶节点,id.place指向标识符在符号表中的位置。语法树有两种表示,一种是指针式,另一种是数组式。数组式用数组的下标取代节点的地址,是指针的另一种表现形式而已,本质上没有大的差别。树的实现算法可以参考《数据结构》。 本文的讨论重点是三地址码的生成,故不对语法树做详细的阐述。

5. 三地址码

三地址码是下列一般形式的语句序列: x := y op z。其中,x、y以及z是名字、常量或编译器生成的临时变量;op代表操作符,例如定点或者浮点算术操作符,或者是布尔型变量的逻辑操作符。注意这里不允许组合的算术表达式,因为语句右边只有一个操作符。这样,像x + y * z这样的源语言表达式应该被翻译成如下序列: t1 := y * z t2 := x + t1

t1与t2是编译器生成的临时变量。这种复杂表达式及嵌套控制流语句的拆解使得三地址码适合于目标代码生成及优化。由程序计算出来的中间值的名字的使用使得三地址码容易被重新排列。

通用的三地址语句有以下几种

(1) 形如x := y op z的赋值语句,其中,op是二元算术操作符或逻辑操作符。 (2) 形如x := op y的赋值指令,其中op是一元操作符。 (3) 形如x := y的复制语句,将y的值赋给x。

(4) 无条件跳转语句goto L101。接下来将执行标号为L101的三地址语句。

(5) 形如if x relop y goto L101的条件跳转语句。这条指令对x和y应用逻辑操作符(<, ==, >=等),如果x与y满足relop关系则执行带有标号L101的语句。如果不满足,紧接着执行if x relop y goto L101后面的语句,与通常的顺序一样。

(6) 过程调用param x和call p及return y,其中y代表一个返回值,是可选的。它们的典型应用如下面的三地址语句序列: param x1 param x2 ? param xn call p

作为过程调用p(x1,x2,?,xn)的一部分生成。

6. 中间代码生成

[1]三地址码的实现

三地址码的实现方法为四元式。四元式是带有四个域的记录结构,即op,arg1,arg2以及result。其中视三地址码的类型,某些域可能为空。arg1,arg2以及result域的内容正常情况下是指向这些域所代表的名字在符号表表项的指针。这样的话,临时名字在生成时一定要被填入符号表。对于支持作用域规则的语言,生成三地址码时需要有一个符号表栈,栈顶的符号表代表当前作用域。源代码中的Par_table_chain结构实现了这个栈及其必须支持的操作。

[2]声明语句的翻译

这里用FineC语言声明语句的翻译模式说明声明语句翻译的原理。FineC语言声明语句的翻译模式及其解释如下:

I.declaration-list --> declaration declaration-list1 | declaration1

这条文法可以产生至少一个声明,declaration-list是起始符

II.declaration --> var-declaration

| { Par_table_chain.mktable() } fun-declaration

{Par_table_chain.jumpout()}

这条文法有两个候选式,第一个候选式var-declaration匹配变量声明;当地一个候选式匹配失败时,说明当前记号流是一个函数声明。进入一个函数声明后,会产生一个新的作用域,一个作用域对应一个符号表,即Par_table。在这个函数声明中声明的变量如果和外围作用域的变量重名,则应该在这个函数的符号表中加入这一变量。

{ Par_table_chain.mktable() } 在进入下面的函数声明语法分析前,先生成一个新的符号

表,并把它置为当前符号表。外围符号表被挂起,外围符号表的地址被赋予新表。符号表链Par_table_chain实际上是一个栈式结构,它的成员是有相互嵌套关系的符号表。函数声明匹配完后,跳出它的作用域,把外围作用域置为当前活动作用域。

III. var-declaration --> “int”ID; { Par_table_chain.enter(ID.name, \ 这条文法对应变量声明,当匹配完“分号”时,说明当前记号流的确是一个变量声明(不需要回溯到II)。此时,在当前符号表中加入这个变量,三个参数分别是变量的名字,类型和宽度。符号表中有一个offset值,初始为0,每加入一个变量则把offset值加上此变量的宽度。如此一来,新加入的变量就能总是拥有正确的地址(在符号表中的位置)。 IV. fun-declaration --> type-specifier ID

{ Par_table_chain.set_name_type(ID.name,

type-specifier.type)}

{Par_table_chain.add_to_previous()} { Quads_code.gen_entry(ID.name) } (params) compound-stmt | type-specifier ID

{ Par_table_chain.set_name_type(ID.name,

type-specifier.type)}

{Par_table_chain.add_to_previous()} { Quads_code.gen_entry(ID.name) }

() compound-stmt 这条文法很长。当匹配函数声明,得到其返回值类型和函数名时。先在当前函数的符号表中附上当前函数的信息。然后在其外围作用域中加入这个函数项,参数为函数名,类型和当前函数作用域编号(隐含加入,详见源代码)。然后生成一个“entry 函数名”的三地址码,代表函数的入口在这里,调用函数时转向这个入口。Quads_code是三地址码数组。然后视情况判断是否匹配参数,然后匹配函数体。注意,因为作用域的跳出在文法II中,所以在匹配当前函数的参数和函数体时,发生的任何变量或函数声明,都被加入到当前函数域中,也就是我们想要的结果。

V. type-specifier --> \{ type-specifier.type := \

| \{ type-specifier.type := \ 匹配函数返回值的类型。

VI. params --> param, params | param 这条文法生成至少一个参数。

VII. param --> \{ Par_table_chain.enter(ID.name, \

{Quads_code.gen_param_load(Par_table_chain, \

当发生参数的声明时,把此参数加入当前作用域中,然后生成形如“load形参名”的三地址码,与过程调用时的“param实参名”一一对应。 [3]算术表达式的翻译

FineC算术表达式的翻译模式如下

I. additive-expression --> term {additive-expressionR.i := term.name} additive-expressionR

{additive-expression.name := additive-expressionR.s}


中间代码生成(2).doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:品牌管理课程论文

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: