PythonでForth処理系を書く! その2(実装する機能)
処理系で実装する機能を列挙してみる。
- 整数をスタックに乗せる
- スタックから二つの整数をとり、それらを足し合わせた整数をスタックに乗せる
- スタックから整数をひとつとり、その数を標準出力に表示する
- 標準出力に改行を表示する
- REPL機能
- 分岐構文 IF-ELSE-THEN
- ループ構文 DO-LOOP
- 変数定義
- 関数定義
第1段階 - 最低限の機能
まず手始めに実装するもの:
- 整数をスタックに乗せる
- スタックから二つの整数をとり、それらを足し合わせた整数をスタックに乗せる
- スタックから整数をひとつとり、その数を標準出力に表示する
- 標準出力に改行を表示する
以上で例えばこんなコードをファイルから読み出して実行できるようになる:
1 2 + . CR
処理としては
- 1をスタックに乗せる
- 2をスタックに乗せる
- 2と1をスタックから取り出し、足した結果の3をスタックに乗せる
- 3をスタックから取り出し、出力する
- 改行を出力する
という流れになる。
第2段階 - REPL
上記の機能をインタラクティブなREPLを通して使えるようにする。
具体的には、一行入力されるごとにコードをインタプリタが実行していく。スタックの状態は行をまたいで維持される。
第3段階 - 分岐構文
IF-ELSE-THEN構文を実装する。
この構文は(とくにTHENが)ちょっと独特で、このように使う:
1 IF 2 ELSE 3 THEN . CR
どういう論理かというと
IFコマンドの時点でスタックの上が0以外ならELSEまで実行してからTHENにジャンプ、0ならELSEまでジャンプしてから実行を続ける
というもの。
上記のコードは2を出力してから改行する。 一番先頭の1が0なら「3を出力してから改行」になる。
第4段階 - ループ構文
DO-LOOP構文を実装する。
この構文の要素は3つあって:
DOでスタックに乗っている二つの整数を分岐のために使う スタックから得たはじめの整数がループインデックスの始点 スタックから得た次の整数がループインデックスの終点
LOOPでループインデックスをインクリメントする ループインデックスが終点未満の場合はDO直後のコマンドまで後方ジャンプ ループインデックスが終点に到達した場合はLOOP以降のコマンドを実行
DOとLOOPの間で使われるIは現在のループインデックスの値をスタックに乗せる
こんな使い方をする:
0 11 1 DO I + LOOP . CR
上記のコードは1以上11未満の整数をループ足し合わせて55を出力して改行する。
第5段階 - 変数定義
Forthでの変数はVARIABLEで宣言、!で代入、@で参照となる。
VARIABLE X VARIABLE Y 5 X ! 10 Y ! X @ Y @ + . CR
のように使う。上記のコードを走らせると15が出力される。
まずVARIABLE X、VARIABLE YでXとYを未使用のメモリ領域に結びつける。VARIABLE宣言の後にはXやYというコードはそのメモリ領域のアドレスをスタックに乗せるコマンドになる。
5 X !では5をスタックに乗せ、Xのアドレスをスタックに乗せ、そして!でその二つの整数をとり、最初の整数をアドレスとして次の整数をそのアドレスの値として代入する。
X @でXのアドレスをスタックに乗せ、そのアドレスをとってその領域に入っている値をスタックに乗せる。
第6段階 - 関数定義
Forthでは特定の処理が紐付いたコードのことをワードと呼ぶ。関数もワードだ。
ユーザがワードを自前で定義できるようにするための構文が:と;だ。
:がワード宣言の冒頭、;がワード宣言の終了を示す。:の次のコードは新しいワード名、それ以降で;までのコードはそのワードに紐づけられる処理だ。
: ADD2 2 + ; 1 ADD2 . CR
で3が出力される。
: ADD2 2 + :でADD2というワードがその処理2 +
とともに登録される。
1 ADD2 . CRはADD2の部分で登録された処理が呼び出されるので結果としては1 2 + . CRと同じ処理となる。
その後
ここまで実装してとりあえず終わる予定。
この枠組みに四則演算やスタック上の値を操作(コピー・廃棄・上位Xを入れ替えなど)するような機能で肉付けしていくと、結構簡単に非常に表現力の高い言語処理系ができあがるようだ。こっちの実装はForthについてもっと理解が進んだら試してみたい。