跳转至

6 赋值语句

主要任务

  1. 将复杂的表达式转化成多条计算指令组成的序列
  2. 分配临时变量保存中间结果
  3. id:查符号表获得其存储的场所
  4. 数组元素的地址计算(符号表中保存数组的基址和用于地址计算的常量表达式的值)
  5. 可以进行一些语义检查(类型检查、变量未定义/重复定义/未初始化)
  6. 类型转换

赋值语句的中间代码生成

id.lexeme 属性代表组成该名字的字符序列

E.place 用来记住符号表条目的地址

newTemp 用来产生一个新的临时变量的名字,将其存入符号表,并返回符号表条目的地址

emit 将其参数写到输出文件上

对 p.info 这样对记录域的访问,先从名字表中找到 p,p 应该是个记录类型的变量,上一节告诉我们 p 的类型是 record ( tblptr ),tblptr 是该记录类型的符号表,从这个符号表中就可以去找 info 条目

  • S → id := E
  • E → E~1~ + E~2~ | -E~1~ |(E~1~)| id

visitS

  • id := E

结尾:获取 id 的地址和存放 E 结果的场所,发射赋值指令

p = lookup(id.lexeme);
if (p != nil) emit(p, '=', E.place);
else error;

visitE

  • E → E~1~ + E~2~

结尾:发射加法指令

E.place = newTemp(); // 新建结果
emit(E.place, '=', E1.place, '+', E2.place);
  • E → -E~1~

结尾:发射负号运算指令

E.place = newTemp();
emit(E.place, '=', uminus, E1.place);
  • E →(E~1~)
E.place = E1.place;
  • E → id

结尾:获取 id 的地址并作为 E 的场所

p = lookup(id.lexeme);
if (p != nil) E.place = p;
else error;

数组元素的地址计算

一维数组元素的地址计算

数组 A 的第 i 个元素的地址: $$ base+(i-low)w $$ w 是宽度,上式可以变换成 $$ iw+(base-low*w) $$ 其中 low*w 是常量,可在编译时计算,减少运行时计算。

二维数组元素的地址计算

行为主序时,A[i_1,i_2] 的地址: $$ base+((i_1-low_i)n_2+(i_2-low_2))w $$ 其中 n_2=high_2-low_2+1

变换成: $$ ((i_1n_2)+i_2)w+(base-((low_1n_2)+low_2)w) $$ 后半部分是常量,可在编译时计算。

多维数组元素的地址计算

行为主序时,A[i_1,...,i_k] 的地址: $$ ((...((i_1n_2+i_2)n_3+i_3)...)n_k+i_k)w+B $$ 常量 B 为 $$ B=base-((...((low_1n_2+low_2)n_3+low_3)...)n_k+low_k)w $$

翻译的主要任务

  1. 发射地址计算的指令
  2. 相关中间指令 t=b[o], b[o]=t

数组元素的访问处理

  • S → L := E
  • L → id [Elist] | id
  • Elist → Elist, E | E
  • E → L | ...

采用语法制导的翻译时存在的问题

  • Elist → Elist, E | E
  • 由 Elist 的结构只能得到各维的下标值,但是无法获得数组的信息(如各维的长度)

需要改写文法为:

  • L → Elist ] | id
  • Elist → id [ E | Elist, E

【Elist → id [ E】这个定义可以获得数组的信息,并且从左到右传播(引入继承属性)

Elist 的综合属性 array 传递符号表中数组名条目的指针

Elist.ndim 来记录已经分析过的下标表达式的个数

limit (array, j) 返回 n_j,是 array 指向的数组中第 j 维的大小

invariant (array) 从 array 的符号表条目中取静态可计算的值(上面的常量 B

width (array) 从 array 的符号表条目中取数组元素的宽度

Elist.place 是临时变量,保存下标表达式计算值

visitL

  • L → id [ E~1~, E~2~ , ... , E~n~ ]

访问 E~1~ 之后:

ndim = 1;
place = E1.place; // 二者都是局部变量

每次访问 E~n~ 后计算:

t = newTemp();
ndim++;
emit(t, '=', place, '*', limit(id.place, ndim));
emit(t, '=', t, '+', En.place);
place = t;

结尾:

L.place = newTemp(); // 计算场所的值
emit(L.place, '=', base(id.place), '-', invariant(id.place));
L.offset = newTemp(); // 计算偏移量
emit(L.offset, '=', place, '*', width(id.place));

visitE

  • E → L

结尾:

if (L.offset == null) E.place = L.place; // 简单变量
else {
    E.place = newTemp();
    emit(E.place, '=', L.place, '[', L.offset, ']');
}

visitS

  • S → L := E

结尾:

if (L.offset == null) emit(L.place, '=', E.place); // 左值是简单变量
else emit(L.place, '[', L.offset, ']', '=', E.place); // 左值是b[o]

类型转换

例如:x=y+i*j,其中 x,y\in floati,j\in int

中间代码:

t1 = i int * j
t2 = inttofloat t1
t3 = y float + t2
x = t3
  • E → E~1~ + E~2~ (举例)

结尾:判断 E~1~ 和 E~2~ 的类型,看是否要进行类型转换。如果需要,则分配存放转换结果的临时变量,并发射类型转换指令。

E.place = newTemp();
if (E1.type == int && E2.type == int) {
    emit(E.place, '=', E1.place, 'int+', E2.place);
    E.type = int;
}
else if (E1.type == int && E2.type == float) {
    u = newTemp(); // 需要类型转换,分配存放转换结果的临时变量
    emit(u, '=', 'inttofloat', E1.place);
    emit(E.place, '=', u, 'float+', E2.place);
    E.type = float;
}
...
本文阅读量