シンプルなPython仮想マシンの作成

こんにちは 次に、Pythonで簡単な仮想マシンを作成する方法を説明します。 誰かがこの記事を面白いと思うことを願っています。



パーサーとコンパイラーは実装しませんが、今のところはアセンブラーのインタープリターマシンのみを作成します。



スタックマシンがあり、2つのスタックを使用します。



コード自体はコマンドのリストになりますが、これもリストです。

code = [ ['val', 2], #  2   ['val', 3], #  3   ['get', '*'], #        * ( ) ['call', 2], #          2    (  ),      ['get', 'puts'], #    ['call', 1], #  ]
      
      







車を機能として実現します。

 # ops -   # env -      def vm(ops, env={}): #    ,           class closure: # pc - ""   (    ops) # env -       def __init__(self, pc, env): self.pc, self.env = pc, env # pc -     # stack -   # fstack (frame stack) -    pc, stack, fstack = 0, [], []
      
      







メインインタープリターループを開始する前に、コード内のメソッドのインデックスを計算する必要があります。 ラベルは特別なラベルコマンドです: ['label', 'label-name']





 labels = {op[1]: index for index, op in enumerate(ops) if op[0] == 'label'}
      
      







メインループ:

  while pc < len(ops): #      ,   op, args, pc = ops[pc][0], ops[pc][1:], pc + 1 arg = args[0] if args else None #   label if op == 'label': pass #      elif op == 'val': stack.append(arg) #          elif op == 'set' or op == 'get':
      
      





ここでは、デバイス環境について少し話をする必要があります。 ここでは、それらはdictオブジェクトであり、変数の名前が保存されるキーと、キー ''(空の文字列)の+値の値には、親環境への「ポインタ」が保存されます。 必要な変数が定義された環境を見つけるには、まず現在の環境を検索し、見つからない場合は親を検索する必要があります。

  e = env while e is not None and arg not in e: e = e[''] if '' in e else None #  ''    ,       #   ,     ,       if op == 'set': (env if e is None else e)[arg] = stack.pop() #    ,       elif op == 'get': if e: stack.append(e[arg]) else: print('undefined variable %s' % arg)
      
      







仮想マシンでは、それぞれ関数から関数を転送して返すことができ、クロージャーを実装します。 最も簡単な方法。 新しい関数を作成するコマンドに出会うと、この関数は作成された環境( 変数の連想配列:value )を記憶します。 したがって、これを高水準言語(マシンのバイトコードにコンパイルされます)で記述できます。

 make-person = fn(name, age) { return fn() { //   name  age print name, age++; }; }; vasya = make-person("Vasily", 20); petya = make-person("Peter", 24); vasya(); // Vasily 20 vasya(); // Vasily 21 petya(); // Peter 24 petya(); // Peter 25
      
      







fn



チームは閉鎖の作成に関与します。 彼女はすべてを実行します。 closure



クラスのオブジェクトをスタックにプッシュします。スタックには、関数コードのアドレス(実際には、目的の関数の名前を持つラベルのアドレス)と現在の環境が表示されます。

  elif op == 'fn': stack.append(closure(labels[arg], env))
      
      





次に、関数呼び出しについて。

次の2種類の関数を使用できます。



それらはさまざまな方法で処理されます。



関数呼び出し中に、nに渡された引数の数を示します。インタープリターはスタックからn個の要素を取得し、それらの配列を作成してスタックに配置します。したがって、関数は任意の数の引数を取ることができます。 コード自体の関数は、引数の配列を解析し、対応する変数を設定する必要があります。

retコマンドは、呼び出し元の場所に制御を戻します。

  #        elif op == 'call' or op == 'tcall': #   fn = stack.pop() #   -         if args and arg: stack.append(popn(arg)) #     if isinstance(fn, closure): #       ,      if op == 'call': fstack.append((pc, env)) #     pc, env = fn.pc, {'': fn.env} #   elif hasattr(fn, '__call__'): stack.append(fn(stack.pop())) #    elif op == 'args': vals = stack.pop() if len(args) != len(vals): print 'warning: wrong arguments count: %d expected, %d given' % (len(args), len(vals)) env.update(dict(zip(args, vals))) # return elif op == 'ret': #  return    ,      if not fstack: break #     pc, env = fstack.pop()
      
      







レクサー(便宜上)とテストケースを含む完全なコード: gist.github.com/Butjok/a531316e2a32576974d2



All Articles