编译自:http://www.aosabook.org/en/500L/a-python-interpreter-written-in-python.html 作者:Taavi Burns 翻译:鸿 如有翻译问题或建议,请公众号留言
玩具解释器 首先从一个玩具解释器开始,这个微型解释器只能做加法,而且值包含了三个指令,这三个指令是:
LOAD_VALUE
ADD_TWO_VALUES
PRINT_ANSWER
由于我们跳过了词法分析,语法分析和编译过程,所以我们可以使用dis模块来帮助解析,例如:
7+5
可以使用下面的语句解释:
what_to_execute = {
"instructions": [("LOAD_VALUE", 0), # the first number
("LOAD_VALUE", 1), # the second number
("ADD_TWO_VALUES", None),
("PRINT_ANSWER", None)],
"numbers": [7, 5] }
Python解释器是一个堆栈机,所以它必须操作堆栈以添加两个数字。解释器将首先执行第一条指令LOAD_VALUE,将第一个数字推入堆栈,再会将第二个数字推入堆栈。 对于第三条指令ADD_TWO_VALUES,它将推出堆栈里的两个数字,将它们加在一起将结果推入堆栈。 最后将答案从堆栈中推出并print出来。
LOAD_VALUE指令表示解释器将一个数字推入堆栈,但指令本身并未指定哪个数字,所以每条指令都需要一条额外的信息,告诉解释器在哪里找到所需要的数字。所以我们的指令集分为两部分:指令本身,以及指令需要的常量列表。现在让我们开始写解释器本身。解释器对象拥有一个列表来表示堆栈。该对象还具有描述执行每条指令的方法。例如,对于LOAD_VALUE,解释器会将值推入堆栈。
class Interpreter:
def __init__(self):
self.stack = []
def LOAD_VALUE(self, number):
self.stack.append(number)
def PRINT_ANSWER(self):
answer = self.stack.pop()
print(answer)
def ADD_TWO_VALUES(self):
first_num = self.stack.pop()
second_num = self.stack.pop()
total = first_num + second_num
self.stack.append(total)
这三个方法实现了解释器所能理解的三条指令。解释器还需要将所有方法联系在一起并实际执行。run_code方法将上面定义的what_to_execute字典作为参数,在循环每一条指令,如果某个指令存在的话就处理该指令的参数,然后在调用解释器对象相应的方法。
def run_code(self, what_to_execute):
instructions = what_to_execute["instructions"]
numbers = what_to_execute["numbers"]
for each_step in instructions:
instruction, argument = each_step
if instruction == "LOAD_VALUE":
number = numbers[argument]
self.LOAD_VALUE(number)
elif instruction == "ADD_TWO_VALUES":
self.ADD_TWO_VALUES()
elif instruction == "PRINT_ANSWER":
self.PRINT_ANSWER()
创建相应的实例进行测试:
interpreter = Interpreter()
interpreter.run_code(what_to_execute)
界面上显示:12。尽管这个解释器功能非常有限,但展现的数字相加的过程几乎就是真正的Python解释器所实现的。不过在这个小例子中,也有几件事需要注意。首先,部分指令需要参数。在真正的Python字节码中,大约一半的指令有参数。参数与指令紧密结合。但是请注意,指令的参数不同于调用方法的参数。其次,注意ADD_TWO_VALUES指令不需要任何参数。相反,要相加在一起的值是从解释器的堆栈弹出,这就是基于堆栈的解释器的明确特征。请记住,如果给定有效的指令集,而不对我们的解释器进行任何更改,则可以添加多个数字。看看下面的指令集。
what_to_execute = {
"instructions": [("LOAD_VALUE", 0),
("LOAD_VALUE", 1),
("ADD_TWO_VALUES", None),
("LOAD_VALUE", 2),
("ADD_TWO_VALUES", None),
("PRINT_ANSWER", None)],
"numbers": [7, 5, 8] }
由此可以开始看到这个结构是如何扩展的:在解释器对象上添加描述更多操作的方法(只要有一个编译器来处理格式良好的指令集)。