理论教育 编译器设计之路:源代码实现与功能介绍

编译器设计之路:源代码实现与功能介绍

时间:2023-11-04 理论教育 版权反馈
【摘要】:笔者先简单介绍一下各语义子程序的功能,以便读者理解和阅读源代码。第25~29行:生成for语句的入口标号。下面,笔者对这三个函数的参数及功能作简单介绍,并不深究其实现。该函数主要用于判断两个操作数的类型是否兼容。程序5-12 semantic.cpp第16行:判断循环终值表达式的结果是否为有序类型。在此,不必深究OpVarSemantic函数的实现,只需理解其功能与GenIR函数是类似的即可。第26~28行:生成JNT语句,其目标标号为循环的出口标号。

编译器设计之路:源代码实现与功能介绍

笔者先简单介绍一下各语义子程序的功能,以便读者理解和阅读源代码

semantic076:1、检查循环变量是否有效。

2、生成Statement对象。

semantic077:1、生成循环变量赋初值语句。

2、生成入口标号语句,即翻译方案中的第一个LABEL语句。

semantic078:1、有效性检查。

2、生成LE_4语句。

3、生成JNT语句,目标标号为出口标号。

semantic080:1、有效性检查。

2、生成ME_4语句。

3、JNT语句,目标标号为出口标号。

semantic079:1、生成ADD_4/SUB_4语句,以改变循环变量。

2、生成JMP语句,目标标号为入口标号。

3、生成出口标号语句,即翻译方案中的第二个LABEL语句。

下面,再来看看for语句的相关语义子程序的实现。

程序5-10 semantic.cpp

第4、5行:检索当前过程函数的变量信息表,判断循环变量是否为局部变量

第7、8行:检索主函数的变量信息表,判断循环变量是否为全局变量。注意,两次检索存在优先级差别。标准Pascal规定循环变量只能是简单变量,故不必考虑复杂类型的情况。

第18行:设置m_iLoopVar属性。这个属性的值就是循环变量在变量信息表中的位序号。这个属性对于for语句的翻译是非常重要的。

第19~24行:生成for语句的出口标号。

第25~29行:生成for语句的入口标号。

第31~35行:生成for语句的continue目标标号。

程序5-11 semantic.cpp

第14行:循环初值表达式的结果类型必须为有序类型且与循环变量类型兼容。这里,调用了两个函数来完成判断,即IsOrd、TypeCompatible。这两个函数是类型系统的相关实现,这里不必深究,只需了解其基本功能即可。

第19行:生成循环变量赋初值语句。这里,笔者调用GenIR函数来实现这一功能。实际上,读者千万不要将循环变量赋初值的问题想得过于简单。如果循环初值表达式的类型与循环变量的类型是完全一致的,问题可能简单一些。不过,当两者类型仅仅是兼容的,则不得不考虑类型转换,这可能使实现复杂度大大增加。

第22行:生成LABEL指令。

semantic077中调用了三个函数,即IsOrd、TypeCompatible、GenIR。下面,笔者对这三个函数的参数及功能作简单介绍,并不深究其实现。(www.daowen.com)

【声明5-5】

bool CType::IsOrd(OpInfo Op)

该函数主要用于判断操作数Op的类型是否为有序类型。在Pascal语言中,不少语句、表达式对类型是否有序是有一定限制的。因此,有序类型的判断是有现实意义的。Op可以是常量、变量等。在标准Pascal中,关于有序类型有非常详细的定义。

【声明5-6】

该函数主要用于判断两个操作数(即Opl、Op2)的类型是否兼容。在类型系统中,讨论类型是否兼容通常是针对某一运算而言的。例如,integer和real类型的变量进行比较运算时,两者是兼容的。但如果试图用real类型的变量为integer类型的变量赋值时,那么,两者就是不兼容的。这里,参数Op就是用于说明两个操作数所处的运算情景。注意,TypeCompatible允许传递的运算情景就是运算符ID(与词法分析器中的定义一致)。在semantic077第14行中,CType::TypeCompatible(Opl,Rslt,17)的作用就是判断将Opl赋给Rslt时,类型是否兼容。根据词法分析器的定义,赋值运算符“:=”的ID就是17。

下面解释一下TypeCompatible函数返回值的含义。当函数返回一1时,即表示两者在给定运算情景下是不兼容的。当函数返回其他值(可能是1、2、3等)时,则表示两者是兼容的,至于不同正整数常量的具体含义将在第6章详述。

实际上,在类型系统中,TypeCompatible函数还存在几个不同形式的重载,以满足不同的需要。这里,不再逐一列举。

【声明5-7】

GenIR函数是一个用于生成IR的重要接口,其主要功能就是根据操作数类型与相应的语义生成所需的IR。由于IR操作数个数的不同,所以GenIR函数也存在几个不同形式的重载,以满足相应的需要。那么,为什么要设计IR生成接口呢?

在高级语言编译器中,由于类型种类繁多,这可能使得IR生成变得比较复杂。在编译过程中,即使是在操作数类型兼容(但不等价)的情况下,仍需要考虑隐式类型转换(强制类型转换)、IR指令选择等问题。设计GenIR函数的目的就是使得语义子程序只需关心表达式的语义及各操作数的来源,而不必关注隐式类型转换、IR操作符选择等细节。

这里,笔者解释一下其中的一个参数--BasicOpType Op。注意,这里的Op并不是IR的操作符,而只是一个描述运算情景的枚举常量。例如,BasicOpType::MOV就表示其语义是将Opl赋给Rslt,至于最终生成的IR操作符是ASSIGN S还是ASSIGN_4或者其他操作符,完全是由传人的操作数类型与类型系统决定的。更多细节说明请读者参考第6章。

程序5-12 semantic.cpp

第16行:判断循环终值表达式的结果是否为有序类型。并且判断在进行比较运算时循环终值类型与循环变量类型是否兼容。注意,for-to-do结构的循环条件是循环变量小于或等于循环终值表达式的结果,所以TypeCompatible(Opl,Op2,20)中的20就是终结符“<=”的ID。

第22~25行:生成一个临时变量,将其作为循环变量与终值表达式比较语句的结果变量,并且生成比较语句。注意,这里调用了OpVarSemantic函数实现比较语句的生成。在此,不必深究OpVarSemantic函数的实现,只需理解其功能与GenIR函数是类似的即可。

第26~28行:生成JNT语句,其目标标号为循环的出口标号。

程序5-13 semantic.cpp

semantic080与semantic078有两个主要区别:

(1)第14行中m_bIsDownto属性的设置不同。由于两个语义子程序所处的文法不同,所以该属性的值是完全不同的。

(2)第15行中调用TypeCompatible函数的传参不同,即第三个参数的值不同。在for-to-do结构中,循环条件是循环变量的值小于或等于循环终止表达式的结果,所以将终结符“<_”的ID(20)作为调用TypeCompatible函数的实参。而在for-downto-do结构中,循环条件是循环变量的值大于或等于循环终止表达式的结果,所以将终结符“>=”的ID(22)作为调用TypeCompatible函数的实参。

程序5-14 semantic.cpp

第13~15行:生成continue语句的目标标号。

第16~25行:根据for的结构,调用GenIR函数生成自减或自增1语句。请注意传入GenIR函数的第三个参数。

第26~28行:生成JMP语句,其目标标号为循环入口标号。

第29~31行:生成for语句的出口标号语句。

第32行:当前语句分析完毕,弹出CurrentStatement栈顶元素,以保证分析的正确性。

至此,笔者已经详细讲述了for语句翻译方案及其实现。当然,由于其中某些函数涉及表达式翻译的话题,所以没有剖析其实现细节。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈