vm2gol v2 製作メモ(4) カウンタ / プログラムをファイルに



コンピュータのようなものを作っているので、 何か 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 方式は、簡単だし pppシリアライズできるのでこれはこれで悪くはなかったのですが)。

プログラムのファイルの内容はこう。 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

引数でのファイルの指定を変えるだけで実行するプログラムが切り替えられるようになりました。 進歩!