PythonでForth処理系を書く! その4(内部状態をPython Dictionaryに保存)
前回の終わりで書いたように、Forth処理系の内部状態、とくにコンパイラ部分とインタプリタ部分のものをデータ構造として抜き出して、保存・出力できるようにしたい。
その変更がこれ:
主要な変更は二点:
- 内部状態をPythonのDictionaryで表し、compileやinterpretにそのState dictionaryを渡している
- コンパイラ・インタプリタのどちらにも存在したif-elif-elseの分岐処理を、個別にState dictionaryを受け取ってひとつの処理を行う複数の小さい関数に分割する
state dictionaryの導入
とくにREPLを実装するのに役にたつと考えての変更。
一つのプログラムを最初から最後まで一気に読み込んで処理を行う場合は、こういう状態が関数内で完結していて問題ないのだが、ユーザから一行ずつプログラムを与えられて、その都度「トークン化・コンパイル・実行」するとなると、過去の状態も含めて保持する必要がある。
その方法としては
あたりが考えられると思う。
一番素直なのはオブジェクト化してしまって状態をそのままメンバ変数にいれてしまうことだろう。あるいはPythonだとyieldキーワードを使ったgeneratorで関数に状態を手軽に持たせることもできる。ただ、デバッグなどの関係もあって、できれば状態を簡単なデータ構造に入れて外部からアクセスできるようにしたかった。(もちろんオブジェクトでもメソッドを定義するなどして実現できるが少し手間だ・・・)
グローバル変数は宗教上の理由でアウト。イミュータブルなStateを引数・戻り値にするのは好みにかなっているのだが、Pythonのデータ構造はそもそもmutationを前提としていてイミュータブルに使うにはコピーコストが大きい。
というわけでcompileやinterpretに引数としてStateを与えて、それに対して破壊的変更を加えていく形のコードになった。
if-elif-elseの分割
Stateをデータ構造につめこんだので、一つの関数内で処理が完結する必要はなくなった。
- 分岐で行っていた各処理を個別の関数にわけて
- 現在位置にあるトークン・コードによってディスパッチ
- Stateも引数として与えて変更がかかる
という流れにする。このような実装にすることで今後機能を追加していくときに「一つのでかい条件分岐にelifを追加してどんどん長くしていく」という地獄を避けられるはず。
次回は今回の変更を踏まえてインタプリタにREPL機能をつける。