こんにちは、鈴木です。
興味本位で作り始めた Forth インタプリタの続きです。
前回はワードを定義できるようにしたので、今度は条件分岐をサポートしようと思います。
条件分岐の書き方(おさらい)
条件分岐の書き方ですが、
1 |
if 真の場合 then |
または、
1 |
if 真の場合 else 偽の場合 then |
と書くのでした。
また、条件分岐はワードの定義内(「:」から「;」までの間)でしか書けない、ということもポイントです。
例えば(スタックの一番上にある)値が負数の場合は 0 にする、というワードは次のように定義できます。
1 |
: f dup 0 < if drop 0 then ; |
if ~ then をネストすることもできるので、値の符号を求めるワード(負数なら -1、正数なら 1、0 なら 0 とするワード)は次のように定義できます。
1 |
: sign dup 0 < if -1 else dup 0 > if 1 else 0 then then ; |
前回書いたコード
前回書いたコードを再掲します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
class MyForth def initialize @stack = [] # ワード定義外で使用可能なワード. @words_in_default = { '.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) }, '.' => lambda { @stack.pop }, 'dup' => lambda { @stack.push(@stack.last) }, 'drop' => lambda { @stack.pop }, 'swap' => lambda { lhs, rhs = @stack.pop(2); @stack.push(rhs, lhs) }, 'over' => lambda { @stack.push(@stack[-2]) }, 'rot' => lambda { @stack.push(@stack.delete_at(-3)) }, 'nip' => lambda { %w(swap drop).each{|word| eval_word(word)} }, 'tuck' => lambda { %w(swap over).each{|word| eval_word(word)} }, ':' => method(:begin_word_definition), } # ワード定義内で使用可能なワード. @words_in_definition = { ';' => method(:end_word_definition), } # eval_word(word) メソッドが内部で呼び出すメソッド. @eval_word = method(:eval_word_in_default) end # ワードを評価する. def eval_word(word) @eval_word.call(word) end private # ワードを評価する (ワード定義外にいる場合). def eval_word_in_default(word) if word =~ /^-?\d+$/ @stack.push(word.to_i) elsif @words_in_default[word] @words_in_default[word].call else puts "ERROR: Unsupported word: #{word}" end end # ワードを評価する (ワード定義内にいる場合). def eval_word_in_definition(word) if @words_in_definition[word] @words_in_definition[word].call else @stack.push(word) end end # ワード定義内に入ったときの処理. def begin_word_definition @stack.push(':') @eval_word = method(:eval_word_in_definition) end # ワード定義が終わったときの処理. def end_word_definition # スタック上のワード定義を分解する. # Ex. [..., ':', 'square', 'dup', '*'] を name='square', definitions=['dup', '*'] に分解する. name, *definitions = @stack.slice!(@stack.rindex(':') .. -1).drop(1) @words_in_default[name] = lambda { definitions.each{|word| eval_word_in_default(word)} } @eval_word = method(:eval_word_in_default) end end |
前回は単純な計算しかできない状態からワード定義ができるように改良しました。
実装面では、ワードを評価するメソッド eval_word(word) 内部で、ワード定義内にいる時は eval_word_in_definition(word) を呼ぶ、ワード定義の外側にいるときは eval_word_in_default(word) を呼ぶように変更しました(どちらを呼び出すかは @eval_word に Method オブジェクトを代入しておきました)。
今回の修正方針
今回は条件分岐をサポートします。
条件分岐はワード定義内(「:」から「;」までの間)でしか書けないため、eval_word_in_definition(word) メソッドでうまいこと対応したいところです。
ということで修正版のコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
class MyForth def initialize @stack = [] # ワード定義外で使用可能なワード. @words_in_default = { '.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) }, '.' => lambda { @stack.pop }, '=' => 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) }, '>' => lambda { lhs, rhs = @stack.pop(2); @stack.push(lhs > rhs) }, '>=' => lambda { lhs, rhs = @stack.pop(2); @stack.push(lhs >= rhs) }, 'dup' => lambda { @stack.push(@stack.last) }, 'drop' => lambda { @stack.pop }, 'swap' => lambda { lhs, rhs = @stack.pop(2); @stack.push(rhs, lhs) }, 'over' => lambda { @stack.push(@stack[-2]) }, 'rot' => lambda { @stack.push(@stack.delete_at(-3)) }, 'nip' => lambda { %w(swap drop).each{|word| eval_word(word)} }, 'tuck' => lambda { %w(swap over).each{|word| eval_word(word)} }, ':' => method(:begin_word_definition), } # ワード定義内で使用可能なワード. @words_in_definition = { ';' => method(:end_word_definition), # (1) 追加. 'if' => method(:begin_if_definition), 'then' => method(:end_if_definition), } # eval_word(word) メソッドが内部で呼び出すメソッド. # (2) if-then はネストする場合があるため配列に変更 (スタックとして使う). @eval_word = [method(:eval_word_in_default)] end def eval_word(word) # (2) 変更. @eval_word.last.call(word) end private def eval_word_in_default(word) if word =~ /^-?\d+$/ @stack.push(word.to_i) elsif @words_in_default[word] @words_in_default[word].call else puts "ERROR: Unsupported word: #{word}" end end def eval_word_in_definition(word) if @words_in_definition[word] @words_in_definition[word].call else @stack.push(word) end end def begin_word_definition @stack.push(':') # (2) 変更. @eval_word.push(method(:eval_word_in_definition)) end def end_word_definition # スタック上のワード定義を分解する. # Ex. [..., ':', 'square', 'dup', '*'] を name='square', definitions=['dup', '*'] に分解する. name, *definitions = @stack.slice!(@stack.rindex(':') .. -1).drop(1) @words_in_default[name] = lambda { definitions.each{|word| eval_word_in_default(word)} } # (2) 変更. @eval_word.pop end # (3) 追加. def begin_if_definition @stack.push('if') @eval_word.push(method(:eval_word_in_definition)) end # (3) 追加. def end_if_definition # スタック上のワード定義を分解する. # Ex. [..., 'if', T1, T2, ..., 'else', F1, F2, ..., 'then'] を true_case=[T1, T2, ...], false_case=[F1, F2, ...] に分解する. definitions = @stack.slice!(@stack.rindex('if') .. -1).drop(1) true_case = definitions.take_while{|word| word != 'else'} false_case = definitions.drop_while{|word| word != 'else'}.drop_while{|word| word == 'else'} # 自身をキーとしてワードに追加する. word = lambda{ (@stack.pop ? true_case : false_case).each{|word| eval_word_in_default(word)} } @stack.push(word) @words_in_default[word] = word @eval_word.pop end end |
コメント中の (1) を見てください。条件分岐は if で始まり then で終わるので、@words_in_definition に if と then を追加しました。else は追加していませんが、それは then を見つけた段階で if から then の中を Proc オブジェクトに変換するため、@words_in_definition に追加して特別に処理する必要は無いからです。
次に (2) の部分ですが、@eval_word を配列に変更しました。というのも、if ~ else ~ then はネストする可能性があるため、単純に「then が来たら @eval_word に method(:eval_word_in_default) を代入」してしまうと正しく動かないためです。呼び出す側も「@eval_word.call(word)」から「@eval_word.last.call(word)」に変更しています。
(3) では if ~ then の部分を処理しています。begin_if_definition メソッドでは @stack に 'if' を積んでから @eval_word に method(:eval_word_in_definition) を追加しています。
end_if_definition メソッドでは、@stack から 'if' の定義を definitions に取り出し、条件が真の場合(true_case)と偽の場合(false_case)に分割しています。あとは Proc オブジェクトに変換してから @words_in_default に追加するだけです。
動かしてみる
それでは動かしてみます。
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 |
最初に出てきた、値の符号を求めるワード sign を定義してみます。
1 |
: sign dup 0 < if -1 else dup 0 > if 1 else 0 then then ; |
適当な値で試してみます。
プラスの値だと 1 になる。
1 2 |
123 sign .s [123, 1] |
0 だと 0 になる。
1 2 |
0 sign .s [0, 0] |
マイナスの値だと -1 になる。
1 2 |
-123 sign .s [-123, -1] |
きちんと動きました。
次回はループをサポートしようと思います。