第八章 语法制导翻译和中间代码生成
- 格式:doc
- 大小:187.00 KB
- 文档页数:11
第8章语法制导翻译和中间代码生成(3学时,15分钟)编译程序的任务是把源程序翻译成目标程序,这个目标程序必须和源程序的语义等同,也就是说,它们的语法结构可以不同,但表达的结果应完全相同。
通常,在语法分析过程中,每当一个产生式获得匹配(自上而下分析)或用于归约(自下而上分析)时,就执行相应于该产生式的语义子程序进行语义处理,这个过程就是语法制导翻译。
编译中的语义处理是指两个功能:第一,审查每个语法结构的静态语义,即验证语法结构合法的程序是否真正有意义,也称为静态语义检查或静态审查。
动态语义检查需要生成相应的目标代码,在运行时进行。
静态语义检查主要涉及以下几个方面:(1)类型检查,如参与运算的操作数及类型应相容。
(2)控制流检查,用以保证控制语句有合法的转向点。
如C语言中不允许goto转入case语句流;break语句需要寻找包含它的最小switch、while或for语句方可找到转向点,否则出错。
(3)一致性检查,如在相同作用域中标识符只能说明一次、case 语句的标号不能相同等。
第二,如果静态语义正确,语义处理的工作是要执行真正的翻译,即生成程序的一种中间表示形式(中间代码),或者直接生成实际的目标代码。
虽然源程序可以直接翻译为目标语言代码,但是通常编译程序还是采用了独立于机器的、复杂性介于源语言与机器语言之间的中间语言。
这样做的好处是:(1)便于进行与机器无关的代码优化;(2)使编译程序改变目标机更容易;(3)使编译程序的结构在逻辑上更为简单明确,以中间语言为界面,编译前端和后端的接口更清晰。
静态语义检查和中间代码产生在编译程序中的位置如图所示:需要指出的是:从单词符号序列的源程序到某种形式的中间代码,不像词法分析和语法分析那样有一套形式化的理论和算法(如自动机等),尽管已经有了一些形式语义系统,但大都没有得到公认。
这就是说,语义的形式化描述要远比语法的形式化困难得多。
目前较为常见的是用属性文法作为描述程序语言语义的工具,并采用语法制导翻译的方法完成对语法成分的翻译工作。
第八章语法制导翻译和中间代码生成1、教学目的及要求:本章介绍编译程序的第三个阶段语义分析及中间代码生成的设计原理和实现方法,要求理解语法制导翻译、语义动作的基本概念;掌握算数表达式和赋值语句到中间代码的翻译、布尔表达式的目标代码结构分析和到四元式的语法制导翻译。
◇明确语义分析在编译过程所处的阶段和作用。
◇掌握属性文法的基本概念。
◇使用属性文法和语法制导翻译方法描述具体的语义分析和产生中间代码。
2、教学内容:语法制导翻译的基本概念、中间代码的形式,可执行语句的语法制导翻译方法。
3、教学重点:语法制导翻译基本思想,语法制导翻译概述,基于属性文法的处理方法,自下而上分析制导翻译概述。
4、教学难点:属性文法的处理方法,语法制导翻译实现的方法。
5、课前思考◇ 回顾第一章介绍的编译过程,理解语义分析在编译过程中的位置和作用。
◇什么是中间表示(中间代码),为什么要中间表示?◇"语法制导翻译"是什么含义?◇ 高级语言语句的结构和低级语言结构的不同。
6、章节内容第一节语法制导翻译概述第二节中间代码的形式第三节简单算术表达式和赋值语句的翻译第四节布尔表达式的翻译8.1语法制导翻译概述编译程序的第三个阶段是语义分析。
语义分析的任务是:在词法分析和语法分析的基础上,分析所写源程序的含义,在理解含义的基础上生成中间代码或直接生成目标代码。
语义分析包括语义检查和语义处理。
语义检查,例:类型、运算、维数、越界。
语义处理,例:变量的存储分配、表达式的求值、语句的翻译(中间代码的生成)。
中间代码或称中间语言,是复杂性介于源程序语言和机器语言之间的一种表示形式,在中间代码一级可进行代码优化工作。
一、属性文法目前许多编译程序的语义分析均采用语法制导翻译方法,它比较接近形式化。
语法制导翻译法采用属性文法为工具来说明程序设计语言的含义。
属性是文法符号的语义性质的表示。
对于文法中的某个终结符或非终结符,其属性可以有“类型”、“值”或“存储位置”等。
一个属性文法包含一个上下文无关文法和一系列语义规则,这些语义规则附在文法的每一个产生式上。
形式上讲,一个属性文法是一个三元组 A=(G,V,F)G:一个上下文无关文法V:一个属性的有穷集F:关于属性的断言或谓词的有穷集其中:每个属性与文法的某个非终结符或终结符相联,每个断言与文法的某个产生式相联,如果对G中的某一输入串而言(句子),A中的所有断言对该输入串的语法树结点的属性全为真,则该串也是A语言中的句子。
例1、有文法G为E→T1+T2|T1 or T2 T→num|true|false若属性文法中用N.t表示与非终结符N相联的属性,则对上述表达式的类型检查的属性文法可表示如下:E→T1+T2 {T1.t=int AND T2.t=int}E→T1 or T2 {T1.t=bool AND T2.t=bool}T→num {T.t=int }T→true {T.t=bool }T→ false {T.t=bool }即与每个非终结符T相联的有属性t(即“类型”),t要么是int,要么是bool。
与非终结符E的产生式相联的断言是:两个T的属性必须相同。
例2、简单算术表达式求值的语义描述产生式语义规则(0)L→E print(E.val)(1)E→E1+T E.val:=E1.val+T.val(2)E→T E.val:=T.val(3)T→T1*F T.val:=T1.val*F.val(4)T→F T.val:=F.val(5)F→(E) F.val:=E.val(6)F→digit F.val:=digit.lexval(lexval指综合属性)每个非终结符都有一个属性val(即“数值”)。
单词digit的值是由词法分析程序提供的。
与产生式L→E相联的语义规则是一个过程,该过程实现的功能是打印由E产生的表达式的值。
我们可以理解为L的属性是空的或是虚的。
例3.说明语句中变量的类型信息的语义规则产生式语义规则(1)D→TL L.in:=T.type(2)T→int T.type:=integer(3)T→real T.type :=real(4)L→L1,id L1.in:=L.in addtype(id.entry,L.in)(5)L→id addtype(id.entry,L.in)该语义规则中的addtype为一过程调用,其功能是把每个标识符的类型信息登录在符号表的相关项中。
type表示类型,综合属性;in起到类型传递的作用,继承属性;entry是单词 id 的属性。
属性文法最早出自克努特笔下,它将属性分为两类:综合属性和继承属性。
综合属性:从语法分析树的角度来看,如果一个节点的某一属性,其值由子节点的属性值来计算则称该属性为综合属性。
有时把内在属性也看作是综合属性。
继承属性:在语法树分析中,若一个节点的某个属性值由该节点的兄弟结点或父节点的属性值来计算,则此节点的该属性称为继承属性。
二、语法制导翻译(产生中间代码)产生中间代码的一种简单办法是让每个产生式对应一个语义子程序。
在自上而下语法分析中,若一个产生式匹配输入串成功,或者在自下而上分析中,当一个产生式被用于归约时,此产生式相应的语义子程序就进入工作。
一个语义子程序描述了一个产生式所对应的翻译工作,这些翻译工作在很大程度上取决于要产生什么形式的中间代码。
包括:改变编译程序某些变量的值,查填各种符号表,诊察与报告源程序错误,产生中间代码等。
在语法分析过程中,随着分析的步步进展,根据每个产生式所对应的语义子程序(语义动作)进行翻译(产生中间代码)的方法叫做语法制导翻译法。
语法制导翻译方法既可用来产生各种中间代码,也可用来产生目标指令,甚至可用来对输入串进行解释执行。
三、语法制导翻译的具体实现途径假定有一个自底向上分析器LR(1),可将LR(1)分析器的能力加以扩大,使之能在用某个产生式进行归约的同时调用相应的语义子程序进行有关的翻译工作,每个产生式的语义子程序执行之后,某些结果(语义信息)必须作为此产生式的左部符号的语义值暂时保存下来,以便以后语义子程序引用这些信息。
假定有一个LR语法分析器,现在把它的分析栈扩充,使得每个文法符号都跟有语义值,如左图。
状态栈语义值符号栈假定我们现在要分析的语法成分是简单算术表达式,所完成的语义处理不是将它翻译成中间代码或目标代码,而是计算表达式的值。
假设语法分析方法是自下而上的。
在用某一产生式进行归约的同时就执行相应的语义动作,在分析出一个句子时,这个句子的“值”也就同时产生了。
以上是对输入串2+3*5的分析处理过程。
按照上述实现方法,若把语义子程序改为产生某种中间代码的动作,那么则可在语法分析的制导下,随着分析的进展逐步生成中间代码。
8.2中间代码的形式不同的编译程序使用了不同的中间代码,有些快速编译程序甚至几乎没有中间代码阶段,但为编译程序的结构在逻辑上更为简单、明确,尤其为使目标代码的优化比较容易实现,许多编译程序都采用了复杂性介于源程序语言和机器语言之间的中间语言,并将源程序翻译成这种中间语言程序(中间代码)。
常见的中间语言有逆波兰记号、三元式、四元式和树形表示等。
源程序的中间代码是在编译程序将高级语言程序翻译成汇编语言或机器代码的过程中产生的,使用中间代码形式的优点:可以不考虑机器特性;可移植性;便于优化处理。
一、逆波兰表示法1、定义逆波兰表示,也称后缀式,即运算量在前,算符写在后面,该方法可在不使用括号的情况下,无二义性地说明算术表达式。
逆波兰表示不仅能用来作为算术表达式的中间代码形式,而且也能作为其它语言结构的中间代码形式。
一般而言,若θ是一个K(K>=1)目运算符,对后缀式e1,e2,..ek作用的结果将被表示为e1e2..ek θ,不必使用括号,如:▪abc+* a*(b+c)▪ab+cd+* (a+b)*(c+d)▪abc*bd*+:= a:=b*c+b*d根据运算量和运算符出现的先后位置,就完全决定了一个表达式的分解。
2、后缀式的计算逆波兰表示法表示表达式,其最大的优点是最易于计算机处理表达式,可用一个栈计算表达式的值。
计算过程为:自左至右扫描后缀式,每碰到运算量就将其推入栈,每碰到一个k目算符时,就将其作用于栈顶的k个项,并用运算结果来代替这k个项。
例:考虑后缀式ab+c@*的计值过程 (@表示一目减)3、后缀式的推广遵守运算量在前,算符在后的规则,可将后缀表示法扩大到比通常表达式更大的范围,如条件语句if E then S1 else S2 可表示为E S1 S2 ¥又如:数组元素A[<下标表达式1>, <下标表达式2>,…, <下标表达式n>]可表示为<下标表达式1><下标表达式2>…<下标表达式n>A subs4、语法制导生成后缀式①E→E1 OP E2 {E.CODE:=E1.CODE‖E2.CODE‖OP}②E→(E1) {E.CODE:= E1.CODE}③E→i {E.CODE:=i}E.CODE表示构成后缀式的符号串。
‖表示两串的“连接”。
第①个产生式语义动作的意思是:E的后缀式等于E1和E2两个后缀式的连接,而后再接上算符OP。
第②个产生式的语义动作指出,把括号无条件地去掉。
第③个产生式的语义动作告诉我们,标识符的语义值是那个标识符本身。
二、三元式和树形表示1、三元式表达式及各种语句都可表示成一组三元式:(算符OP 运算对象1 运算对象2)。
例:a:=b*c+b*d的三元式表示为(1) ( * b c)(2) ( * b d)(3) ( + (1) (2))(4) ( := a (3))三元式中含有对中间计算结果的显示引用,如三元式(1)表示的是b*c的结果。
对于一目算符op,通常用一个运算对象,如规定arg1,而多目运算符,可用若干个连续的三元式表示。
2、间接三元式为便于代码优化处理,作为中间代码,常不使用三元式,而另设一张指示器表(间接码表),按运算的先后顺序列出有关三元式在三元式表中的位置,即利用一张间接码表辅以三元式表的方法来表示中间代码,这种方法即为间接三元式。
+ * a b d c 如x:=(a+b)*c; y:=d ↑(a+b)的间接三元式为:三元式表间接码表 (1) + a b(1) (2) * (1) c(2) (3) := x (2)(3) (4) ↑ d (1)(1) (5) := y (4)(4) (5)当在代码优化过程中需要调整运算顺序时,只需重新安排间接码表,无须改动三元式表。
另外由于另设了间接码表,相同的三元式就无须重复填进三元式表中,这样可以节省三元式表的存储空间。
3、树形表示如a*b+c*d 用树形结构表示为:树形表示是三元式表示的翻版。