Python写的Python解释器(二)

编译自: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] }

由此可以开始看到这个结构是如何扩展的:在解释器对象上添加描述更多操作的方法(只要有一个编译器来处理格式良好的指令集)。

原文发布于微信公众号 - 鸿的学习笔记(shujuxuexizhilu)

原文发表时间:2018-05-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Web项目聚集地

手写一个Mybatis框架

在手写自己的Mybatis框架之前,我们先来了解一下Mybatis,它的源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,才能够更深入的理解源...

1072
来自专栏技术栈大杂烩

Python: 函数与方法的区别 以及 Bound Method 和 Unbound Method

随着我们越来越频繁使用Python, 我们难免会接触到类, 接触到类属性和方法.但是很多新手包括我, 不知道方法 和 函数 的区别,这次简单来讨论下, 如果有哪...

3131
来自专栏GreenLeaves

EF 约定介绍

当前环境为EF Code First开发模式中 一、EF默认约定 1、常用约定 (1)、当没有显示指定实体主键的时候,EF会默认将长得最像Id的属性(且类型为G...

22210
来自专栏菩提树下的杨过

mybatis的物理分页:mybatis-paginator

文件分割与合并是一个常见需求,比如:上传大文件时,可以先分割成小块,传到服务器后,再进行合并。很多高大上的分布式文件系统(比如:google的GFS、taoba...

2279
来自专栏应兆康的专栏

Python Web - Flask笔记5

MySQL Workbench是一款专为MySQL设计的ER/数据库建模工具。它是著名的数据库设计工具DBDesigner4的继任者。你可以用MySQL Wor...

1781
来自专栏码农分享

SQL Server 多表数据增量获取和发布 4

最关键的在于获取捕获表信息(系统表中间_CT结尾的数据)。 根据网上资料查取,找到了获取当前捕获表时间区间范围内数据的方式。 见[SQL Server 多表...

1572
来自专栏程序员的SOD蜜

左求值表达式,堆栈,调试陷阱与ORM查询语言的设计

1,表达式的求值顺序与堆栈结构 “表达式” 是程序语言一个很重要的术语,也是大家天天写的程序中很常见的东西,但是表达式的求值顺序一定是从左到右么? C/C++语...

3276
来自专栏函数式编程语言及工具

FunDA(15)- 示范:任务并行运算 - user task parallel execution

    FunDA的并行运算施用就是对用户自定义函数的并行运算。原理上就是把一个输入流截分成多个输入流并行地输入到一个自定义函数的多个运行实例。这些函数运行实例...

1929
来自专栏码匠的流水账

聊聊kafka的partition分配

本文主要研究一下kafka的partition分配,主要是key到parition的映射,partition对consumer的分配,以及partition的r...

3401
来自专栏钟绍威的专栏

递归思想解决输出目录下的全部文件

刚刚了解了下递归思想 递归就是在方法内调用本方法 下面说一个实际的应用 输出目录下的全部文件,当目录中还有目录时,则进入目录输出里面的文件 import j...

1977

扫码关注云+社区

领取腾讯云代金券