6 赋值语句
主要任务
- 将复杂的表达式转化成多条计算指令组成的序列
- 分配临时变量保存中间结果
- id:查符号表获得其存储的场所
- 数组元素的地址计算(符号表中保存数组的基址和用于地址计算的常量表达式的值)
- 可以进行一些语义检查(类型检查、变量未定义/重复定义/未初始化)
- 类型转换
赋值语句的中间代码生成
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 $$
翻译的主要任务
- 发射地址计算的指令
- 相关中间指令 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 float,i,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;
}
...