Arantium Maestum

プログラミング、囲碁、読書の話題

PythonでForth処理系を書く! その4(内部状態をPython Dictionaryに保存)

前回の終わりで書いたように、Forth処理系の内部状態、とくにコンパイラ部分とインタプリタ部分のものをデータ構造として抜き出して、保存・出力できるようにしたい。

その変更がこれ:

github.com

主要な変更は二点:

  1. 内部状態をPythonのDictionaryで表し、compileやinterpretにそのState dictionaryを渡している
  2. コンパイラインタプリタのどちらにも存在したif-elif-elseの分岐処理を、個別にState dictionaryを受け取ってひとつの処理を行う複数の小さい関数に分割する

state dictionaryの導入

とくにREPLを実装するのに役にたつと考えての変更。

一つのプログラムを最初から最後まで一気に読み込んで処理を行う場合は、こういう状態が関数内で完結していて問題ないのだが、ユーザから一行ずつプログラムを与えられて、その都度「トークン化・コンパイル・実行」するとなると、過去の状態も含めて保持する必要がある。

その方法としては

  • オブジェクト
  • Python generator
  • グローバル変数としてのState
  • 引数・戻り値としてのイミュータブルState
  • 引数としてのミュータブルState

あたりが考えられると思う。

一番素直なのはオブジェクト化してしまって状態をそのままメンバ変数にいれてしまうことだろう。あるいはPythonだとyieldキーワードを使ったgeneratorで関数に状態を手軽に持たせることもできる。ただ、デバッグなどの関係もあって、できれば状態を簡単なデータ構造に入れて外部からアクセスできるようにしたかった。(もちろんオブジェクトでもメソッドを定義するなどして実現できるが少し手間だ・・・)

グローバル変数は宗教上の理由でアウト。イミュータブルなStateを引数・戻り値にするのは好みにかなっているのだが、Pythonのデータ構造はそもそもmutationを前提としていてイミュータブルに使うにはコピーコストが大きい。

というわけでcompileやinterpretに引数としてStateを与えて、それに対して破壊的変更を加えていく形のコードになった。

if-elif-elseの分割

Stateをデータ構造につめこんだので、一つの関数内で処理が完結する必要はなくなった。

  • 分岐で行っていた各処理を個別の関数にわけて
  • 現在位置にあるトークン・コードによってディスパッチ
  • Stateも引数として与えて変更がかかる

という流れにする。このような実装にすることで今後機能を追加していくときに「一つのでかい条件分岐にelifを追加してどんどん長くしていく」という地獄を避けられるはず。

次回は今回の変更を踏まえてインタプリタにREPL機能をつける。