こんにちは、鈴木です。
Forth プログラムを書いてみて、結構楽しい、ということが分かりました。
なんとなく Forth インタプリタを書いてみたい気持ちになってきました。
まだ言語仕様の一部しか知りませんので途中で破綻する気もしますが、気にせずチャレンジします!
ということで書いてみた
あまり志を高くしすぎるとすぐに挫折しそうなので、まずは単純な計算ができるレベルを目指します。
ということで書いてみました。言語は Ruby です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class MyForth def initialize @stack = [] @words = { '.s' => lambda { p @stack }, '+' => lambda { lhs, rhs = @stack.pop(2); @stack.push(lhs + rhs) }, '-' => lambda { lhs, rhs = @stack.pop(2); @stack.push(lhs - rhs) }, '*' => lambda { lhs, rhs = @stack.pop(2); @stack.push(lhs * rhs) }, '/' => lambda { lhs, rhs = @stack.pop(2); @stack.push(lhs / rhs) }, } end def eval_word(word) if word =~ /^-?\d+$/ @stack.push(word.to_i) elsif @words[word] @words[word].call else puts "ERROR: Unsupported word: #{word}" end end end |
MyForth というクラスを作りました。
なにはともあれスタックが無いと始まらないので、コンストラクタで @stack を空の配列で初期化しています。これに push/pop することでスタックとして使います。
@words には定義済みのワードを登録しています。とりあえずスタックの状態を確認するための「.s」と四則演算だけ登録しています。@words にはワード名をキーとして処理を行う Proc オブジェクトを登録します。
eval_word(word) メソッドは渡されたワードを順番に処理します。やっていることは単純で、数値であれば @stack に push する、定義済みのワードなら処理を実行する、そうでなければエラーメッセージを表示します。
動かしてみる
それでは動かしてみます。Forth プログラムはホワイトスペースで区切られたワードの羅列でしたので、入力をホワイトスペースで区切ってから MyForth#eval_word(word) に渡す足場を作れば REPL の完成です。
1 2 3 4 5 6 7 |
forth = MyForth.new while line = gets line.split(/\s/).each do |word| forth.eval_word(word) end end |
試しに 1 から 10 の合計を求めてみます。
1 |
1 2 3 4 5 6 7 8 9 10 + + + + + + + + + .s |
きっと 55 になってくれると思います、、
1 |
[55] |
ちゃんと 55 になりました!
Forth の構文はシンプルなので、基本部分は簡単に作ることが出来ました。
まだまだ機能が足りないので、ワード定義や条件分岐くらいはできるように改良したいと思います。