- 目次ページに戻る / 前 / 次
- 前回からの差分をまとめて見る
準備が整ったので while やります!
["var", "a"] , ["set", "a", 0] , ["while", ["eq", 0, 0] , ["set", "a", ["+", "a", 1]] ]
こんな感じでやりたいのですが!!
["eq", 0, 0]
の部分の eq
がまだでしたね……準備が整ってませんでした!!
やっつけましょう!
組み込みの eq 演算子
テスト用の vgtコードです:
// 23_eq.vgt.json ["stmts" , ["func", "main" , [] , [ ["eq", 0, 0] , ["eq", 0, 1] ] ] ]
前回追加した +
と同じ並びに eq
のための分岐を追加します。
case文のときに似たことやったので同じ感じでさらっと追加
(共通化できそうな気もしますがひとまず放置で)。
--- a/vgcg.rb +++ b/vgcg.rb @@ -79,6 +79,24 @@ def codegen_exp(lvar_names, exp) alines << " set_reg_a #{left}" alines << " set_reg_b #{right}" alines << " add_ab" + when "eq" + $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 0" + alines << " jump end_eq_#{label_id}" + + # then + alines << "label then_#{label_id}" + alines << " set_reg_a 1" + + alines << "label end_eq_#{label_id}" else raise not_yet_impl("operator", operator) end @@ -154,6 +172,8 @@ def codegen_func_def(rest) alines << " sub_sp 1" when "set" alines += codegen_set(fn_arg_names, lvar_names, stmt_rest) + when "eq" + alines += codegen_exp(lvar_names, stmt) when "return" val = stmt_rest[0] alines << " set_reg_a #{val}"
実行して、
["eq", 0, 0] を実行した直後 => reg_a に 1(真)がセットされた ["eq", 0, 1] を実行した直後 => reg_a に 0(偽)がセットされた
こうなっていることが確認できました。OK。
今度こそ準備が整ったので while文やりましょう!
while文
while文はこんな構文にします。
["while", {条件} , [ // ループの本体 {文1} , {文2} ... , {文n} ] ]
while文のためのサンプルプログラムを用意します。
無限ループでローカル変数 a
をインクリメントするだけ。
// 23_while.vgt.json ["stmts" , ["func", "main" , [] , [ ["var", "a"] , ["set", "a", 0] , ["while", ["eq", 0, 0] , [ ["set", "a", ["+", "a", 1]] ] ] ] ] ]
これをコンパイルすると while文の部分がこんなアセンブリコードになってほしい:
label while_{id} # ここで条件を評価して結果を reg_a に入れる set_reg_b 1 # 比較用の値(真) compare jump_eq true_{id} jump end_while_{id} label true_{id} # 真の場合の処理 jump while_{id} # ループの先頭に戻る label end_while_{id}
はい、ではまずハードコーディングで追加して、と。
--- a/vgcg.rb +++ b/vgcg.rb @@ -52,6 +52,40 @@ def codegen_case(when_blocks) alines end +def codegen_while() + alines = [] + + $label_id += 1 + label_id = $label_id + + alines << "" + + # ループの先頭 + alines << "label while_#{label_id}" + + # TODO 条件の評価 + alines << " set_reg_a 1" + alines << " set_reg_b 1" + alines << " compare" + + # true の場合ループの本体を実行 + alines << " jump_eq true_#{label_id}" + + # false の場合ループを抜ける + alines << " jump end_while_#{label_id}" + + alines << "label true_#{label_id}" + alines << " # TODO ループの本体" + + # ループの先頭に戻る + alines << " jump while_#{label_id}" + + alines << "label end_while_#{label_id}" + alines << "" + + alines +end + def codegen_exp(lvar_names, exp) alines = [] operator, *args = exp @@ -179,6 +213,8 @@ def codegen_func_def(rest) alines << " set_reg_a #{val}" when "case" alines += codegen_case(stmt_rest) + when "while" + alines += codegen_while() else raise not_yet_impl("stmt_head", stmt_head) end
一応動かして問題なさそうなのを確認してから、
条件の評価部分で codegen_exp()
を呼び出すように書き換え。
--- a/vgcg.rb +++ b/vgcg.rb @@ -52,7 +52,8 @@ def codegen_case(when_blocks) alines end -def codegen_while() +def codegen_while(lvar_names, rest) + cond_exp, body = rest alines = [] $label_id += 1 @@ -63,8 +64,8 @@ def codegen_while() # ループの先頭 alines << "label while_#{label_id}" - # TODO 条件の評価 - alines << " set_reg_a 1" + # 条件の評価 ... 結果が reg_a に入る + alines += codegen_exp(lvar_names, cond_exp) # 比較対象の値(真)をセット alines << " set_reg_b 1" alines << " compare" @@ -215,7 +216,7 @@ def codegen_func_def(rest) when "case" alines += codegen_case(stmt_rest) when "while" - alines += codegen_while() + alines += codegen_while(lvar_names, stmt_rest) else raise not_yet_impl("stmt_head", stmt_head) end
そして、ループ本体部分を修正。
ここは codegen_stmts()
に丸投げでいいはず。
--- a/vgcg.rb +++ b/vgcg.rb @@ -77,7 +77,8 @@ def codegen_while(lvar_names, rest) alines << " jump end_while_#{label_id}" alines << "label true_#{label_id}" - alines << " # TODO ループの本体" + # ループの本体 + alines += codegen_stmts(body) # ループの先頭に戻る alines << " jump while_#{label_id}"
動くかな?
$ ./run.sh 23_while.vgt.json vgcg.rb:243:in `block in codegen_stmts': Not yet implemented ("stmt_head") ("set") (RuntimeError)
codegen_stmts()
を set文に対応させます。
--- a/vgcg.rb +++ b/vgcg.rb @@ -239,6 +239,8 @@ def codegen_stmts(rest) case stmt_head when "func" alines += codegen_func_def(stmt_rest) + when "set" + alines += codegen_set(fn_arg_names, lvar_names, stmt_rest) else raise not_yet_impl("stmt_head", stmt_head) end
再度実行。
$ ./run.sh 23_while.vgt.json vgcg.rb:243:in `block in codegen_stmts': undefined local variable or method `fn_arg_names' for main:Object (NameError)
関数の引数が解決できないので、適宜メソッドの引数で渡すように修正。
--- a/vgcg.rb +++ b/vgcg.rb @@ -52,7 +52,7 @@ def codegen_case(when_blocks) alines end -def codegen_while(lvar_names, rest) +def codegen_while(fn_arg_names, lvar_names, rest) cond_exp, body = rest alines = [] @@ -78,7 +78,7 @@ def codegen_while(lvar_names, rest) alines << "label true_#{label_id}" # ループの本体 - alines += codegen_stmts(body) + alines += codegen_stmts(fn_arg_names, lvar_names, body) # ループの先頭に戻る alines << " jump while_#{label_id}" @@ -217,7 +217,7 @@ def codegen_func_def(rest) when "case" alines += codegen_case(stmt_rest) when "while" - alines += codegen_while(lvar_names, stmt_rest) + alines += codegen_while(fn_arg_names, lvar_names, stmt_rest) else raise not_yet_impl("stmt_head", stmt_head) end @@ -231,7 +231,7 @@ def codegen_func_def(rest) alines end -def codegen_stmts(rest) +def codegen_stmts(fn_arg_names, lvar_names, rest) alines = [] rest.each do |stmt| @@ -257,7 +257,7 @@ def codegen(tree) head, *rest = tree # assert head == "stmts" - alines += codegen_stmts(rest) + alines += codegen_stmts([], [], rest) alines end
トップレベルの呼び出しのときは
関数の引数もローカル変数も存在しないので
codegen_stmts([], [], rest)
のように空の配列を渡しておけばいいでしょう。
これで動くようになりました!
================================ reg_a(4) reg_b(1) reg_c(0) zf(1) ---- memory (main) ---- 00 ["call", 5] 02 ["exit"] 03 ["label", "main"] 05 ["push", "bp"] 07 ["cp", "sp", "bp"] 10 ["sub_sp", 1] 12 ["cp", 0, "[bp-1]"] 15 ["label", "while_1"] pc => 17 ["set_reg_a", 0] 19 ["set_reg_b", 0] 21 ["compare"] 22 ["jump_eq", 30] 24 ["set_reg_a", 0] 26 ["jump", 34] 28 ["label", "then_2"] 30 ["set_reg_a", 1] 32 ["label", "end_eq_2"] 34 ["set_reg_b", 1] 36 ["compare"] 37 ["jump_eq", 43] 39 ["jump", 55] 41 ["label", "true_1"] 43 ["set_reg_a", "[bp-1]"] 45 ["set_reg_b", 1] 47 ["add_ab"] 48 ["cp", "reg_a", "[bp-1]"] 51 ["jump", 17] 53 ["label", "end_while_1"] 55 ["cp", "bp", "sp"] 58 ["pop", "bp"] 60 ["ret"] ---- memory (stack) ---- 38 0 39 0 40 0 41 0 42 0 43 0 44 0 45 0 sp => 46 4 ... ローカル変数 a bp => 47 49 48 2 49 0
これを実行すると、同じ箇所がぐるぐる実行されて
ローカル変数 a
の値が 1 ずつ増えていく様子が観察できます!
というわけで while で無限ループができました!!