zoukankan      html  css  js  c++  java
  • [译]Python编写虚拟解释器

    使用Python编写虚拟机解释器

    一、实验说明

    1. 环境登录

    无需密码自动登录,系统用户名shiyanlou,密码shiyanlou

    2. 环境介绍

    本实验环境采用带桌面的Ubuntu Linux环境,实验中会用到程序:

    1. LX终端(LXTerminal):Linux命令行终端,打开后会进入Bash环境,可以使用Linux命令
    2. GVim:非常好用的编辑器,最简单的用法可以参考课程Vim编辑器

    3. 环境使用

    使用R语言交互式环境输入实验所需的代码及文件,使用LX终端(LXTerminal)运行所需命令进行操作。

    完成实验后可以点击桌面上方的“实验截图”保存并分享实验结果到微博,向好友展示自己的学习进度。实验楼提供后台系统截图,可以真实有效证明您已经完成了实验。

    实验记录页面可以在“我的主页”中查看,其中含有每次实验的截图及笔记,以及每次实验的有效学习时间(指的是在实验桌面内操作的时间,如果没有操作,系统会记录为发呆时间)。这些都是您学习的真实性证明。

    二、课程介绍

    众所周知,python语言作为一门超级人性化的语言越来越被受到重视。虚拟服务同样受到人们的重视,就比如实验楼的虚拟实验环境,那么本次项目的目的就是让大家学会使用python制作一个虚拟解释器,这里的虚拟解释器指的是一定意义上的堆栈机

    感谢Christian Stigen Larsen的开源项目Crianza,那么我们就跟着他一起学习如何创建一个解释器吧!

    三、课程内容

    >解释器是能够执行用其他计算机语言编写的程序的系统软件,它是一种翻译程序。它的执行方式是一边翻译一边执行,因此其执行效率一般偏低,但是解释器的实现较为简单,而且编写源程序的高级语言可以使用更加灵活和富于表现力的语法。

    1、构建堆栈机

    堆栈机本身并不拥有寄存器,它的执行原理非常简单:将需要处理的值放入栈中,然后执行它们。尽管堆栈机的原理就是这么简单,但是不能不说它确实很强大,不然Python、Java等高级语言也不会将它作为它们的虚拟机。

    无论如何,先来深入了解一下堆栈的原理。首先,我们需要一个指令指针栈,它能够储存返回地址。这个返回地址是当我们执行一个子例程(比如函数)的时候,需要用它跳回到开始调用该函数的地方。

    那么有了这个神奇的堆栈,很多复杂难以理解的程序就变得非常简单。比如说,有这么一个数学表达式:(2+3)*4。在堆栈机中,这个数学表达式等价于2 3 + 4 * ——将'2'和'3'依次推入栈中,接下来要推入的指令是'+',将前面两个数字弹出,令他们执行加法运算后再将它们的和入栈。然后依次将'2'与'3'的和与4相乘的结果推入栈中。运算结束,so easy!

    那么,让我们开始建立一个栈,由于Python这个语言拥有类似于C语言中数据结构的一个类collections.deque,因此可以在这个类的基础上定义出属于我们的栈。

     1 from collections import deque
     2 
     3 class Stack(deque):
     4 """定义一个栈类"""
     5     # 添加元素
     6     push = deque.append
     7 
     8     # 返回最后一个元素
     9     def top(self):
    10         return self[-1]    

    那么这里面既然定义了'push'、'top'方法,为什么没有定义'pop'?因为'deque'这个类本身就拥有方法'pop',除了'pop'还有'popleft'呢,有兴趣的同学可以研究一下这个类与'list'对象的区别和联系。

    接下来,让我们建立一个虚拟机类——'Machine'。综上所述,我们需要两个栈和一段存储代码的内存空间。得益于Python的动态类型,因此我们可以往列表里面存储任何东西,但是我们不能区分列表里面的内置函数和字符串,正确的做法是将Python内置函数单独存放于一个列表,关于这个问题大家可以思考一下。在这个项目中用的是字典)方法,键值分别对应字符串和函数。另外,我们还需要一个指令指针,用来指向代码中下一个需要被执行的模块。

    1 class Machine:
    2     def __init__(self, code):
    3     """预先定义一个初始化函数"""
    4 
    5         self.data_stack = Stack()
    6         self.return_addr_stack = Stack()
    7         self.code = code
    8         self.instruction_pointer = 0        

    再创建一些栈结构中必备的函数:

    1 def pop(self):
    2     return self.data_stack.pop()
    3 
    4 def push(self, value):
    5     self.data_stack.push(value)
    6 
    7 def top(self):
    8     return self.data_stack.top()

    为了执行我们“操作码”(实际上,并不是真正意义上的操作码,只是一种动态类型,但是你懂得~)我们需要建立一个'dispatch'函数。但是在这之前,我们需要创建一个解释器的循环:

    1 def run(self):
    2 """代码运行的条件"""
    3 
    4     while self.instruction_pointer < len(self.code):
    5     opcode = self.code[self.instruction_pointer]
    6     self.instruction_pointer += 1
    7     self.dispatch(opcode)

    上面的代码原理很简单:获取下一个指令,指令指针自增1个然后基于操作码执行'dispatch'函数,下面是'dispatch'函数的定义(函数定义有点长,你们可以尝试改进一下):

     1 def dispatch(self, op):
     2     dispatch_map = {
     3         "%": self.mod,
     4         "*": self.mul,
     5         "+": self.plus,
     6         "-": self.minus,
     7         "/": self.div,
     8         "==": self.eq,
     9         "cast_int": self.cast_int,
    10         "cast_str": self.cast_str,
    11         "drop": self.drop,
    12         "dup": self.dup,
    13         "if": self.if_stmt,
    14         "jmp": self.jmp,
    15         "over": self.over,
    16         "print": self.print_,
    17         "println": self.println,
    18         "read": self.read,
    19         "stack": self.dump_stack,
    20         "swap": self.swap,
    21         }
    22 
    23     if op in dispatch_map:
    24         dispatch_map[op]()
    25     elif isinstance(op, int):
    26     # 如果指令是整型数据,就将数据存放到数据栈中
    27         self.push(op)
    28     elif isinstance(op, str) and op[0]==op[-1]=='"':
    29     # 如果是字符串类型的,就将字符串内容存放到数据栈中
    30         self.push(op[1:-1])
    31     else:
    32         raise RuntimeError("Unknown opcode: '%s'" % op)        

    上面的代码非常浅显易懂,就是当输入一段指令,该函数就会根据这段指令在'dispatch_map'字典中找到对应的方法,比如:符号'*'对应的是'self.mul'函数。以上过程就类似于'Forth'语言的构建过程。

    当输入指令'*',程序就会执行函数'self.mul',所以我们还需要定义对应的函数:

    1 def mul(self):
    2     self.push(self.pop() * self.pop())

    其他的函数定义也是依次类推,根据它们的功能和名称定义不同的函数。

    到这里,你可以定义你想要的函数了,一个虚拟机环境基本构成就是这样!

    然而并没有完,环境搭建好了,最重要的'解释'还没有完成,一个语言解释器包括两部分:
    1. 解析:解析部分接受一个由字符序列表示的输入指令,然后将输入字符分解成一系列的词法单元
    2. 执行:程序内部的解释器根据语义规则进一步处理词法单元,进而执行原指令的实际运算。

    流程如下图所示:

    下面一节中,我们将会讨论如何构建解析器。

    2、为我们的指令创建一个简单的解析器

    让我们使用'tokenize'模块为输入的指令构建一个解析器吧~

     1 import tokenize
     2 from StringIO import StringIO
     3 
     4 def parse(text):
     5 
     6     # 以StingIO的形式将text对象读入到内存中,并以字符串形式返回到                                                    generate_tokens()函数中
     7     tokens = tokenize.generate_tokens(StringIO(text).readline)
     8 
     9     # generate_tokens生成器生成一个5元祖:标记类型、标记字符串、标记开始位置二元组、标记结束位置二元组以及标记所在的行号
    10     # 下面大写的单词都属于token模块的常量
    11     for toknum, tokval, _, _, _ in tokens:
    12         if toknum == tokenize.NUMBER:
    13             yield int(tokval)
    14         elif toknum in [tokenize.OP, tokenize.STRING,         tokenize.NAME]:
    15             yield tokval
    16         elif toknum == tokenize.ENDMARKER:
    17             break
    18         else:
    19             raise RuntimeError("Unknown token %s: '%s'" %
    20     (tokenize.tok_name[toknum], tokval))                           

    更多关于Python令牌器('tokenize')的常量查看请查阅官方文档

    3、简单优化:常量折叠

    >“常量折叠”是 就是在编译器进行语法分析的时候,将常量表达式计算求值,并用求得的值来替换表达式,放入常量表。可以算作一种编译优化。

     1 def constant_fold(code):
     2 """对简单的数学表达式诸如:2 3 + 进行计算并将结果作为常数返回原指令列表中"""
     3     while True:
     4     # 在指令中找到两个连续的数字以及一个算数运算符
     5         for i, (a, b, op) in enumerate(zip(code, code[1:], code[2:])):
     6            if isinstance(a, int) and isinstance(b, int) 
     7             and op in {"+", "-", "*", "/"}:
     8                 m = Machine((a, b, op))
     9                 m.run()
    10                 code[i:i+3] = [m.top()]
    11                 print("Constant-folded %s%s%s to %s" % (a,op,b,m.top()))
    12                 break
    13          else:
    14             break
    15     return code

    这个方法唯一的缺点是我们必须得更新跳转的地址,尤其是在遇到读取或者跳转等操作时需要不断的跳转。但是任何问题都有它对应解决的方案,有一个简单的例子就是在跳转的时候只允许调到指令的命名标签上,这样的话,在执行常量折叠之后就可以跳转到它们真正的地址上。

    4、读取-求值-输出循环

    我们可以通过以下代码实现一个简单的“读取-求值-输出循环”的交互式编程环境:

     1 def repl():
     2     print('Hit CTRL+D or type "exit" to quit.')
     3 
     4     while True:
     5         try:
     6             source = raw_input("> ")
     7         code = list(parse(source))
     8         code = constant_fold(code)
     9         Machine(code).run()
    10     except (RuntimeError, IndexError) as e:
    11        print("IndexError: %s" % e)
    12     except KeyboardInterrupt:
    13         print("
    KeyboardInterrupt")

    5、课后作业

    1. 列表项试着在不查看完整源代码的情况下制作这个虚拟解释器(可以参考Python内置函数),并尝试生成'Fibonacci'序列,将运行过程和结果截图;
    2. 如果完成了第一题,恭喜你,又'get'一个技能,你可以查看下面的完整代码对比你自己的代码,把你的代码中重要的细节和你的思考写入实验报告;
    3. 那么接下来请尝试给指令中的函数添加'call'和'return'功能。提示:'call'函数是先将当前地址返回到栈中,再调用'self.jmp'函数。'return'函数显然是先将栈中的地址弹出,根据该地址设置一个指令指针从'call'函数中返回到原来开始被调用的地方。

    6、完整代码

    代码同样可以通过github获取:

      1 #!/usr/bin/env python
      2 # coding: utf-8
      3 
      4 """
      5 A simple VM interpreter.
      6 
      7 Code from the post at http://csl.name/post/vm/
      8 This version should work on both Python 2 and 3.
      9 """
     10 
     11 from __future__ import print_function
     12 from collections import deque
     13 from io import StringIO
     14 import sys
     15 import tokenize
     16 
     17 
     18 def get_input(*args, **kw):
     19 """Read a string from standard input."""
     20     if sys.version[0] == "2":
     21         return raw_input(*args, **kw)
     22     else:
     23         return input(*args, **kw)
     24 
     25 
     26 class Stack(deque):
     27     push = deque.append
     28 
     29     def top(self):
     30         return self[-1]
     31 
     32 
     33 class Machine:
     34     def __init__(self, code):
     35         self.data_stack = Stack()
     36         self.return_stack = Stack()
     37         self.instruction_pointer = 0
     38         self.code = code
     39 
     40     def pop(self):
     41         return self.data_stack.pop()
     42 
     43     def push(self, value):
     44         self.data_stack.push(value)
     45 
     46     def top(self):
     47         return self.data_stack.top()
     48 
     49     def run(self):
     50         while self.instruction_pointer < len(self.code):
     51             opcode = self.code[self.instruction_pointer]
     52             self.instruction_pointer += 1
     53             self.dispatch(opcode)
     54 
     55     def dispatch(self, op):
     56         dispatch_map = {
     57             "%": self.mod,
     58             "*": self.mul,
     59             "+": self.plus,
     60             "-": self.minus,
     61             "/": self.div,
     62             "==": self.eq,
     63             "cast_int": self.cast_int,
     64             "cast_str": self.cast_str,
     65             "drop": self.drop,
     66             "dup": self.dup,
     67             "exit": self.exit,
     68             "if": self.if_stmt,
     69             "jmp": self.jmp,
     70             "over": self.over,
     71             "print": self.print,
     72             "println": self.println,
     73             "read": self.read,
     74             "stack": self.dump_stack,
     75             "swap": self.swap,
     76         }
     77 
     78         if op in dispatch_map:
     79             dispatch_map[op]()
     80         elif isinstance(op, int):
     81             self.push(op) # push numbers on stack
     82         elif isinstance(op, str) and op[0]==op[-1]=='"':
     83         self.push(op[1:-1]) # push quoted strings on stack
     84         else:
     85             raise RuntimeError("Unknown opcode: '%s'" % op)
     86 
     87         # OPERATIONS FOLLOW:
     88 
     89     def plus(self):
     90         self.push(self.pop() + self.pop())
     91 
     92     def exit(self):
     93         sys.exit(0)
     94 
     95      def minus(self):
     96         last = self.pop()
     97         self.push(self.pop() - last)
     98 
     99     def mul(self):
    100         self.push(self.pop() * self.pop())
    101 
    102     def div(self):
    103         last = self.pop()
    104         self.push(self.pop() / last)
    105 
    106     def mod(self):
    107         last = self.pop()
    108         self.push(self.pop() % last)
    109 
    110     def dup(self):
    111         self.push(self.top())
    112 
    113     def over(self):
    114         b = self.pop()
    115         a = self.pop()
    116         self.push(a)
    117         self.push(b)
    118         self.push(a)
    119 
    120     def drop(self):
    121         self.pop()
    122 
    123     def swap(self):
    124         b = self.pop()
    125         a = self.pop()
    126         self.push(b)
    127         self.push(a)
    128 
    129     def print(self):
    130         sys.stdout.write(str(self.pop()))
    131         sys.stdout.flush()
    132 
    133     def println(self):
    134         sys.stdout.write("%s
    " % self.pop())
    135         sys.stdout.flush()
    136 
    137     def read(self):
    138         self.push(get_input())
    139 
    140     def cast_int(self):
    141         self.push(int(self.pop()))
    142     
    143     def cast_str(self):
    144         self.push(str(self.pop()))
    145 
    146     def eq(self):
    147         self.push(self.pop() == self.pop())
    148 
    149     def if_stmt(self):
    150         false_clause = self.pop()
    151         true_clause = self.pop()
    152         test = self.pop()
    153         self.push(true_clause if test else false_clause)
    154 
    155     def jmp(self):
    156         addr = self.pop()
    157         if isinstance(addr, int) and 0 <= addr < len(self.code):
    158             self.instruction_pointer = addr
    159         else:
    160             raise RuntimeError("JMP address must be a valid integer.")
    161 
    162     def dump_stack(self):
    163         print("Data stack (top first):")
    164 
    165         for v in reversed(self.data_stack):
    166             print(" - type %s, value '%s'" % (type(v), v))
    167 
    168 
    169 def parse(text):
    170     # Note that the tokenizer module is intended for parsing Python source
    171     # code, so if you're going to expand on the parser, you may have to use
    172     # another tokenizer.
    173 
    174     if sys.version[0] == "2":
    175         stream = StringIO(unicode(text))
    176     else:
    177         stream = StringIO(text)
    178 
    179         tokens = tokenize.generate_tokens(stream.readline)
    180 
    181         for toknum, tokval, _, _, _ in tokens:
    182             if toknum == tokenize.NUMBER:
    183                 yield int(tokval)
    184             elif toknum in [tokenize.OP, tokenize.STRING, tokenize.NAME]:
    185                 yield tokval
    186             elif toknum == tokenize.ENDMARKER:
    187                 break
    188             else:
    189                 raise RuntimeError("Unknown token %s: '%s'" %
    190 (tokenize.tok_name[toknum], tokval))
    191 
    192 def constant_fold(code):
    193 """Constant-folds simple mathematical expressions like 2 3 + to 5."""
    194     while True:
    195     # Find two consecutive numbers and an arithmetic operator
    196         for i, (a, b, op) in enumerate(zip(code, code[1:], code[2:])):
    197             if isinstance(a, int) and isinstance(b, int) 
    198 and op in {"+", "-", "*", "/"}:
    199                 m = Machine((a, b, op))
    200                 m.run()
    201                 code[i:i+3] = [m.top()]
    202                 print("Constant-folded %s%s%s to %s" % (a,op,b,m.top()))
    203                 break
    204         else:
    205             break
    206     return code
    207 
    208 def repl():
    209     print('Hit CTRL+D or type "exit" to quit.')
    210 
    211     while True:
    212         try:
    213             source = get_input("> ")
    214             code = list(parse(source))
    215             code = constant_fold(code)
    216             Machine(code).run()
    217         except (RuntimeError, IndexError) as e:
    218             print("IndexError: %s" % e)
    219         except KeyboardInterrupt:
    220             print("
    KeyboardInterrupt")
    221 
    222 def test(code = [2, 3, "+", 5, "*", "println"]):
    223         print("Code before optimization: %s" % str(code))
    224     optimized = constant_fold(code)
    225     print("Code after optimization: %s" % str(optimized))
    226 
    227     print("Stack after running original program:")
    228     a = Machine(code)
    229     a.run()
    230     a.dump_stack()
    231 
    232     print("Stack after running optimized program:")
    233     b = Machine(optimized)
    234     b.run()
    235     b.dump_stack()
    236 
    237     result = a.data_stack == b.data_stack
    238     print("Result: %s" % ("OK" if result else "FAIL"))
    239     return result
    240 
    241     def examples():
    242     print("** Program 1: Runs the code for `print((2+3)*4)`")
    243     Machine([2, 3, "+", 4, "*", "println"]).run()
    244 
    245     print("
    ** Program 2: Ask for numbers, computes sum and product.")
    246     Machine([
    247         '"Enter a number: "', "print", "read", "cast_int",
    248         '"Enter another number: "', "print", "read", "cast_int",
    249         "over", "over",
    250         '"Their sum is: "', "print", "+", "println",
    251         '"Their product is: "', "print", "*", "println"
    252         ]).run()
    253 
    254     print("
    ** Program 3: Shows branching and looping (use CTRL+D to exit).")
    255     Machine([
    256         '"Enter a number: "', "print", "read", "cast_int",
    257         '"The number "', "print", "dup", "print", '" is "', "print",
    258         2, "%", 0, "==", '"even."', '"odd."', "if", "println",
    259         0, "jmp" # loop forever!
    260         ]).run()
    261 
    262 
    263 if __name__ == "__main__":
    264     try:
    265         if len(sys.argv) > 1:
    266             cmd = sys.argv[1]
    267             if cmd == "repl":
    268                 repl()
    269             elif cmd == "test":
    270                 test()
    271                 examples()
    272             else:
    273                 print("Commands: repl, test")
    274         else:
    275             repl()
    276     except EOFError:
    277         print("")                                        
  • 相关阅读:
    遍历一个枚举类型
    ASP.NET:C#中时间格式的转换
    DataAdapter去批量更新数据的FAQ
    .Net/C#: 实现支持断点续传多线程下载的 Http Web 客户端工具类 (第2版) (C# DIY HttpWebClient) 收藏
    如何使数据库中取出的数据保持原有格式
    如何获取控制台应用程序自己的文件名
    2008将倒掉一大部分的工厂和贸易公司
    组六对半分组组合投资方案(36789)
    重又归孑然一身
    善于总结
  • 原文地址:https://www.cnblogs.com/wing1995/p/4683555.html
Copyright © 2011-2022 走看看