前面,提出了一个表达式翻译的基本模型,并通过实例分析了翻译模型的工作过程。本小节将在此基础上讨论如何应用这个翻译模型生成NPIR,只有生成了NPIR才算是真正意义上实现了表达式的翻译。
实际上,在先前提出的翻译模型中,忽略了一个重要的元素——类型。数据结构课程讨论表达式计算时,并不涉及类型,当然,也不需要涉及类型。不过,对于编译器而言,这却是不可忽视的重要因素。在前面章节中,笔者已经介绍了类型系统的相关理论与实现,但始终没有将其应用到实际翻译中。这里,讨论的重点就是把类型系统引入到表达式翻译中,让读者明白类型系统并不是游离于表达式而存在的。
与翻译模型不同,作为一个实际编译器,Neo Pascal在生成运算IR之前,通常还需要完成几项工作:
(1)评估操作数的相容性。操作数在给定的运算符环境中是否相容是编译器设计者关心的。继续翻译生成IR的前提就是操作数必须相容。如果操作数不相容,编译器就必须终止分析并报错,否则后续的翻译与分析是没有任何意义的。
(2)生成类型转换IR。类型等价与类型相容的语义处理是不同的。在类型等价的情况下,编译器不必考虑类型隐式转换等问题。不过,在类型相容的情况下,编译器通常需要生成类型转换IR,以保证运算IR操作数的类型匹配。(www.daowen.com)
(3)推断结果操作数的类型。在表达式翻译中,编译器通常需要使用临时变量来存储运算的中间结果,那么,临时变量的类型(即运算结果的类型)就需要由编译器推断得到。由于表达式经常出现在赋值语句中,有些读者可能误以为被赋值对象的类型就是表达式运算结果的类型,这是完全错误的。读者应该明确一个概念,表达式结果操作数的类型只取决于操作数的类型与运算符本身。那么,是不是表达式结果都是用临时变量存储的呢?的确如此,至少在翻译表达式时,编译器必须这么做。即使编译器在翻译A:=B+C时,也不会生成直接类似于(ADD,B,C,A)这样的IR。而是申请一个临时变量T,将B+C的计算结果暂存于T中,然后,再考虑将T的值赋给A。有些读者可能会质疑为什么需要T作为暂存呢?事实上,除了T与A的类型完全等价的情况之外,将T的值赋给A时,编译器还需要考虑两者之间的隐式转换问题,所以临时变量T是有存在必要的。在表达式翻译阶段,即使是T与A的类型完全等价的情况下,为了使表达式翻译规程统一,也不必考虑将T省去。当然,设计者也不希望产生冗长的临时变量,在后续优化阶段,借助于一些专门的IR优化算法解决这类问题是非常有效的。
(4)常量折叠。简言之,常量折叠就是针对那些所有操作数都是常量的运算或表达式,编译器会在编译阶段直接计算得到其运算结果,而不必生成相应的运算IR。严格地说,这个话题应该属于编译优化的范畴。不过,在表达式翻译过程中,对于编译器来说,无论采用何种语法分析方式,发现与处理这类问题都是相对简单的,而其产生的效果也是非常显著的。因此,编译器设计者都会考虑将常量折叠纳入表达式翻译方案中。当然,表达式翻译时可以完成的常量折叠是有限的,它并不能完全替代优化阶段的常量折叠算法。常量折叠的思想并不复杂,但是,具体实现却比较容易出错。除了常量的类型推断之外,编译器设计者应该特别注意编译环境与运行环境不一致可能导致的错误。
至此,笔者详细讨论了Neo Pascal表达式翻译的基本问题。不过,这些问题并非程序设计语言表达式翻译的全部内容。试图设计一个完备的模型以应对一切程序设计语言复杂且多变的应用需求可能是徒劳的。在编译器设计中,设计者经常需要对一些经典的翻译模型进行改进与完善以适应不同语言的需要,例如,C语言的布尔表达式的短路机制。这是编译原理课程中一个非常经典的翻译话题,有兴趣的读者可以参考相关书籍,其基本思想是值得借鉴与学习的。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。