- 目次ページに戻る / 前 / 次
- 前回からの差分をまとめて見る
どうすれば「本棚の左端から36番目の本を取って」と言う代わりに「広辞苑を取って」と言えるようになるでしょうか? コンピュータが名前とモノの対応表を持てばよいのです。
『コーディングを支える技術――成り立ちから学ぶプログラミング作法』 p85
今回はローカル変数の宣言と代入をやります。
( アセンブリをやっていたときはループ、条件分岐、 それからサブルーチン〜という順番でやっていました。 それとは違う順番になっていますが、 特に意図したものではなく、次にやることを気分で適当に決めていたためです。 )
vgtコード:
// 18_local_var.vgt.json ["stmts" , ["func", "main" , [] , [ ["var", "a"] // ← これ! ] ] ]
ローカル変数の宣言のために、
["var", "{ローカル変数名}"]
という構文を追加します (func に var と来るとちょっと GO言語っぽいですね)。
期待するアセンブリコード出力:
call main exit label main push bp cp sp bp # 関数の処理本体 sub_sp 1 # ← これが加わる cp bp sp pop bp ret
これだけなら楽勝ですね。 codegen_func_def()
の case
の分岐を1つ増やすだけ。
--- a/vgcg.rb +++ b/vgcg.rb @@ -25,6 +25,8 @@ def codegen_func_def(rest) when "call" fn_name = stmt_rest[0] alines << " call #{fn_name}" + when "var" + alines << " sub_sp 1" else raise not_yet_impl("stmt_head", stmt_head) end
宣言するだけでは意味がないので、変数に値をセットしてみましょう。
またまた文法を拡張して、こんどは変数に値を代入する
["set", "{ローカル変数名}", {セットしたい値}]
という構文を追加します。
vgtコードの方にも追加。
--- a/18_local_var.vgt.json +++ b/18_local_var.vgt.json @@ -3,6 +3,7 @@ , [] , [ ["var", "a"] + , ["set", "a", 12] ] ] ]
ちなみに、var a = 12;
のように、
宣言と同時に値を代入する構文は用意しませんでした。
var
と set
を使うことで同等のことがすでにできているので、
さっさと次に進もうと考えたためです。
( ただ、ちょっとだけとはいえ煩雑ではあったので、 利便性のために追加しておいても良かったかなという気もします。 そんなに難しくなさそうですし。 )
で、(1個目の)ローカル変数への値のセットは、アセンブリでは
cp 12 [bp-1]
のように変換されてほしいので、そのように修正します。
var
と同じように codegen_func_def()
の分岐に追加します
(ひとまず [bp-1]
はハードコーディングで……)。
--- a/vgcg.rb +++ b/vgcg.rb @@ -27,6 +27,10 @@ def codegen_func_def(rest) alines << " call #{fn_name}" when "var" alines << " sub_sp 1" + when "set" + lvar_name = stmt_rest[0] + val = stmt_rest[1] + alines << " cp #{val} [bp-1]" else raise not_yet_impl("stmt_head", stmt_head) end
アセンブリコードに変換してみましょう。
$ ruby vgcg.rb 18_local_var.vgt.json call main exit label main push bp cp sp bp # 関数の処理本体 sub_sp 1 # ローカル変数を宣言(格納場所を確保) cp 12 [bp-1] # ローカル変数に値を代入(確保した場所に cp) cp bp sp pop bp ret
よさそうですね。
run.sh
で実行すると……
vgvm.rb:233:in `copy': Not yet implemented ("copy src") (12) (RuntimeError) from vgvm.rb:149:in `block in start' from vgvm.rb:126:in `loop' from vgvm.rb:126:in `start' from vgvm.rb:328:in `<main>'
えーっとこれは…… 即値は cp
命令のコピー元として使えないようになってますね。
VM を修正します。
--- a/vgvm.rb +++ b/vgvm.rb @@ -219,6 +219,8 @@ class Vm def copy(arg1, arg2) src_val = case arg1 + when Integer + arg1 when "reg_a" @reg_a when "sp"
あらためて実行すると……こんどは大丈夫。
================================ 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 ["cp", 12, "[bp-1]"] pc => 15 ["cp", "bp", "sp"] 18 ["pop", "bp"] 20 ["ret"] ---- memory (stack) ---- 38 0 39 0 40 0 41 0 42 0 43 0 44 0 45 0 sp => 46 12 ... ローカル変数にセットした値 bp => 47 49 48 2 49 0
set文が動きました!! めでたしめでたし。
めでたく動いたので、ハードコーディングした部分をいい感じにしましょう。 ここをいい感じにすると 2個目以降のローカル変数も使えるようになるはず。
vgtコード:
// 18_local_vars.vgt.json ["stmts" , ["func", "main" , [] , [ ["var", "a"] , ["set", "a", 12] // [bp-1] にセットされてほしい , ["var", "b"] , ["set", "b", 34] // [bp-2] にセットされてほしい ] ] ]
上のコメントに書いているように、
1個目の引数 a
は [bp-1]
、
2個目の引数 b
は [bp-2]
に値がセットされるように変換したい。
変数名から「その変数が何番目に宣言されたか」が分かればよいので、 何かしらのマッピング情報を用意すればいいでしょう。
マッピングってことはハッシュかな、と一瞬考えましたが、 何番目かが分かればいいだけなので、 ハッシュじゃなくて配列を用意して、 変数が出現した(宣言された)順番に追加しとけばいいんじゃないでしょうか。
それでやってみましょう。
--- a/vgcg.rb +++ b/vgcg.rb @@ -19,6 +19,9 @@ def codegen_func_def(rest) alines << "" alines << " # 関数の処理本体" + + lvar_names = [] + body.each {|stmt| stmt_head, *stmt_rest = stmt case stmt_head @@ -26,11 +29,13 @@ def codegen_func_def(rest) fn_name = stmt_rest[0] alines << " call #{fn_name}" when "var" + lvar_names << stmt_rest[0] alines << " sub_sp 1" when "set" lvar_name = stmt_rest[0] val = stmt_rest[1] - alines << " cp #{val} [bp-1]" + lvar_pos = lvar_names.index(lvar_name) + 1 + alines << " cp #{val} [bp-#{lvar_pos}]" else raise not_yet_impl("stmt_head", stmt_head) end
$ ruby vgcg.rb 18_local_vars.vgt.json call main exit label main push bp cp sp bp # 関数の処理本体 sub_sp 1 cp 12 [bp-1] sub_sp 1 cp 34 [bp-2] cp bp sp pop bp ret
うまくいったようです!
================================ 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 ["cp", 12, "[bp-1]"] 15 ["sub_sp", 1] 17 ["cp", 34, "[bp-2]"] pc => 20 ["cp", "bp", "sp"] 23 ["pop", "bp"] 25 ["ret"] ---- memory (stack) ---- 37 0 38 0 39 0 40 0 41 0 42 0 43 0 44 0 sp => 45 34 ... 2個目のローカル変数 46 12 ... 1個目のローカル変数 bp => 47 49 48 2 49 0
2個目のローカル変数 b
に値をセットした直後の様子。
いい感じです!
これができたら、main から呼び出した先の関数でも 同じようにローカル変数が使えるようになってるんじゃないでしょうか。
調子に乗って試してみましょう。
vgtコード:
// 18_local_vars_call.vgt.json ["stmts" , ["func", "main" , [] , [ ["var", "a"] , ["set", "a", 12] , ["var", "b"] , ["set", "b", 34] , ["call", "fn_sub"] ] ] , ["func", "fn_sub" , [] , [ ["var", "a"] , ["set", "a", 56] , ["var", "b"] , ["set", "b", 78] ] ] ]
fn_sub()
でローカル変数 b
に値がセットされた直後の状態:
================================ 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 ["cp", 12, "[bp-1]"] 15 ["sub_sp", 1] 17 ["cp", 34, "[bp-2]"] 20 ["call", 30] 22 ["cp", "bp", "sp"] 25 ["pop", "bp"] 27 ["ret"] 28 ["label", "fn_sub"] 30 ["push", "bp"] 32 ["cp", "sp", "bp"] 35 ["sub_sp", 1] 37 ["cp", 56, "[bp-1]"] 40 ["sub_sp", 1] 42 ["cp", 78, "[bp-2]"] pc => 45 ["cp", "bp", "sp"] 48 ["pop", "bp"] 50 ["ret"] ---- memory (stack) ---- 33 0 34 0 35 0 36 0 37 0 38 0 39 0 40 0 sp => 41 78 ... fn_sub のローカル変数2 42 56 ... fn_sub のローカル変数1 bp => 43 47 44 22 45 34 ... main のローカル変数2 46 12 ... main のローカル変数1 47 49 48 2 49 0
おお〜いいですね〜。すんなり〜。
main と同じ変数名( a
, b
)を使っても
ちゃんと別のものとして扱われています。