- 目次ページに戻る / 前 / 次
- 前回からの差分をまとめて見る
なんだかライフゲーム詐欺みたいになってきましたが 今度こそほんとにライフゲームに突入できる……はず……。
というか突入しましょう!
やるぞぉ……!
はい、やります。
vgtコードです。 最終的にはグライダーを動かしたいのですが、 まずは確認が簡単そうなブリンカーから始めましょうか。
// 27_blinker.vgt.json ["stmts" , ["func", "vram_set", ["w", "x", "y", "val"] , [ ["var", "yw"] , ["set", "yw", ["*", "y", "w"]] , ["var", "vi"] // vram index , ["set", "vi", ["+", "yw", "x"]] , ["set", "vram[vi]", "val"] ] ] , ["func", "main", [] , [ ["var", "w"] // 盤面の幅 , ["set", "w", 5] // VRAM の初期化(ブリンカー) , ["call", "vram_set", "w", 2, 1, 1] , ["call", "vram_set", "w", 2, 2, 1] , ["call", "vram_set", "w", 2, 3, 1] ] ] ]
まずは初期化の部分ということで、
main
関数とは別に、座標を指定して状態をセットする vram_set
関数を用意し、
座標
(2, 1)
(2, 2)
(2, 3)
の 3つのセルを生存状態にします。
VM の初期化時に VRAM の内容を 0 で初期化しているので、
これ以外のセルはすべて死亡状態です。
こういう状態に初期化したい: ..... ..@.. ..@.. ..@.. .....
さて、適当に書きましたが、とりあえず動かしてみましょうか。 どうなるでしょうか?
$ ./run.sh 27_blinker.vgt.json vgcg.rb:106:in `codegen_exp': Not yet implemented ("left") ("y") (RuntimeError) from vgcg.rb:154:in `codegen_set' from vgcg.rb:221:in `block in codegen_func_def' from vgcg.rb:196:in `each' from vgcg.rb:196:in `codegen_func_def' from vgcg.rb:251:in `block in codegen_stmts' from vgcg.rb:247:in `each' from vgcg.rb:247:in `codegen_stmts' from vgcg.rb:270:in `codegen' from vgcg.rb:282:in `<main>'
vgcg.rb
の該当箇所を見てみると下記のようになっています。
def codegen_exp(lvar_names, exp) # ... left = case args[0] when Integer args[0] when String case when lvar_names.include?(args[0]) lvar_pos = lvar_names.index(args[0]) + 1 "[bp-#{lvar_pos}]" else raise not_yet_impl("left", args[0]) end else # ...
ローカル変数には対応済ですが、関数の引数には未対応だったようです。 対応しましょう。
--- a/vgcg.rb +++ b/vgcg.rb @@ -65,7 +65,7 @@ def codegen_while(fn_arg_names, lvar_names, rest) alines << "label while_#{label_id}" # 条件の評価 ... 結果が reg_a に入る - alines += codegen_exp(lvar_names, cond_exp) + alines += codegen_exp(fn_arg_names, lvar_names, cond_exp) # 比較対象の値(真)をセット alines << " set_reg_b 1" alines << " compare" @@ -89,7 +89,7 @@ def codegen_while(fn_arg_names, lvar_names, rest) alines end -def codegen_exp(lvar_names, exp) +def codegen_exp(fn_arg_names, lvar_names, exp) alines = [] operator, *args = exp @@ -102,6 +102,9 @@ def codegen_exp(lvar_names, exp) when lvar_names.include?(args[0]) lvar_pos = lvar_names.index(args[0]) + 1 "[bp-#{lvar_pos}]" + when fn_arg_names.include?(args[0]) + fn_arg_pos = fn_arg_names.index(args[0]) + 2 + "[bp+#{fn_arg_pos}]" else raise not_yet_impl("left", args[0]) end @@ -151,7 +154,7 @@ def codegen_set(fn_arg_names, lvar_names, rest) rest[1] when rest[1].is_a?(Array) exp = rest[1] - alines += codegen_exp(lvar_names, exp) + alines += codegen_exp(fn_arg_names, lvar_names, exp) "reg_a" when fn_arg_names.include?(rest[1]) fn_arg_pos = fn_arg_names.index(rest[1]) + 2 @@ -220,7 +223,7 @@ def codegen_func_def(rest) when "set" alines += codegen_set(fn_arg_names, lvar_names, stmt_rest) when "eq" - alines += codegen_exp(lvar_names, stmt) + alines += codegen_exp(fn_arg_names, lvar_names, stmt) when "return" val = stmt_rest[0] alines << " set_reg_a #{val}"
再度実行。
$ ./run.sh 27_blinker.vgt.json vgcg.rb:141:in `codegen_exp': Not yet implemented ("operator") ("*") (RuntimeError) from vgcg.rb:157:in `codegen_set' from vgcg.rb:224:in `block in codegen_func_def' from vgcg.rb:199:in `each' from vgcg.rb:199:in `codegen_func_def' from vgcg.rb:254:in `block in codegen_stmts' from vgcg.rb:250:in `each' from vgcg.rb:250:in `codegen_stmts' from vgcg.rb:273:in `codegen' from vgcg.rb:285:in `<main>'
おっと、掛け算がまだなかったですね。 足し算と同じ感じで追加しましょう。
まず VM に命令を追加してから、それに合わせてコード生成器を修正する、 という順番が正攻法な気がしますが、 そんなに難しいことやってないのでコード生成器から先にやってしまっても大丈夫でしょう。
エラーメッセージにしたがってコード生成器を掛け算に対応させます。
--- a/vgcg.rb +++ b/vgcg.rb @@ -119,6 +119,10 @@ def codegen_exp(fn_arg_names, lvar_names, exp) alines << " set_reg_a #{left}" alines << " set_reg_b #{right}" alines << " add_ab" + when "*" + alines << " set_reg_a #{left}" + alines << " set_reg_b #{right}" + alines << " mult_ab" when "eq" $label_id += 1 label_id = $label_id
足し算とほぼ同じですね。
reg_a
、 reg_b
に値をセットし、
mult_ab
命令を実行すると結果が reg_a
に入るようにします。
"mult" は "multiply" の略です。
実行するとこんどは VM に mult_ab
命令などない!
と怒られるので、 命令を追加します。
--- a/vgvm.rb +++ b/vgvm.rb @@ -173,6 +173,9 @@ class Vm when "add_ac" add_ac() @pc += pc_delta + when "mult_ab" + mult_ab() + @pc += pc_delta when "add_sp" set_sp(@sp + @mem.main[@pc + 1]) @pc += pc_delta @@ -294,7 +297,7 @@ class Vm 2 when "set_reg_a", "set_reg_b", "label", "call", "push", "pop", "add_sp", "sub_sp", "jump_eq", "jump" 1 - when "ret", "exit", "add_ab", "compare" + when "ret", "exit", "add_ab", "compare", "mult_ab" 0 else raise "Invalid operator (#{operator})" @@ -346,6 +349,10 @@ class Vm @reg_a = @reg_a + @reg_c end + def mult_ab + @reg_a = @reg_a * @reg_b + end + def set_reg_a(val) @reg_a = case val
こちらも足し算のコードをコピペしてちょこっと変えるだけですね。 動かしてみます。
$ ./run.sh 27_blinker.vgt.json (略) ================================ reg_a(0) reg_b(0) reg_c(0) zf(0) ---- memory (main) ---- 00 ["call", 41] 02 ["exit"] 03 ["label", "vram_set"] 05 ["push", "bp"] 07 ["cp", "sp", "bp"] 10 ["sub_sp", 1] 12 ["set_reg_a", "[bp+4]"] 14 ["set_reg_b", "w"] 16 ["mult_ab"] 17 ["cp", "reg_a", "[bp-1]"] 20 ["sub_sp", 1] 22 ["set_reg_a", "[bp-1]"] 24 ["set_reg_b", "x"] 26 ["add_ab"] 27 ["cp", "reg_a", "[bp-2]"] 30 ["set_vram", "vi", "[bp+5]"] 33 ["cp", "bp", "sp"] 36 ["pop", "bp"] 38 ["ret"] 39 ["label", "main"] 41 ["push", "bp"] 43 ["cp", "sp", "bp"] 46 ["sub_sp", 1] 48 ["cp", 5, "[bp-1]"] 51 ["push", 1] 53 ["push", 1] 55 ["push", 2] pc => 57 ["push", "w"] 59 ["call", 5] 61 ["add_sp", 4] 63 ["push", 1] 65 ["push", 2] 67 ["push", 2] 69 ["push", "w"] 71 ["call", 5] 73 ["add_sp", 4] 75 ["push", 1] 77 ["push", 3] 79 ["push", 2] 81 ["push", "w"] 83 ["call", 5] 85 ["add_sp", 4] 87 ["cp", "bp", "sp"] 90 ["pop", "bp"] 92 ["ret"] ---- memory (stack) ---- 35 0 36 0 37 0 38 0 39 0 40 0 41 0 42 0 sp => 43 2 44 1 45 1 46 5 bp => 47 49 48 2 49 0 ---- memory (vram) ---- ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... vgvm.rb:214:in `block in start': Not yet implemented ("push") ("w") (RuntimeError) from vgvm.rb:142:in `loop' from vgvm.rb:142:in `start' from vgvm.rb:389:in `<main>'
機械語コードに w
が出てくるのはおかしいので、
コード生成器がおかしいはず。
w
の参照が解決できてないのでは。
39 ["label", "main"] 41 ["push", "bp"] 43 ["cp", "sp", "bp"] 46 ["sub_sp", 1] 48 ["cp", 5, "[bp-1]"] 51 ["push", 1] 53 ["push", 1] 55 ["push", 2] pc => 57 ["push", "w"] 59 ["call", 5]
main()
が始まって、 vram_set()
の呼び出しの前に引数を
スタックに push するとこがおかしいようだ、と当たりを付けて調べてみると……。
ここですね。 やはり、参照の解決をしていないようです。
def codegen_func_def(rest) # ... case stmt_head when "call" fn_name, *fn_args = stmt_rest fn_args.reverse.each {|fn_arg| alines << " push #{fn_arg}" # ←ここ } alines << " call #{fn_name}" alines << " add_sp #{fn_args.size}" when "call_set"
修正します。
--- a/vgcg.rb +++ b/vgcg.rb @@ -206,7 +206,20 @@ def codegen_func_def(rest) when "call" fn_name, *fn_args = stmt_rest fn_args.reverse.each {|fn_arg| - alines << " push #{fn_arg}" + case fn_arg + when Integer + alines << " push #{fn_arg}" + when String + case + when lvar_names.include?(fn_arg) + lvar_pos = lvar_names.index(fn_arg) + 1 + alines << " push [bp-#{lvar_pos}]" + else + raise not_yet_impl(fn_arg) + end + else + raise not_yet_impl(fn_arg) + end } alines << " call #{fn_name}" alines << " add_sp #{fn_args.size}"
動かします。
$ ./run.sh 27_blinker.vgt.json (略) ================================ reg_a(0) reg_b(0) reg_c(0) zf(0) ---- memory (main) ---- 00 ["call", 41] 02 ["exit"] 03 ["label", "vram_set"] 05 ["push", "bp"] 07 ["cp", "sp", "bp"] 10 ["sub_sp", 1] 12 ["set_reg_a", "[bp+4]"] 14 ["set_reg_b", "w"] 16 ["mult_ab"] 17 ["cp", "reg_a", "[bp-1]"] 20 ["sub_sp", 1] 22 ["set_reg_a", "[bp-1]"] 24 ["set_reg_b", "x"] 26 ["add_ab"] 27 ["cp", "reg_a", "[bp-2]"] 30 ["set_vram", "vi", "[bp+5]"] 33 ["cp", "bp", "sp"] 36 ["pop", "bp"] 38 ["ret"] 39 ["label", "main"] 41 ["push", "bp"] 43 ["cp", "sp", "bp"] 46 ["sub_sp", 1] 48 ["cp", 5, "[bp-1]"] 51 ["push", 1] 53 ["push", 1] 55 ["push", 2] pc => 57 ["push", "[bp-1]"] 59 ["call", 5] 61 ["add_sp", 4] 63 ["push", 1] 65 ["push", 2] 67 ["push", 2] 69 ["push", "[bp-1]"] 71 ["call", 5] 73 ["add_sp", 4] 75 ["push", 1] 77 ["push", 3] 79 ["push", 2] 81 ["push", "[bp-1]"] 83 ["call", 5] 85 ["add_sp", 4] 87 ["cp", "bp", "sp"] 90 ["pop", "bp"] 92 ["ret"] ---- memory (stack) ---- 35 0 36 0 37 0 38 0 39 0 40 0 41 0 42 0 sp => 43 2 44 1 45 1 46 5 bp => 47 49 48 2 49 0 ---- memory (vram) ---- ..... ..... ..... ..... ..... ..... ..... ..... ..... ..... vgvm.rb:214:in `block in start': Not yet implemented ("push") ("[bp-1]") (RuntimeError) from vgvm.rb:142:in `loop' from vgvm.rb:142:in `start' from vgvm.rb:389:in `<main>'
たぶんこれも push
命令で参照の解決が未実装ですねー。
今までサボッていたツケが回ってきた、と見ることもできますが、 YAGNI でやってるので今が実装のタイミングなのだ、と見ることもできるので、 大丈夫だと思います。 たぶん。
--- a/vgvm.rb +++ b/vgvm.rb @@ -208,8 +208,16 @@ class Vm case arg when Integer arg - when "bp" - @bp + when String + case arg + when "bp" + @bp + when /^\[bp\-(\d+)\]$/ + stack_addr = @bp - $1.to_i + @mem.stack[stack_addr] + else + raise not_yet_impl("push", arg) + end else raise not_yet_impl("push", arg) end
お次はこう:
vgvm.rb:373:in `set_reg_a': Not yet implemented ("val") ("[bp+4]") (RuntimeError) from vgvm.rb:154:in `block in start' from vgvm.rb:142:in `loop' from vgvm.rb:142:in `start' from vgvm.rb:397:in `<main>'
修正しまーす。
# Vm#set_reg_a() --- a/vgvm.rb +++ b/vgvm.rb @@ -369,6 +369,9 @@ class Vm when /^\[bp-(\d+)\]$/ stack_addr = @bp - $1.to_i @mem.stack[stack_addr] + when /^\[bp\+(\d+)\]$/ + stack_addr = @bp + $1.to_i + @mem.stack[stack_addr] else raise not_yet_impl("val", val) end
またこれ:
03 ["label", "vram_set"] 05 ["push", "bp"] 07 ["cp", "sp", "bp"] 10 ["sub_sp", 1] 12 ["set_reg_a", "[bp+4]"] 14 ["set_reg_b", "w"] ← まだ "w" が残ってる pc => 16 ["mult_ab"] vgvm.rb:361:in `*': String can't be coerced into Fixnum (TypeError) from vgvm.rb:361:in `mult_ab' from vgvm.rb:177:in `block in start' from vgvm.rb:142:in `loop' from vgvm.rb:142:in `start' from vgvm.rb:400:in `<main>'
次から次に出てきますね……ガンガンやっつけていきましょう。
codegen_exp()
の右項で参照の解決が未実装でした。
--- a/vgcg.rb +++ b/vgcg.rb @@ -112,7 +112,21 @@ def codegen_exp(fn_arg_names, lvar_names, exp) raise not_yet_impl("left", args[0]) end - right = args[1] + right = + case args[1] + when Integer + args[1] + when String + case + when fn_arg_names.include?(args[1]) + fn_arg_pos = fn_arg_names.index(args[1]) + 2 + "[bp+#{fn_arg_pos}]" + else + raise not_yet_impl("right", args[1]) + end + else + raise not_yet_impl("right", args[1]) + end case operator when "+"
またなんか似た感じの:
================================ reg_a(1) reg_b("[bp+2]") reg_c(0) zf(0) ---- memory (main) ---- 00 ["call", 41] 02 ["exit"] 03 ["label", "vram_set"] 05 ["push", "bp"] 07 ["cp", "sp", "bp"] 10 ["sub_sp", 1] 12 ["set_reg_a", "[bp+4]"] 14 ["set_reg_b", "[bp+2]"] pc => 16 ["mult_ab"] vgvm.rb:361:in `*': String can't be coerced into Fixnum (TypeError) from vgvm.rb:361:in `mult_ab' from vgvm.rb:177:in `block in start' from vgvm.rb:142:in `loop' from vgvm.rb:142:in `start' from vgvm.rb:400:in `<main>'
w
が [bp+2]
になりましたが、
こんどは reg_b
に [bp+2]
という文字列がそのまま入っていてダメです。
見てみるとこうなっていて、
def start # ... case op # ... when "set_reg_a" val = @mem.main[@pc + 1] set_reg_a(val) @pc += pc_delta when "set_reg_b" n = @mem.main[@pc + 1] @reg_b = n @pc += pc_delta # ... def set_reg_a(val) @reg_a = case val when Integer val when /^\[bp-(\d+)\]$/ stack_addr = @bp - $1.to_i @mem.stack[stack_addr] when /^\[bp\+(\d+)\]$/ stack_addr = @bp + $1.to_i @mem.stack[stack_addr] else raise not_yet_impl("val", val) end end
set_reg_a
命令では対応済なので、
set_reg_b
でも同じようにすればいいでしょう。
--- a/vgvm.rb +++ b/vgvm.rb @@ -154,8 +154,8 @@ class Vm set_reg_a(val) @pc += pc_delta when "set_reg_b" - n = @mem.main[@pc + 1] - @reg_b = n + val = @mem.main[@pc + 1] + set_reg_b(val) @pc += pc_delta when "set_reg_c" n = @mem.main[@pc + 1] @@ -377,6 +377,22 @@ class Vm end end + def set_reg_b(val) + @reg_b = + case val + when Integer + val + when /^\[bp-(\d+)\]$/ + stack_addr = @bp - $1.to_i + @mem.stack[stack_addr] + when /^\[bp\+(\d+)\]$/ + stack_addr = @bp + $1.to_i + @mem.stack[stack_addr] + else + raise not_yet_impl("val", val) + end + end + def compare @zf = (@reg_a == @reg_b) ? 1 : 0 end
お次は?
---- memory (main) ---- 00 ["call", 41] 02 ["exit"] 03 ["label", "vram_set"] 05 ["push", "bp"] 07 ["cp", "sp", "bp"] 10 ["sub_sp", 1] 12 ["set_reg_a", "[bp+4]"] 14 ["set_reg_b", "[bp+2]"] 16 ["mult_ab"] 17 ["cp", "reg_a", "[bp-1]"] 20 ["sub_sp", 1] 22 ["set_reg_a", "[bp-1]"] 24 ["set_reg_b", "[bp+3]"] 26 ["add_ab"] 27 ["cp", "reg_a", "[bp-2]"] pc => 30 ["set_vram", "vi", "[bp+5]"] 33 ["cp", "bp", "sp"] 36 ["pop", "bp"] 38 ["ret"] vgvm.rb:241:in `[]=': no implicit conversion of String into Integer (TypeError) from vgvm.rb:241:in `block in start' from vgvm.rb:142:in `loop' from vgvm.rb:142:in `start' from vgvm.rb:416:in `<main>'
vi
は変数名なので機械語コードに出現しているのがおかしい、
というパターンですね。
つまり参照が未解決……。
codegen_set()
を修正します。
--- a/vgcg.rb +++ b/vgcg.rb @@ -188,7 +188,15 @@ def codegen_set(fn_arg_names, lvar_names, rest) case dest when /^vram\[(.+)\]$/ vram_addr = $1 - alines << " set_vram #{vram_addr} #{src_val}" + case + when /^\d+$/ =~ vram_addr + alines << " set_vram #{vram_addr} #{src_val}" + when lvar_names.include?(vram_addr) + lvar_pos = lvar_names.index(vram_addr) + 1 + alines << " set_vram [bp-#{lvar_pos}] #{src_val}" + else + raise not_yet_impl("vram_addr", vram_addr) + end else lvar_pos = lvar_names.index(dest) + 1 alines << " cp #{src_val} [bp-#{lvar_pos}]"
どうでしょうか?
---- memory (main) ---- 00 ["call", 41] 02 ["exit"] 03 ["label", "vram_set"] 05 ["push", "bp"] 07 ["cp", "sp", "bp"] 10 ["sub_sp", 1] 12 ["set_reg_a", "[bp+4]"] 14 ["set_reg_b", "[bp+2]"] 16 ["mult_ab"] 17 ["cp", "reg_a", "[bp-1]"] 20 ["sub_sp", 1] 22 ["set_reg_a", "[bp-1]"] 24 ["set_reg_b", "[bp+3]"] 26 ["add_ab"] 27 ["cp", "reg_a", "[bp-2]"] pc => 30 ["set_vram", "[bp-2]", "[bp+5]"] vgvm.rb:241:in `[]=': no implicit conversion of String into Integer (TypeError) from vgvm.rb:241:in `block in start' from vgvm.rb:142:in `loop' from vgvm.rb:142:in `start' from vgvm.rb:416:in `<main>'
vi
は [bp-2]
に置き換わりましたが、まだダメか〜。
修正するぞ〜。
# set_vram --- a/vgvm.rb +++ b/vgvm.rb @@ -238,7 +238,27 @@ class Vm arg1 = @mem.main[@pc + 1] arg2 = @mem.main[@pc + 2] - @mem.vram[arg1] = arg2 + src_val = + case arg2 + when Integer + arg2 + when /^\[bp\+(\d+)\]$/ + stack_addr = @bp + $1.to_i + @mem.stack[stack_addr] + else + raise not_yet_impl("set_vram", arg2) + end + + case arg1 + when Integer + @mem.vram[arg1] = src_val + when /^\[bp-(\d+)\]$/ + stack_addr = @bp - $1.to_i + vram_addr = @mem.stack[stack_addr] + @mem.vram[vram_addr] = src_val + else + raise not_yet_impl("set_vram", arg1) + end @pc += pc_delta when "get_vram"
どうだ!?
$ ./run.sh 27_blinker.vgt.json (略) ================================ reg_a(17) reg_b(2) reg_c(0) zf(0) ---- memory (main) ---- 00 ["call", 41] pc => 02 ["exit"] 03 ["label", "vram_set"] 05 ["push", "bp"] 07 ["cp", "sp", "bp"] 10 ["sub_sp", 1] 12 ["set_reg_a", "[bp+4]"] 14 ["set_reg_b", "[bp+2]"] 16 ["mult_ab"] 17 ["cp", "reg_a", "[bp-1]"] 20 ["sub_sp", 1] 22 ["set_reg_a", "[bp-1]"] 24 ["set_reg_b", "[bp+3]"] 26 ["add_ab"] 27 ["cp", "reg_a", "[bp-2]"] 30 ["set_vram", "[bp-2]", "[bp+5]"] 33 ["cp", "bp", "sp"] 36 ["pop", "bp"] 38 ["ret"] 39 ["label", "main"] 41 ["push", "bp"] 43 ["cp", "sp", "bp"] 46 ["sub_sp", 1] 48 ["cp", 5, "[bp-1]"] 51 ["push", 1] 53 ["push", 1] 55 ["push", 2] 57 ["push", "[bp-1]"] 59 ["call", 5] 61 ["add_sp", 4] 63 ["push", 1] 65 ["push", 2] 67 ["push", 2] 69 ["push", "[bp-1]"] 71 ["call", 5] 73 ["add_sp", 4] 75 ["push", 1] 77 ["push", 3] 79 ["push", 2] 81 ["push", "[bp-1]"] 83 ["call", 5] 85 ["add_sp", 4] 87 ["cp", "bp", "sp"] 90 ["pop", "bp"] 92 ["ret"] ---- memory (stack) ---- 41 85 42 5 43 2 44 3 45 1 46 5 47 49 48 2 sp bp => 49 0 ---- memory (vram) ---- ..... ..... ..@.. ..... ..@.. ..... ..@.. ..... ..... ..... exit
やった〜動いた〜!!
(上に貼ったのは終了時の状態です)
Enter キーを連打してプログラムの実行を進めると、 3つのセルが順番に生存状態になっていくのが VRAM のダンプ表示で確認できました!!!!
グラフィックが動くと、 これまでとは違う種類の手応えがあっていいですね……!!
楽しくなってきた!!
とりあえず動くところまで持って行きたかったので ほぼコピペみたいな感じで参照を解決する部分のコードを書き散らかしてしまいました。
似たような処理がこれだけ多く出現すると さすがにリファクタリングしたくなってきますので、 次回はリファクタリングにしましょう。
あと、ダンプ表示が長くなってきて1画面に収まらなくなってきたので、 そこもどうにかしましょう。