PL/0源程序
var n,i,apple;
procedure fordiy;
const a=7;
var b,c;
begin
while i>0 do
begin
apple:=(apple+1)*2;
i:=i-1;
end;
end;
begin
read(n);
i:=n;
apple:=1;
call fordiy;
write(apple);
end.
分析
1、常量变量定义并不调用gendo,包括produre里的常变量定义,直到三种定义过后,进入本层的第一个statement处理语句之前调用gendo实现int虚拟机代码,才开辟空间。
2、然后进入produre的statement语句。
3、while语句里 如i>0,i为var用lod,为const用lit(栈顶存立即数,number也是用这个),这两个指令都是将数存入栈顶。因此栈顶就得到i的值和0了。这两个值是两个expression函数分别得到的,然后检查中间的>,得到opr 0 12,表示次栈顶大于栈顶的话,次栈顶为1(因为是0的话表示不满足,会用jpc判断为0跳转),栈顶指针-1。则把这个条件句分析完了,然后while条件语句出来直接来个jpc 0 0,表示如果栈顶为0,转移至地址0,用于跳出while循环(暂时是0,编译程序分析到while完成后,根据cx来改)
如图:
4、然后进入while的statement语句。
解析赋值语句
如图:分别是获取apple的值,得到number 1,opr 0 2 把两个相加,lit 0 2再得到2,然后opr 0 4 让次栈顶值和栈顶值2相乘,并存在次栈顶,栈顶指针-1,最后sto 1 5表示把这个值存放在层差为1,偏移量为5的那个位置,然后栈顶指针-1。
再解析这个:
5、如图:lod 1 4 表示得到层差为1,偏移量为4的位置上的内容,即i的值,这个可以通过符号表看出来(当前为1层,层差为1即第0层上的addr=4的那个变量,即i)。然后得到常数1,opr 0 3 让次栈顶减去栈顶(前减去后),再用sto存回去,跟前面一样。然后就是end了,这个while完毕。
注:这里可以看出为什么常量没有addr和lev,而只有一个val,因为常量只需要查到这个name之后,取它的值,而不需要修改它,因此不需要知道它在哪,而var常量需要被修改,故存放地址,所以生成常量lit指令就只需要名字表里的val值,lit的A就直接存放常量的值,而变量需要lod(其第二个是L,第三个A是偏移)
思路:名字表是定义变量时存放来做辅助用的,后面根据编程语句生成P-code指令时会用到名字表的信息。
6、while的statement语句出来后,gendo一个jmp 0 cx1,这个cx1是进入condition条件处理前保存的cx,在这里即lod之前的虚拟机代码指针,应该是3。如图,确实是3,继续回到while判断句那儿,看符不符合继续循环的条件:
7、produre的statement语句出来后,再gendo生成opr 0 0,表示结束被调用过程,返回调用点并退栈。
8、终于进入主过程体的编译过程。
这里给出当下的名字表:
·用int 0 6申请空间后,依次翻译read(n)(opr 0 16控制台输入并放在栈顶,sto 0 3存入层差为0,偏移量为3的位置,即n,然后栈指针-1)、i:=n(lod 0 3取出n到栈顶,然后sto 0 4存入i,栈指针-1)、apple:=1(lit 0 1得到1,sto 0 5存入apple)、call fordiy(cal 0 2调用地址为2,层差为0的过程,这个在名字表里可见)、write(apple)(lod 0 5取apple的值到栈顶,opr 0 14输出栈顶的值且栈指针-1,opr 0 15控制台屏幕输出一个换行)。到这儿最后只剩下opr 0 0,这个指令是每个block最后都有的,来结束被调用过程并返回调用点。
中间代码及注释
// 0和1 不是没用的jmp 0 0,这个jmp后的值应该是被改了,没有输出而已
(实际上一进来的0就是jmp 0 19跳到了19的代码处,直接运行主过程,开辟主过程空间;而1应该就是jmp 0 2转向过程fordiy入口)
// 2是开辟produre的空间
2 int 0 5
// 接下来4条是处理while的判断语句(while i>0)
3 lod 1 4
4 lit 0 0
5 opr 0 12
6 jpc 0 18(这里显然后期该过,while分析完成后改的,本来是jpc 0 0,用于跳出循环)
// 这6条处理apple:=(apple+1)*2;
7 lod 1 5
8 lit 0 1
9 opr 0 2
10 lit 0 2
11 opr 0 4
12 sto 1 5
// 这4条处理i:=i-1;
13 lod 1 4
14 lit 0 1
15 opr 0 3
16 sto 1 4
// 这个是while出来后的jmp,即继续判断while里的循环条件
17 jmp 0 3
// 这里就是produre执行完了,用这一句来返回调用点并退栈
18 opr 0 0
// 表示进入主过程的程序体,开辟空间(当然,也是在进入begin前就输出这个了)
19 int 0 6
// 处理read(n);
20 opr 0 16
21 sto 0 3
// 处理i:=n;
22 lod 0 3
23 sto 0 4
// 处理apple:=1;
24 lit 0 1
25 sto 0 5
// 处理call fordiy;
26 cal 0 2
// 处理write(apple);
27 lod 0 5
28 opr 0 14
29 opr 0 15
// 最后结束被调用过程(主过程)
30 opr 0 0