Note4- 中间代码生成

在这一部分,我们将学习各类语句的翻译,包括声明语句,赋值语句,控制语句,witch 语句,过程调用语句等。

声明语句

声明语句翻译的主要任务是收集标识符的类型等属性信息,并为每一个名字分配一个相对地址。

类型表达式

首先,基本类型,如 integer, real, char, boolean 等是类型表达式类型构造符可以作用于类型表达式,其结果也是类型表达式

举例:

声明语句的翻译

声明语句的翻译就是解决开头提到的两个问题:

我们从类型表达式可以知道该类型在运行时刻所需要的存储单元数量(类型的宽度)。在编译时刻,可以使用类型的宽度为每一个名字分配一个相对地址。

因此我们为每个符号在符号表中设置两个字段:类型和相对地址。

变量声明语句的 SDT

设置 enter 函数,enter(name, type, offset) 表示在符号表中为名字 name 创建记录,将 name 的类型设置为 type,相对地址设置为 offset。在 SDT 中,每到一个声明,就将 offset 加上这个类型的宽度。接下来,我详细举例。

设有如下 SDT:

我们要翻译 real x; int i;

  1. 根据所有具有相同左部的产生式的 SELECT 集是否判断该文法是否为 LL(1) 文法。经验证,这是 LL(1) 文法。

  2. 指针指向 real,首先根据第一个产生式替换 P

  3. 此时语义动作 {offset=0} 在栈顶,我们执行这个语义动作,初始化了 offset,然后将语义动作 {a} 出栈

  4. 此时栈顶为 D,根据第二个产生式替换 D

  5. 进行若干步替换栈顶非终结符,得到树为:

  6. 此时栈顶匹配成功,real 出栈,然后执行动作符号,计算出 B 的综合属性 B.type=read; B.width=8。然后执行第四条产生式中的语义动作,令 t=B.type, w=B.width。

  7. 接下来,栈顶为 C,而指针指向 x,所以用第八个产生式替换,此时又要执行语义动作,将刚刚保存的 t, w 在赋值给 C

  8. 接下来匹配到 x,和分号,然后要执行语义动作,这个语义动作就是在符号表中创建记录,并更新 offset

  9. 后半部分同理,不再赘述,最终形成树和符号表插入条目为:

赋值语句

赋值语句翻译的主要任务就是生成对表达式求值的三地址码。

简单赋值语句的翻译

可以看到,这样的翻译进行了大量的 code 拼接,我们也可以不用这种方式,而是使用增量翻译,修改 gen() 函数,让 gen() 函数将生成的三地址指令拼接到至今为止已生成的指令序列之后。

数组引用的翻译

将数组引用翻译成三地址码时要解决的主要问题是确定数组元素的存放地址,也就是数组元素的寻址。

对于一个 k 维数组:array(n1,array(n2,)),数组元素 a[i1][i2][ik] 的相对地址是 base+i1×w1+i2×w2++ik×wk,其中 wi 表示前 i 维的大小。

而在数组引用基本文法为:

我们为 L 设置三个综合属性:

控制流语句

控制流语句的基本文法:

控制流语句的代码结构

S -> if B then S1 else S2 为例,其代码结构为:

布尔表达式 B 被翻译为由跳转指令构成的跳转代码。需要解决的问题是如何确定跳转指令中目标地址:

控制流语句的 SDT

让我们尝试编写 if-then-else 语句的 SDT:

while-do 语句的 SDT:

布尔表达式

布尔表达式的值是通过代码序列中的跳转位置来表示的。如:

布尔表达式的 SDT

回填

生成一个跳转指令时,暂时不指定该跳转指令的目标标号。这样的指令都被放入由跳转指令组成的列表中。同一个列表中的所有跳转指令具有相同的目标标号。等到能够确定正确的目标标号时,才去填充这些指令的目标标号。

对非终结符 B 维护如下综合属性:

定义如下函数:

布尔表达式的回填

这里 M 的作用就是获得 B2 的第一条指令的标号

控制流语句的回填

switch 语句的翻译

switch 语句可以翻译成这种代码结构:

也可以这样,将分支测试指令集中在一起:

过程调用的翻译