Atitti. 语法树AST、后缀表达式、DAG、三地址代码
抽象语法树的观点认为任何复杂的语句嵌套情况都可以借助于树的形式加以描述。确实,不得不承认应用抽象语法树可以使语句翻译变得相对容易,它很好地描述了语句、表达式之间的联系。不过,由于Neo Pascal并不会显式构造抽象语法树,所以不得不借助于其他数据结构实现。根据先前的经验,栈结构就是不二之选。
DAG(有向无环图)
后缀表达式:也称为逆波兰表达式,这种形式简单明晰,便于存储。在处理表达式翻译时,后缀表达式有着其他形式无法比拟的优势。不过,由于后缀表达式的应用领域比较单一,所以很少独立作为一个实际编译器的IR存在。
1. 后缀表达式
不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则,如:(2 + 1) * 3 , 即2 1 + 3 *
作者:: ★(attilax)>>> 绰号:老哇的爪子 ( 全名::Attilax Akbar Al Rapanui 阿提拉克斯 阿克巴 阿尔 拉帕努伊 ) 汉字名:艾龙, EMAIL:1466519819@qq.com
转载请注明来源: http://blog.csdn.net/attilax
1.1. 前缀记法、中缀记法和后缀记法
它们都是对表达式的记法,因此也被称为前缀记法、中缀记法和后缀记法。它们之间的区别在于运算符相对与操作数的位置不同:前缀表达式的运算符位于与其相关的操作数之前;中缀和后缀同理。
举例:
(3 + 4) × 5 - 6
就是中缀表达式
- × + 3 4 5 6 前缀表达式
3 4 + 5 × 6 - 后缀表达式
中缀表达式(中缀记法)
中缀表达式是一种通用的算术或逻辑公式表示方法,操作符以中缀形式处于操作数的中间。中缀表达式是人们常用的算术表示方法。
虽然人的大脑很容易理解与分析中缀表达式,但对计算机来说中缀表达式却是很复杂的,因此计算表达式的值时,通常需要先将中缀表达式转换为前缀或后缀表达式,然后再进行求值。对计算机来说,计算前缀或后缀表达式的值非常简单。
三地址代码:也称为"四元组",即操作符和三个操作数地址。这是一种最为常见的IR。甚至有些书籍认为IR就是中间代码(即三地址代码
所谓的三地址,指的就是每一行代码通常包含三个地址信息,即操作数1、操作数2、结果操作数。例如,(ADD A,1,C )这行三地址代码的含义就是A+1→C。这种形式初看与汇编语言有点类似
当然,三地址代码也不是完美的,由于它是相对离散的,在分析源程序结构方面,它就不及语法树便捷
三地址代码的理由如下:三地址代码是一种线性IR。由于输入源程序及输出目标程序都是线性的,因此,线性IR有着其他形式无法比拟的优势。另外,相对于其他表示形式而言,程序员对于线性表示形式通常会有一种莫名的亲切感,编译器设计者当然也不例外。早期编译器设计者往往都是汇编语言程序设计的高手,可以非常自然、流畅地阅读线性的三地址代码形式。同时,线性表示形式也会降低输入输出的实现难度。随着编译器"端"、"遍"等概念的出现,IR已经不仅仅是一种存储在内存中的数据结构。有时它也需要以文件形式转存输出,作为接口供其他系统读取使用。
为什么将其设计为"三地址"的形式呢?实际上,这是计算机科学家经过多年实践探索后才得到共识的。三地址代码并不是唯一的线性IR,只能说是最为常见的而已。在编译技术领域,二地址代码、单地址代码(即栈式机代码)都曾出现过,也曾在某些应用领域盛行一时,尤其是单地址代码。
然而,单地址代码的情况则截然不同了,在现代编译器设计中,单地址代码也是应用比较广泛的一种IR。尤其是近年随着混合语言的日渐壮大,单地址代码也重新进入了人们的视野。由于执行单地址代码程序的栈式机架构相对比较简单,可以非常方便地构造相关的解释器或虚拟机,所以单地址代码深受混合语言设计者的欢迎。读者熟悉的Java字节码、.NET的IL都是单地址代码
三地址代码是在二地址代码的基础上发展而来的。二地址代码的不足之处在于它通常会给其中一个源操作分量带来一定副作用。当然,这种设计的灵感最初是来源于x86指令系统的,但是却忘了一个重要的区别:x86指令中往往都是以寄存器作为暂存空间的。而暂存空间对于二地址代码却是一个棘手的问题。为了解决二地址代码的不足,人们提出了一个对源操作分量不产生任何副作用的形式,那就是三地址代码。也就是说,在一行三地址代码中,任何运算都不会改变两个源操作分量。这是三地址代码与二地址代码的主要区别。这个特性是非常重要的,它将使得编译器更自由地复用名字与值,不必考虑代码带来的副作用。
最后,再来谈谈IR的级别,即IR依赖于目标机的程度。按级别分类,可将IR分成三类:高级形式(HIR)、中级形式(MIR)、低级形式(LIR),也可称为高级中间语言、中级中间语言、低级中间语言。
高级形式(HIR)是一种尽可能保持了源语言程序结构的IR,这种形式能较好地保留源程序的原始语义信息。由于高级形式太接近源语言程序结构,所以很少有编译器将其独立作为IR传递给后端。
中级形式(MIR)既要以一种与语言无关的方式在一定程度上反映源语言的特性,又要能够适应多种体系结构的IR。中级形式是一种比较常用的IR,它兼顾了源语言、目标机的特性,又能适用于大多数优化算法。当一个编译器仅设计一种IR时,中级形式是较理想的选择。
低级形式(LIR)就是在一定程度上包含某些目标机特性的IR,比目标语言稍高级,常作为一些机器相关的优化算法的输入。不过,实际上,除了一些较大型的编译器需要使用低级形式之外,低级形式并不是很常见。因为更多编译器设计者更愿意直接基于目标语言作优化。
表5-6 if语句的翻译方案
if语句 |
翻 译 方 案 |
if <表达式> then <语句1> else <语句2> |
<表达式翻译> (JNT , <表达式结果> , null , __L1 ) <语句1> (JMP , __L2 , null , null ) (LABEL , __L1 , null , null ) <语句2> (LABEL , __L2 , null , null ) |
如果省略了else部分,那么只需将翻译方案中第4~6行语句省略,并将第7行的"__L2"替换为"__L1"即可。semantic068、semantic069、semantic070主要的功能就是根据翻译方案翻译输入的if语句。也就是说,试图依靠这三个语义子程序,完成翻译方案中黑体语句的生成。在上述翻译方案中,可以暂且将"__L1"称为"假分支标号",而将"__L2"称为"出口标号"。另外,需注意一点,当输入语句是if-then结构时,第7行语句的标号不应该取出口标号,而应该取假分支标号,因为此时并不存在真正意义的假分支,因此,可以将假分支标号当作出口标号使用。
while语句的翻译方案
while语句 |
翻译方案 |
while <表达式> do <语句> |
(LABEL ,__L1,null,null) <表达式翻译> (JNT ,<表达式结果>,null,__L0) <语句> (JMP ,__L1,null,null) (LABEL ,__L0,null,null) |
5.1.2 IR设计及其级别 - 51CTO.COM.html