- 目次ページに戻る / 前 / 次
- 前回からの差分をまとめて見る
チンタラやっててなかなか進みませんが一歩ずつやっていきます。
ループができるようになったら、次は条件分岐ですよ! プログラムといえば条件分岐!
条件分岐はどうやるかというと…… compare
と jump_eq
という命令を導入します。
こいつを使って前回のカウンタを改造して、
値が 3 になったら 0 に戻ってまた 1 ずつ増えるというカウンタを作りましょう。
BASIC 風に書くとこうでしょうか:
10 set_reg_c 1 # これは最初に1回だけ実行される 20 set_reg_b 3 # これは最初に1回だけ実行される 30 set_reg_a 0 40 add_ac # a + c の結果を a にセット 50 compare # reg_a と reg_b を比較してフラグをセットする 60 jump_eq 30 # reg_a と reg_b が等しかったら、30番地に戻って reg_a の値を 0 に戻す # そうでなければ何もせず次に進む 70 jump 40 # reg_a と reg_b が等しくない場合
機械語コードに翻訳します。
# 05_compare_jump_eq.vge.yaml [ # 0 "set_reg_c", 1, # 2 "set_reg_b", 3, # 4 "set_reg_a", 0, # 6 "add_ac", "compare", "jump_eq", 4, "jump", 6 ]
まず、レジスタが reg_a
, reg_b
の2つだけだとめんどくさくなりそうな気がしたので、
reg_c
を使うようにしてみました。
reg_a
に足すべき値である 1 を reg_c
にセットしておき、
add_ac
で reg_c
の値を reg_a
に足します。
set_reg_c
と add_ac
はまだないので追加します。
set_reg_a
、add_ab
とほとんど同じです。
--- a/vgvm.rb +++ b/vgvm.rb @@ -35,6 +35,10 @@ class Vm n = @mem[@pc + 1] @reg_b = n @pc += 2 + when "set_reg_c" + n = @mem[@pc + 1] + @reg_c = n + @pc += 2 when "add_ab" add_ab() @pc += 1
--- a/vgvm.rb +++ b/vgvm.rb @@ -42,6 +42,9 @@ class Vm when "add_ab" add_ab() @pc += 1 + when "add_ac" + add_ac() + @pc += 1 when "jump" addr = @mem[@pc + 1] @pc = addr @@ -74,6 +77,10 @@ class Vm def add_ab @reg_a = @reg_a + @reg_b end + + def add_ac + @reg_a = @reg_a + @reg_c + end end exe_file = ARGV[0]
(add_ab
も add_ac
も 1行しかないのでメソッドにしなくてよかったかもしれませんね……
まあ、いちいち直していくのも煩雑なのでこのまま進めます)
準備ができたところで今回の目玉である compare
と jump_eq
の登場です!
compare
の動作はこう:
reg_a と reg_b の値を比較し、 等しければ: ゼロフラグに 1 をセット 等しくなければ: ゼロフラグに 0 をセット
jump_eq
の動作はこう:
ゼロフラグの値が 0 だったら: 何もせず次の命令に進む(プログラムカウンタを素直に進める) 1 だったら: 引数で指定されたアドレスにジャンプする
比較とジャンプの 2ステップに別れるんですね。ふむふむー。
で、この 2つのステップを仲介しているのがゼロフラグ。 ゼロフラグもレジスタの一種で、これも今回追加します。
まずゼロフラグ。
--- a/vgvm.rb +++ b/vgvm.rb @@ -12,6 +12,9 @@ class Vm @reg_b = 0 @reg_c = 0 + # flag + @zf = 0 + @mem = [] end
compare
と jump_eq
を追加。
--- a/vgvm.rb +++ b/vgvm.rb @@ -48,9 +48,15 @@ class Vm when "add_ac" add_ac() @pc += 1 + when "compare" + compare() + @pc += 1 when "jump" addr = @mem[@pc + 1] @pc = addr + when "jump_eq" + addr = @mem[@pc + 1] + jump_eq(addr) else raise "Unknown operator (#{op})" end @@ -84,6 +90,18 @@ class Vm def add_ac @reg_a = @reg_a + @reg_c end + + def compare + @zf = (@reg_a == @reg_b) ? 1 : 0 + end + + def jump_eq(addr) + if @zf == 1 + @pc = addr + else + @pc += 2 + end + end end exe_file = ARGV[0]
ステップ数が増えて、今のままだとダンプ出力が長くなってしまいます。
ここに長々としたのを貼るのもなんなので、
dump
というメソッドを作ってダンプ出力を見やすくしました。
--- a/vgvm.rb +++ b/vgvm.rb @@ -23,6 +23,8 @@ class Vm end def start + dump() # 初期状態 + loop do # operator op = @mem[@pc] @@ -62,11 +64,19 @@ class Vm end # 1命令実行するごとにダンプしてちょっと待つ - pp self + dump() sleep 1 end end + def dump + puts "pc(%2d) | reg_a(%d) b(%d) c(%d) | zf(%d)" % [ + @pc, + @reg_a, @reg_b, @reg_c, + @zf + ] + end + def set_mem(addr, n) @mem[addr] = n end @@ -108,6 +118,5 @@ exe_file = ARGV[0] vm = Vm.new vm.load_program(exe_file) -pp vm # 初期状態 vm.start
結果です! いい感じですね!
$ ruby vgvm.rb 05_compare_jump_eq.vge.yaml pc( 0) | reg_a(0) b(0) c(0) | zf(0) pc( 2) | reg_a(0) b(0) c(1) | zf(0) pc( 4) | reg_a(0) b(3) c(1) | zf(0) pc( 6) | reg_a(0) b(3) c(1) | zf(0) pc( 7) | reg_a(1) b(3) c(1) | zf(0) pc( 8) | reg_a(1) b(3) c(1) | zf(0) pc(10) | reg_a(1) b(3) c(1) | zf(0) pc( 6) | reg_a(1) b(3) c(1) | zf(0) pc( 7) | reg_a(2) b(3) c(1) | zf(0) pc( 8) | reg_a(2) b(3) c(1) | zf(0) pc(10) | reg_a(2) b(3) c(1) | zf(0) pc( 6) | reg_a(2) b(3) c(1) | zf(0) pc( 7) | reg_a(3) b(3) c(1) | zf(0) pc( 8) | reg_a(3) b(3) c(1) | zf(1) … ゼロフラグが立った! pc( 4) | reg_a(3) b(3) c(1) | zf(1) … 4番地にジャンプしている! pc( 6) | reg_a(0) b(3) c(1) | zf(1) … reg_a が 0 にリセットされた! pc( 7) | reg_a(1) b(3) c(1) | zf(1) … ↓ 以下くりかえしている! pc( 8) | reg_a(1) b(3) c(1) | zf(0) pc(10) | reg_a(1) b(3) c(1) | zf(0) pc( 6) | reg_a(1) b(3) c(1) | zf(0) pc( 7) | reg_a(2) b(3) c(1) | zf(0) pc( 8) | reg_a(2) b(3) c(1) | zf(0) pc(10) | reg_a(2) b(3) c(1) | zf(0) pc( 6) | reg_a(2) b(3) c(1) | zf(0) pc( 7) | reg_a(3) b(3) c(1) | zf(0) pc( 8) | reg_a(3) b(3) c(1) | zf(1) pc( 4) | reg_a(3) b(3) c(1) | zf(1) pc( 6) | reg_a(0) b(3) c(1) | zf(1) pc( 7) | reg_a(1) b(3) c(1) | zf(1) (略)
同じ項目が縦に並んで、変化が見やすくなりました。