vm2gol v2 製作メモ(40) 落ち穂拾い: ローカル変数宣言時に初期値をセット



これは簡単なのでサラッとやります。

(18) ローカル変数の宣言と代入 / var, set文 のときにちょっと触れていた件です。 だいぶ枝葉な話ですね。


  ["var", "x"]
, ["set", "x", 12]

このように2つの文に分けて書いていたのを、次のように 1つの文で書けるようにしたい。

  ["var", "x", 12]

アセンブリコードのレイヤーではこうですね。 var文と set文でそれぞれを出力していたのを、 var文だけで両方出力されるようにしたい。

sub_sp 1       # ローカル変数の宣言
cp 12 [bp-{N}] # ローカル変数に値をセット

動作確認用のコードを用意。

// 40_var_init.vgt.json 

["stmts"
, ["func", "main", []
  , [
      ["var", "a"]
    , ["var", "b", 12]
    ]
  ]
]

修正する箇所は codegen_func_def() の var文の箇所。

def codegen_func_def(rest)
  # ...
  body = rest[2]
  # ...
  lvar_names = []

  body.each {|stmt|
    stmt_head, *stmt_rest = stmt
    case stmt_head
    # ...
    when "var"
      lvar_names << stmt_rest[0]
      alines << "  sub_sp 1"
    when "set"
    # ...

var文の引数(stmt_rest)の数を見て、初期値が指定されていたら cp {初期値} [bp-{N}] を出力する、という修正で良さそうです。

それを愚直に書いてもいいのですが、 ここは横着して codegen_set() がそのまま使えそうです。 横着というか意味的にもそうするのが適切なように思えます。 そうしましょう。

--- a/vgcg.rb
+++ b/vgcg.rb
@@ -390,6 +390,9 @@ def codegen_func_def(rest)
     when "var"
       lvar_names << stmt_rest[0]
       alines << "  sub_sp 1"
+      if stmt_rest.size == 2
+        alines += codegen_set(fn_arg_names, lvar_names, stmt_rest)
+      end
     when "set"
       alines += codegen_set(fn_arg_names, lvar_names, stmt_rest)
     when "eq"

できました。簡単〜。

動かして確認。

$ STEP= ./run.sh 40_var_init.vgt.json
...
================================
6: reg_a(0) reg_b(0) reg_c(0) zf(0)
---- memory (main) ----
      00   ["call", 5]
      02   ["exit"]
      03 ["label", "main"]
      05   ["push", "bp"]
      07   ["cp", "sp", "bp"]
      10   ["sub_sp", 1]
      12   ["sub_sp", 1]        ... ローカル変数 b の宣言
      14   ["cp", 12, "[bp-2]"] ... ローカル変数 b に初期値をセット
pc => 17   ["cp", "bp", "sp"]
      20   ["pop", "bp"]
      22   ["ret"]
---- memory (stack) ----
         37 0
         38 0
         39 0
         40 0
         41 0
         42 0
         43 0
         44 0
sp    => 45 12 ... ローカル変数 b
         46 0
   bp => 47 49
         48 2
         49 0

大丈夫そうですね。


ではライフゲームの方も書き換えます。

--- a/gol.vgt.json
+++ b/gol.vgt.json
@@ -63,8 +63,7 @@
 , ["func", "calc_next_gen", ["current_val", "count"]
   , [
       // 注目しているセルの次世代の生死
-      ["var", "next_val"]
-    , ["set", "next_val", 0]
+      ["var", "next_val", 0]
 
     , ["case"
       , [["eq", "current_val", 0]
@@ -88,8 +87,7 @@
 
 , ["func", "count_alive", ["w", "h", "x", "y"]
   , [
-      ["var", "count"]
-    , ["set", "count", 0]
+      ["var", "count", 0]
 
     , ["var", "xl"]
     , ["var", "xr"]
@@ -161,11 +159,8 @@
 
 , ["func", "make_next_gen", ["w", "h"]
   , [
-      ["var", "x"]
-    , ["set", "x", 0]
-
-    , ["var", "y"]
-    , ["set", "y", 0]
+      ["var", "x", 0]
+    , ["var", "y", 0]
 
     , ["var", "count"]
       // 注目しているセルの現世代の生死
@@ -203,8 +198,7 @@
 
 , ["func", "replace_with_buf", []
   , [
-      ["var", "vi"]
-    , ["set", "vi", 0]
+      ["var", "vi", 0]
 
     , ["var", "vi_buf"]
     , ["var", "temp"]
@@ -222,10 +216,8 @@
 
 , ["func", "main", []
   , [
-      ["var", "w"] // 盤面の幅
-    , ["set", "w", 5]
-    , ["var", "h"] // 盤面の高さ
-    , ["set", "h", 5]
+      ["var", "w", 5] // 盤面の幅
+    , ["var", "h", 5] // 盤面の高さ
 
       // 初期状態の設定
     , ["call", "vram_set", "w", 1, 0, 1]

codegen_set() を流用したことにより、 ["+", 1, 2] のような二項の式も初期値として指定できるようになっています。

なので、次の箇所も書き換えられます。

--- a/gol.vgt.json
+++ b/gol.vgt.json
@@ -89,15 +89,10 @@
   , [
       ["var", "count", 0]
 
-    , ["var", "xl"]
-    , ["var", "xr"]
-    , ["var", "yt"]
-    , ["var", "yb"]
-
-    , ["set", "xl", ["+", "x", -1]]
-    , ["set", "xr", ["+", "x",  1]]
-    , ["set", "yt", ["+", "y", -1]]
-    , ["set", "yb", ["+", "y",  1]]
+    , ["var", "xl", ["+", "x", -1]]
+    , ["var", "xr", ["+", "x",  1]]
+    , ["var", "yt", ["+", "y", -1]]
+    , ["var", "yb", ["+", "y",  1]]
 
     , ["_cmt", "★ xl の補正の直前"]
     , ["call_set", "xl", ["adjust_index", "w", "xl"]]

ライフゲームを実行すると……壊れていないようですね。OKです!

gol.vgt.json は前回と比べて 13行減り、見た目がちょっとすっきりしました。 枝葉だけど微妙に気にはなっていたので、気分的にもまたちょっとすっきりです。