コンピュータのようなものを作っているので、
何か compute させようということで、足し算をさせて、
regチカのときのループと組み合わせてカウンタを作ってみます。
reg_a
の値を 1, 2, 3, ... と増やしていくというものです。
add_ab()
というメソッドがすでにあり、
足し算した結果を reg_c
に入れるようにしてましたが、
ちょっと書き換えて、結果を reg_a
に入れることにします。
(こんな感じで、まず適当に作って、 後で調べたり考えなおしたりして適当に修正していきます。)
--- a/vgvm.rb +++ b/vgvm.rb @@ -62,7 +62,7 @@ class Vm end def add_ab - @reg_c = @reg_a + @reg_b + @reg_a = @reg_a + @reg_b end end
VM のメインループから呼び出す部分がなかったので書いておきます。
--- a/vgvm.rb +++ b/vgvm.rb @@ -32,6 +32,9 @@ class Vm n = @mem[@pc + 1] @reg_a = n @pc += 2 + when "add_ab" + add_ab() + @pc += 1 when "jump" addr = @mem[@pc + 1] @pc = addr
ループは前回の regチカと同じですね。 jump
を使うだけです。
BASIC 風に書くとこんな感じ。
10 set_reg_a 1 20 set_reg_b 1 30 add_ab 40 jump 30
このようにプログラムを書き換えましょう。 簡単なので BASIC 風に書くまでもなかったかも。
@mem = [ # 0 "set_reg_a", 1, # 2 "set_reg_b", 1, # 4 "add_ab", # 5 "jump", 4 ]
動かしてみると set_reg_b
なんて命令はないぞ、
と怒られるので適当に set_reg_b
命令を追加します。
--- a/vgvm.rb +++ b/vgvm.rb @@ -35,6 +35,10 @@ class Vm n = @mem[@pc + 1] @reg_a = n @pc += 2 + when "set_reg_b" + n = @mem[@pc + 1] + @reg_b = n + @pc += 2 when "jump" addr = @mem[@pc + 1] @pc = addr
あらためて実行すると……
$ ruby vgvm.rb #<Vm:0x0056153e64c090 @mem=["set_reg_a", 1, "set_reg_b", 1, "add_ab", "jump", 4], @pc=0, @reg_a=0, @reg_b=0, @reg_c=0> #<Vm:0x0056153e64c090 @mem=["set_reg_a", 1, "set_reg_b", 1, "add_ab", "jump", 4], @pc=2, @reg_a=1, @reg_b=0, @reg_c=0> #<Vm:0x0056153e64c090 @mem=["set_reg_a", 1, "set_reg_b", 1, "add_ab", "jump", 4], @pc=4, @reg_a=1, @reg_b=1, @reg_c=0> #<Vm:0x0056153e64c090 @mem=["set_reg_a", 1, "set_reg_b", 1, "add_ab", "jump", 4], @pc=5, @reg_a=2, @reg_b=1, @reg_c=0> #<Vm:0x0056153e64c090 @mem=["set_reg_a", 1, "set_reg_b", 1, "add_ab", "jump", 4], @pc=4, @reg_a=2, @reg_b=1, @reg_c=0> #<Vm:0x0056153e64c090 @mem=["set_reg_a", 1, "set_reg_b", 1, "add_ab", "jump", 4], @pc=5, @reg_a=3, @reg_b=1, @reg_c=0> #<Vm:0x0056153e64c090 @mem=["set_reg_a", 1, "set_reg_b", 1, "add_ab", "jump", 4], @pc=4, @reg_a=3, @reg_b=1, @reg_c=0> #<Vm:0x0056153e64c090 @mem=["set_reg_a", 1, "set_reg_b", 1, "add_ab", "jump", 4], @pc=5, @reg_a=4, @reg_b=1, @reg_c=0> #<Vm:0x0056153e64c090 @mem=["set_reg_a", 1, "set_reg_b", 1, "add_ab", "jump", 4], @pc=4, @reg_a=4, @reg_b=1, @reg_c=0> #<Vm:0x0056153e64c090 @mem=["set_reg_a", 1, "set_reg_b", 1, "add_ab", "jump", 4], @pc=5, @reg_a=5, @reg_b=1, @reg_c=0> (略)
いいですね!
プログラムカウンタが 4 と 5 を行ったり来たりして、reg_a
の値が1ずつ増えています。
(ずっと動き続けるので、飽きたら Ctrl-C で止めましょう)
プログラムを外部に切り出す
さて、ここまではプログラムをこのようにメモリに直書きしていましたが、
@mem = [ # 0 "set_reg_a", 1, # 2 "set_reg_b", 1, # ... ]
そうするとプログラムの切り替えが面倒なので ファイルに切り出すことにします。
最初は Ruby のソースとしてそのままファイルに出して eval を使ってこうしていたのですが、
# プログラムファイル [ # 0 "set_reg_a", 1, # 2 "set_reg_b", 1, # ... ] # プログラムファイルの読み込み @mem = eval(File.read("program.rb"))
いろいろあって YAML にしたのでここではいきなりそのようにします
(eval 方式は、簡単だし p
や pp
でシリアライズできるのでこれはこれで悪くはなかったのですが)。
プログラムのファイルの内容はこう。
vm2gol 用の実行可能(executable)ファイルということで、拡張子を .vge.yaml
としました。
# 04_counter.vge.yaml [ # 0 "set_reg_a", 1, # 2 "set_reg_b", 1, # 4 "add_ab", # 5 "jump", 4 ]
ファイルからロードするように VM を修正します。
--- a/vgvm.rb +++ b/vgvm.rb @@ -1,5 +1,6 @@ # coding: utf-8 require 'pp' +require 'yaml' class Vm def initialize @@ -11,16 +12,11 @@ class Vm @reg_b = 0 @reg_c = 0 - @mem = [ - # 0 - "set_reg_a", 1, - # 2 - "set_reg_b", 1, - # 4 - "add_ab", - # 5 - "jump", 4 - ] + @mem = [] + end + + def load_program(path) + @mem = YAML.load_file(path) end def start @@ -76,7 +72,10 @@ class Vm end end +exe_file = ARGV[0] + vm = Vm.new +vm.load_program(exe_file) pp vm # 初期状態 vm.start
実行するときは、プログラムファイルを引数で渡してこうします。
ruby vm.rb 04_counter.vge.yaml
引数でのファイルの指定を変えるだけで実行するプログラムが切り替えられるようになりました。 進歩!