数据结构
mid_code:这个是列表,存放的语法分析生成的中间代码
stack:数据栈,这里用列表开辟8K个空间,其实代码只用到了15个,这个可以大大缩减
B:基址寄存器,
T:栈顶寄存器,指向栈顶的位置
I:指令寄存器,存放的是当前要执行的中间代码
P:存放下一条指令的列表下标,通过mid_code[p]获取指令
解释器的运行
通过 I = mid_code[p] 获取指令,通过指令的操作码,也就是 I['F'] 来执行不同的指令
JMP和JPC指令是把P指向跳转的指令地址,也就是 I['A'] 的值
INT指令是给数据栈开辟空间,需要修改栈顶寄存器T的值
LIT指令是把常数,也就是 I['A'] 的值放到栈顶
OPR指令除了OPR 0,0 其余都是通过栈顶或者次栈顶和栈顶数据的运算,然后把结果放在栈顶
LOD、STO、CAL等指令牵涉到get_sl函数,需要详细讲解一下SL,DL,RA的作用
每一次过程调用都将在运行栈增加一个过程活动记录,当前活动记录的起始单元由基址寄存器B指出
过程活动记录中的头3个单元是固定的联系信息:
静态链SL:存放的是定义该过程所对应的上一层过程,最近一次运行时的活动记录的起始单元。
动态链DL:存放的是调用该过程前正在运行过程的活动记录的起始单元。过程返回时当前活动记录要被撤销,此时需要动态链信息来修改基址寄存器b的内容。
返回地址RA:记录该过程返回后应该执行的下一条指令地址,即调用该过程的指令执行时指令地址寄存器p的内容加1
每当一个过程被调用,就需要在栈上先分配3个空间用来存储上述信息,然后才是分配空间存储过程的局部变量。对于主过程,SL=DL=RA=0
下面举个例子详细说明,PL\0代码如下
var m, n, g;
procedure gcd(m,n);
begin
if n = 0 then
g := m;
else
g := gcd(n, m mod n)
end
begin
m := 24;
n := 16;
g := gcd(m, n)
end
运行时栈如下图所示
RA的作用
当主函数执行到 g := gcd(m,n) 语句时,中间代码生成的是CAL指令,这时会创建一个新的活动记录,新的活动记录会先开辟三个空间SL、DL、RA。RA记录的是CAL 指令的下一条指令,也就是过程调用返回的指令OPR 0,0在中间代码中的位置P,这样gcd过程调用结束后可以直接获取下一条需要执行的指令
DL的作用
DL记录的是当前过程返回后上一层活动记录的起始单元,当执行CAL指令创建新的活动记录的时候,基址寄存器B就会指向栈顶寄存器+1,也就是新的过程活动记录的SL。但是当前活动结束后需要执行OPR 0,0指令返回到调用该过程的那一层,所以需要恢复调用层的SL在数据栈的位置(也可以说是B寄存器的值,但是不能理解为SL的值,SL的值下面讲),这样返回到上一层的局部变量才可以正确根据基址寄存器+偏移量来访问
SL的作用
DL中描述了过程内部的变量访问是根据基址寄存器B+偏移量I['A']来找到在数据栈中的位置,但如果是主函数那一层定义的变量需要在子过程内部被访问呢?这个时候就是SL起作用
注:在C语言里面定义的全局变量是可以被定义的所有函数直接使用的,这里思想是一样的,外层定义的变量内部的子过程是可以使用的,但是如何在数据栈中找到外层定义的变量呢?
LOD、STO指令的第二个值说的是层差,这里的层差就是说当前过程所在层和定义层的差。举个例子,LOD, 1, 4指令是将某个变量的值放入栈顶,如何寻找这个变量呢,本层的活动记录SL的值记录的就是定义该变量在数据栈所在层的基址,基址+偏移量4就获取该变量在数据栈的位置了。那么SL的值如何求呢,指令中的1表示层差是1,也就是该变量是在本层的上一层定义的,gcd过程使用的m变量就是定义在外层的,所以SL的值就是上一层的活动记录的起始地址,也就是上一层SL在数据栈中的位置
总结
RA是记录当前过程结束后执行的下一条指令的位置
DL是当前过程结束后返回到上一层后恢复当前活动记录的基址寄存器B,因为本层会先占据寄存器B的值,等结束后再恢复回去
SL是为了本层使用了外层定义的变量,需要知道外层的基址,这样才能根据基址+偏移量获取到变量的值
# 这里开始进行中间代码解释执行
stack = [0 for i in range(0, 8000)] # 数据栈 前三个0是主函数的SL DL RA
# 根据当前B的值和level层差获取SL的值
def get_sl(B, level):
global stack
res_B = B
while level > 0:
res_B = stack[res_B]
level -= 1
return res_B
# 解释器
def interpreter():
# 先定义好需要用到的数据
global stack
B = 0 # 基址寄存器
T = 0 # 栈顶寄存器
I = None # 存放要执行的代码
P = 0 # 存放下一条要执行的代码在mid_code数组的下标
# 开始执行
I = mid_code[P]
P += 1
while P != 0: # P为0表示主函数结束 指令回到起点 那么就算执行结束
if I['F'] == 'JMP': # 直接跳转到对应指令
P = I['A']
elif I['F'] == 'JPC':
if stack[T] == 0: # 栈顶值为0才跳转
P = I['A']
T -= 1 # 无论是否跳转都要去除栈顶的值
elif I['F'] == 'INT':
T += I['A'] - 1 # 开辟空间
elif I['F'] == 'LOD':
T += 1
stack[T] = stack[get_sl(B, I['L']) + I['A']]
elif I['F'] == 'STO':
stack[get_sl(B, I['L']) + I['A']] = stack[T]
T -= 1
elif I['F'] == 'LIT':
T += 1
stack[T] = I['A']
elif I['F'] == 'CAL': # 函数调用
T += 1
stack[T] = get_sl(B, I['L'])
stack[T + 1] = B
stack[T + 2] = P
B = T
P = I['A']
elif I['F'] == 'OPR':
if I['A'] == 0: # 函数返回
T = B - 1
P = stack[T + 3]
B = stack[T + 2]
elif I['A'] == 1: # 取反操作
stack[T] = -stack[T]
elif I['A'] == 2: # 加法
T -= 1
stack[T] = stack[T] + stack[T + 1]
elif I['A'] == 3: # 减法
T -= 1
stack[T] = stack[T] - stack[T + 1]
elif I['A'] == 4: # 乘法
T -= 1
stack[T] = stack[T] * stack[T + 1]
elif I['A'] == 5: # 除法
T -= 1
stack[T] = int(stack[T] / stack[T + 1])
elif I['A'] == 6: # odd 奇偶
stack[T] = stack[T] % 2
elif I['A'] == 8: # ==
T -= 1
stack[T] = stack[T] == stack[T + 1]
elif I['A'] == 9: # !=
T -= 1
stack[T] = stack[T] != stack[T + 1]
elif I['A'] == 10: # <
T -= 1
stack[T] = stack[T] < stack[T + 1]
elif I['A'] == 11: # >=
T -= 1
stack[T] = stack[T] >= stack[T + 1]
elif I['A'] == 12: # >
T -= 1
stack[T] = stack[T] > stack[T + 1]
elif I['A'] == 13: # <=
T -= 1
stack[T] = stack[T] <= stack[T + 1]
I = mid_code[P] # 获取下一条指令
if P == 0:
break
P += 1 # 默认P+1获取下一条指令 除非跳转