我们来编写 10 + 13 + 20
根据之前的指令集我们依葫芦画瓢
what_to_execute = {
"instructions": [("LOAD_CONST", 0), # the first number
("LOAD_CONST", 1), # the second number
("BINARY_ADD", None),
("LOAD_CONST", 2),
("BINARY_ADD", None),
("RETURN_VALUE", None)],
"co_consts": (10, 13, 20)}
代码不变,执行即可
给解释器增加变量的支持
需要指令,变量到值得映射关系
STORE_NAME:将栈顶元素弹出存储到变量中
LOAD_NAME:将变量压栈
目前,我们会忽略命名空间和作用域,所以我们可以把变量和值的映射直接存储在解释器对象中
def s():
… a = 1
… b = 2
… print(a + b)
what_to_execute = {
"instructions": [("LOAD_CONST", 0),
("STORE_NAME", 0),
("LOAD_CONST", 1),
("STORE_NAME", 1),
("LOAD_NAME", 0),
("LOAD_NAME", 1),
("BINARY", None),
("RETURN_VALUE", None)],
"co_consts": [1, 2],
"co_varnames": ("a", "b")
LOAD_NAME将变量压栈,我们肯定不是将真正的变量进行压栈,而是建立变量和值的映射关系,将值进行压栈,为了跟踪哪个变量指向了哪个值,我们在__init__方法增加一个字典
目前我们有两个数据集,指令的参数就有两个不同含义,所以我们就需要判断该参数应该对应哪个数据集和传递给哪个函数。Python解释器通过字节码分为操作码和操作数来辨别。我们这里就把指令和对应参数的映射关系放在一个独立的方法中
class Interpreter:
def __init__(self):
self.stack = []
self.environment = {}
def LOAD_CONST(self, number):
self.stack.append(number)
def RETURN_VALUE(self):
answer = self.stack.pop()
print(answer)
def BINARY_ADD(self):
first_number = self.stack.pop()
second_number = self.stack.pop()
result = first_number + second_number
self.stack.append(result)
def STORE_NAME(self, name):
# 将栈顶元素弹出,存储到变量中
val = self.stack.pop()
# 要建立映射,我们把映射放在一个字典里面
self.environment[name] = val
def LOAD_NAME(self, name):
# 将变量进行压栈,但我们其实是将值压栈,所以通过建立的映射拿到值
val = self.environment[name]
# 将拿到的值进行压栈
self.stack.append(val)
def parse_argument(self, instruction, argument, what_to_execute):
# 该函数是为了判断指令后面的参数该在哪个数据集中取值
co_consts = ["LOAD_CONST"]
co_varnames = ["LOAD_NAME", "STORE_NAME"]
# 如果该指令在["LOAD_CONST"]中,将指令参数传递给其指令对应的数据集取到值
if instruction in co_consts:
argument = what_to_execute["co_consts"][argument]
# 如果该指令在["LOAD_NAME", "STORE_NAME"]中,将指令参数传递给其指令对应数据集取到值
elif instruction in co_varnames:
argument = what_to_execute["co_varnames"][argument]
# 将取到的值传递出去,如果遇到不需要参数的指令,指令参数为None,我们直接返回
return argument
我们增加了STORE_NAME与LOAD_NAME方法,还增加了函数参数该如何选择的方法
现在,让我们来写把所有指令和数据结合在一起并执行的方法。
def run_code(self, par_what_to_execute):
instructions = par_what_to_execute["instructions"]
for each_step in instructions:
instruction, argument = each_step
# 调用我们写的给根据指令给出对应参数的函数,传入指令,指令参数,和指令集
argument = self.parse_argument(instruction, argument, par_what_to_execute)
if instruction == "LOAD_CONST":
self.LOAD_CONST(argument)
elif instruction == "BINARY_ADD":
self.BINARY_ADD()
elif instruction == "RETURN_VALUE":
self.RETURN_VALUE()
elif instruction == "STORE_NAME":
self.STORE_NAME(argument)
elif instruction == "LOAD_NAME":
self.LOAD_NAME(argument)
写完发现,才五个指令,run_code方法就开始变得冗长了,后续继续增加指令的话,都需要一个if分支。所以就可以利用动态类型语言的关键——反射机制
将代码改写
def run_code(self, par_what_to_execute):
instructions = par_what_to_execute["instructions"]
for each_step in instructions:
instruction, argument = each_step
argument = self.parse_argument(instruction, argument, par_what_to_execute)
# 动态的获取指令所对应的方法
bytecode_method = getattr(self, instruction)
# 根据函数是否有参数来进行参数的传递和调用
if argument is None:
bytecode_method()
else:
bytecode_method(argument)