vm2gol v2 製作メモ(29) 盤面のスキャン / 組み込みの neq



盤面の初期化ができたので続きをやります。

えーと続きというのは何かというと…… とりあえずループを回して盤面のセルを1個ずつスキャンする処理が必要なので、 それをやりましょう。

Ruby のコードでいうとこの部分。

(0...$h).each do |y|
  (0...$w).each do |x|
    # x, y を使った処理
  end
end

一度にあれこれやると大変そうなので、 前回のブリンカーの初期化を改造して、 まずは「メインの盤面のセルを全部 1(生存)にするだけ」 をやってみましょう。

1行だけでやってみる

いや、さらにステップを小さくしましょう。 まずは y を 0 に固定にして x だけ 0 から w-1 まで変化させるところからやってみます。 これなら単に一重の while ループ書くだけですね。

// 29_loop_x.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]

    , ["var", "x"]
    , ["set", "x", 0]

    , ["while", ["neq", "x", "w"], [
        ["call", "vram_set", "w", "x", 0, 1]
      , ["set", "x", ["+", "x", 1]]
      ]]
    ]
  ]
]

実行します。

$ ./run.sh 29_loop_x.vgt.json 
vgcg.rb:132:in `codegen_exp': Not yet implemented ("right") ("w") (RuntimeError)
        from vgcg.rb:78:in `codegen_while'
        from vgcg.rb:294:in `block in codegen_func_def'
        from vgcg.rb:274:in `each'
        from vgcg.rb:274:in `codegen_func_def'
        from vgcg.rb:315:in `block in codegen_stmts'
        from vgcg.rb:311:in `each'
        from vgcg.rb:311:in `codegen_stmts'
        from vgcg.rb:334:in `codegen'
        from vgcg.rb:346:in `<main>'

これは……あ、 codegen_exp の右項の処理がローカル変数に未対応ですね。 対応させましょう。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -128,6 +128,8 @@ def codegen_exp(fn_arg_names, lvar_names, exp)
       case
       when fn_arg_names.include?(args[1])
         to_fn_arg_addr(fn_arg_names, args[1])
+      when lvar_names.include?(args[1])
+        to_lvar_addr(lvar_names, args[1])
       else
         raise not_yet_impl("right", args[1])
       end

実行。

$ ./run.sh 29_loop_x.vgt.json 
vgcg.rb:168:in `codegen_exp': Not yet implemented ("operator") ("neq") (RuntimeError)
        from vgcg.rb:78:in `codegen_while'
        from vgcg.rb:296:in `block in codegen_func_def'
        from vgcg.rb:276:in `each'
        from vgcg.rb:276:in `codegen_func_def'
        from vgcg.rb:317:in `block in codegen_stmts'
        from vgcg.rb:313:in `each'
        from vgcg.rb:313:in `codegen_stmts'
        from vgcg.rb:336:in `codegen'
        from vgcg.rb:348:in `<main>'

なるほど、以前 eq は作っていました (※ 第23回) が、 neq は未実装でした。 実装しましょう。


eq とだいたい同じ。 "neq" は "not equal" の略です。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -164,6 +164,24 @@ def codegen_exp(fn_arg_names, lvar_names, exp)
     alines << "  set_reg_a 1"
 
     alines << "label end_eq_#{label_id}"
+  when "neq"
+    $label_id += 1
+    label_id = $label_id
+
+    alines << "  set_reg_a #{left}"
+    alines << "  set_reg_b #{right}"
+    alines << "  compare"
+    alines << "  jump_eq then_#{label_id}"
+
+    # else
+    alines << "  set_reg_a 1"
+    alines << "  jump end_neq_#{label_id}"
+
+    # then
+    alines << "label then_#{label_id}"
+    alines << "  set_reg_a 0"
+
+    alines << "label end_neq_#{label_id}"
   else
     raise not_yet_impl("operator", operator)
   end

eq の部分をコピペして、 ラベルを修正して、 評価結果(set_reg_a にセットする値)の真偽を逆にしただけ。 たぶんこれでいいはず……。

実行。

$ ./run.sh 29_loop_x.vgt.json 
vgcg.rb:339:in `block in codegen_stmts': Not yet implemented ("stmt_head") ("call") (RuntimeError)
        from vgcg.rb:331:in `each'
        from vgcg.rb:331:in `codegen_stmts'
        from vgcg.rb:91:in `codegen_while'
        from vgcg.rb:314:in `block in codegen_func_def'
        from vgcg.rb:294:in `each'
        from vgcg.rb:294:in `codegen_func_def'
        from vgcg.rb:335:in `block in codegen_stmts'
        from vgcg.rb:331:in `each'
        from vgcg.rb:331:in `codegen_stmts'
        from vgcg.rb:354:in `codegen'
        from vgcg.rb:366:in `<main>'

なるほど、 codegen_while()codegen_stmts() と呼び出しているのですが、 codegen_stmts() が call文に対応していません。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -333,6 +333,8 @@ def codegen_stmts(fn_arg_names, lvar_names, rest)
     case stmt_head
     when "func"
       alines += codegen_func_def(stmt_rest)
+    when "call"
+      alines += codegen_call(lvar_names, stmt_rest)
     when "set"
       alines += codegen_set(fn_arg_names, lvar_names, stmt_rest)
     else

codegen_func_def() からコピペ……。 前回ちょっと書いたように codegen_stmts()codgen_func_def() の内容が重複してます。 リファクタリングできそうですが、前回書いた通り放置して、 ベタに codegen_stmts() を call文に対応させます。

ともかくこれで動くようになりました! VRAM のメイン領域の y=0 の行のセルが左から順に生存になっていき、 右端のセルまで全部生存になったところで while ループを抜けて プログラムが終了します。

---- memory (vram) ----
@@@@@ .....
..... .....
..... .....
..... .....
..... .....

exit

二重ループですべてのセルを生存にする

では二重ループですべてのセルを生存にしてみましょう! はたして二重ループはちゃんと動くでしょうか?

// 29_loop_xy.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]
    , ["var", "h"] // 盤面の高さ
    , ["set", "h", 5]

    , ["var", "x"]
    , ["set", "x", 0]

    , ["var", "y"]
    , ["set", "y", 0]

    , ["while", ["neq", "y", "h"], [
        ["set", "x", 0]

      , ["while", ["neq", "x", "w"], [
          ["call", "vram_set", "w", "x", "y", 1]
        , ["set", "x", ["+", "x", 1]]
        ]]

        , ["set", "y", ["+", "y", 1]]

      ]]
    ]
  ]
]

実行。

$ ./run.sh 29_loop_xy.vgt.json 
vgcg.rb:341:in `block in codegen_stmts': Not yet implemented ("stmt_head") ("while") (RuntimeError)
        from vgcg.rb:331:in `each'
        from vgcg.rb:331:in `codegen_stmts'
        from vgcg.rb:91:in `codegen_while'
        from vgcg.rb:314:in `block in codegen_func_def'
        from vgcg.rb:294:in `each'
        from vgcg.rb:294:in `codegen_func_def'
        from vgcg.rb:335:in `block in codegen_stmts'
        from vgcg.rb:331:in `each'
        from vgcg.rb:331:in `codegen_stmts'
        from vgcg.rb:356:in `codegen'
        from vgcg.rb:368:in `<main>'

これも同じパターン。 codegen_while()codegen_stmts() と呼びだされて、 codegen_stmts() が while文に対応してません。

同じように codegen_func_def() からのコピペで修正します。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -337,6 +337,8 @@ def codegen_stmts(fn_arg_names, lvar_names, rest)
       alines += codegen_call(lvar_names, stmt_rest)
     when "set"
       alines += codegen_set(fn_arg_names, lvar_names, stmt_rest)
+    when "while"
+      alines += codegen_while(fn_arg_names, lvar_names, stmt_rest)
     else
       raise not_yet_impl("stmt_head", stmt_head)
     end

動くようになりました!! 下記のようにローカル変数 w, h, x, y が割り当てられていて、 x, y が指しているセルが生存に変わっていく様子が観察できます!

---- memory (stack) ----
         35 7
         36 5
         37 47
         38 135
         39 5
         40 2
         41 1
         42 1
sp    => 43 1 ... y
         44 3 ... x
         45 5 ... h
         46 5 ... w
   bp => 47 49
         48 2
         49 0
---- memory (vram) ----
@@@@@ .....
@@@.. .....
..... .....
..... .....
..... .....

そのまま Enter キーを押しっぱなしにしていると、 下記のようにすべてのセルが生存になったところでプログラムが終了しました!

---- memory (stack) ----
         41 4
         42 1
         43 5
         44 5
         45 5
         46 5
         47 49
         48 2
sp bp => 49 0
---- memory (vram) ----
@@@@@ .....
@@@@@ .....
@@@@@ .....
@@@@@ .....
@@@@@ .....

exit

やぼうのじつげんに またいっぽ ちかづいたぞ!