シンプルな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

Source: https://habr.com/ru/post/J270797/


All Articles