前言
在之前的语法制导翻译中,我们学习了翻译模式,为文法定义匹配的语义动作,通过这个“动作”可以生成代码,但为了进行与机器无关的代码优化工作,选择生成中间代码(四元式),而不是机器代码。下面对每一类语句设计翻译模式,这里一个翻译模式就是一个“算法”。
说明语句
过程中的说明语句
过程中的说明语句: i d 1 , i d 2 , i d 3 , . . . , i d n : t y p e id_1, id_2, id_3, ..., id_n: type id1,id2,id3,...,idn:type, M → ϵ M\rightarrow \epsilon M→ϵ通过引入非终结符号将语义动作移至产生式最后,即可以归约之后才进行该动作。enter操作将符号填入符号表中,offset指示符号变量在数据区中的位置,后面生成中间代码时则指示四元式在代码区中的位置。
保留作用域的信息
编程时可能会出现嵌套过程,为了保留变量的作用域信息,我们使用层次化的符号表,对于一个子过程调用,offset域指向子过程符号表,同时子符号表在Header部分设置指回父过程符号表,在完成子过程后返回父符号表继续添加表项。
两个产生 ϵ \epsilon ϵ的产生式的作用均是创建一张新的符号表,将新表加入tblptr栈,将0加入offset, m k t a b l e ( x ) mktable(x) mktable(x)指示了 x x x是新创建表的父表。增加了 D → p r o c i d N D ; S D\rightarrow proc\ id\ ND;S D→proc id ND;S中enterproc用于产生子过程的表项,addwidth操作将当前表的offset保存在符号表的Header。
至此完成了符号表的构建,下面开始生成中间代码。
赋值语句的翻译
简单算术表达式及赋值语句
lookup操作从当前符号表中查找名字,找不到则转到外围符号表继续查找。
数组元素的引用
这里假设数组第
i
i
i维度从
l
o
w
i
low_{i}
lowi开始编号,因此元素
A
[
i
1
,
i
2
,
.
.
.
i
k
]
A[i_1,i_2,...i_k]
A[i1,i2,...ik]的地址为
b
a
s
e
+
(
(
.
.
.
(
(
i
1
−
l
o
w
1
)
×
n
2
+
i
2
−
l
o
w
2
)
.
.
.
)
×
n
k
+
i
k
−
l
o
w
k
)
×
w
base+((...((i_1-low_1)\times n_2+i_2-low_2)...)\times n_k+i_k-low_{k})\times w
base+((...((i1−low1)×n2+i2−low2)...)×nk+ik−lowk)×w
将
i
i
i和
l
o
w
low
low进行分离得到不会改变的基准地址,可以提前计算出来,存储至符号表中以及动态地址。
第一种的文法很容易想到,但是在翻译过程中没有办法追踪Elist属于哪个数组对象,无法排查数组越界错误(语法错误),因此使用第二种文法,先将 i d [ E id[E id[E归约为 E l i s t Elist Elist。
以下翻译模式主要是将数组元素的地址计算出来。
(6)用offset属性表示一个变量是简单变量还是数组,是数组,要通过数组引用将数组元素L.place[L.offset]赋值给E.place。
(7)此时动态地址已经储存在Elist.place中,乘上字宽 w w w,得到偏移量赋给L.offset,将Elist.array减去C得到数组的起始地址赋给L.place。
(9)中 g e n ( ∗ , E l i s t 1 . p l a c e , l i m i t ( E l i s t 1 . a r r a y , m ) , t ) gen(*,Elist_1.place,limit(Elist_1.array,m),t) gen(∗,Elist1.place,limit(Elist1.array,m),t)产生的四元式查询当前这一维度的长度和 E 1 . p l a c e E_1.place E1.place(存储 e k − 1 e_{k-1} ek−1)相乘,再加上 E . p l a c e E.place E.place,递推地计算 e k = e k − 1 × n k + i k e_k=e_{k-1}\times n_k+i_k ek=ek−1×nk+ik,最后将 e k e_k ek赋给 E . p l a c e E.place E.place。完成动态地址的计算。
(10)中 E l i s t . a r r a y = i d . p l a c e Elist.array=id.place Elist.array=id.place,正如我们上面提到,将id数组对象绑定到Elist上,方能调用 l i m i t limit limit方法获取数组每一维长度。
布尔表达式的翻译
数值表示法
作为条件控制的布尔式翻译
生成跳转四元式时并不能确定第四区块,也就是跳转的目的地址,因此利用第四区块构造出一条链表,表示等待填入,而且这条链表链接的跳转指令均将跳转至同一个目的地址。Merge操作发生在确定两条链表的目的地址相同时,backpatch操作发生在确定了跳转的目的地址的时刻。(这里可以深刻体会到:一个翻译模式是一个算法!)
(1)E是一个布尔表达式,或操作,只要一个为真就从真出口出去,因此,两个布尔表达式为真对应跳转语句的目的地址应该是一个,但此时还无法确定,通过Merge操作将两个链表合并,等待填入。
E
1
.
f
a
l
s
e
l
i
s
t
E_1.falselist
E1.falselist进行回填,因为
E
1
E_1
E1为假,应该跳转至
E
2
E_2
E2所对应的布尔表达式进行判断,而这个表达式的首地址通过非终结符M记录下来了,因此用属性M.quad回填
E
1
E_1
E1为假的情况下对应的链表。
(2)类同(1)。
(6)出现一个布尔表达式,生成两条跳转语句(真/假出口),但这两条语句的目的地址都无法确定,因此创建链表链接它们,等待填入。
控制语句的翻译
控制流语句
为语句S增加属性nextlist,表示S之后要执行的语句。E是已经归约完成的布尔表达式。
(1)用该产生进行归约时,
M
1
,
M
2
M_1,M_2
M1,M2通过空字归约得到,它们分别记录了代码段
S
1
,
S
2
S_1,S_2
S1,S2的首地址,也就是布尔表达式E的真/假出口,回填。最后将
S
1
,
N
,
S
2
S_1,N,S_2
S1,N,S2的nextlist进行合并。
(5)nextlist属性在这里使用,因为while语句的执行逻辑是执行完S之后,返回入口表达式进行判断,所以**并不是所有的语句S执行完都继续执行下一句!**因此引入了nextlist属性。
(6)begin,end语法
⟺
\iff
⟺C/C++中的大括号。
(7)A是赋值语句,通过产生式
S
→
A
S\rightarrow A
S→A归约成代码段,并通过mklist创建链表,等待填入代码段之后的下一条语句的地址。
(8)识别到分号,表示这是顺序执行的两条语句,用M.quad回填前面一条语句的nextlist,并将S.nextlist赋给L.nextlist。
标号与goto语句
在符号表中记录标号和地址的关系,遇到标号则查找标号表中是否包含该标号,若有则标号重定义,报语法错误;若没有则将标号将入符号表。遇到goto语句可能有3中情况:
(1)标号已存在且已经定义,生成四元式(j,-,-,p);
(2)标号不存在,符号表中增加标号,其地址设置为nxq,生成四元式(j,-,-,-),等待回填;
(3)标号已存在但没有定义,由于符号表中只有一项,无法构造出链表,因此直接生成四元式(j,-,-,x),x为符号表中该标号暂时对应地址,这个位置可能还是一条跳转四元式,最后通过(2)产生的四元式跳转到标号处,通过多次跳转跳转到标号处。
Case语句的翻译
在goto语句的基础上实现, I → A S I\rightarrow AS I→AS,将一个case和相应的statement归约,不同于之前的Merge操作,这里查表next,获取地址a,将标号next的地址修改为nxq(即后面生成四元式的地址),生成一条四元式(j,-,-,a),跳转到该地址。
reference
山东大学编译原理郑艳伟老师ppt