- 目次ページに戻る / 前 / 次
- 前回からの差分をまとめて見る
関数まわりが整備できたので、次は条件分岐(if文)やりましょうか。
えーと、こんな感じで……
["if" , ["eq", 33, 31] , [ ["set_reg_a", 11] ] , [ ["set_reg_a", 22] ] ]
こんな感じで作っていましたが、この後すぐに 「case 文で代用できるから if文はなくても良かったな…」 となって if文のコード生成をなくしてしまいました。
というわけで if文はなかったことにして case文をやります。
こっちが case文の例です:
// 21_case.vgt.json ["case" , [ ["eq", 44, 45] // 条件1 , ["set_reg_a", 11] // 条件1 が真だった場合に実行 ] , [ ["eq", 55, 56] // 条件2 , ["set_reg_a", 22] // 条件2 が真だった場合に実行 ] // else節に相当 , [ ["eq", 0, 0] , ["set_reg_a", 33] ] ]
Lisp の cond ですね。
else 節部分の ["eq", 0, 0]
部分がちょっと不格好ですが、
今の段階でもこうすれば動作としては期待するものになるので、
いったんこれで。
まずは今使えるものでなんとかします
(結局最後までこのままなんですが)。
C言語や Java の switch文とは違ってフォールスルーはせず、したがって break もなし。
上のコードだと条件1 も条件2 も真にならず else 節が実行されますが、動作確認するときは適当に 条件1 が真になるように書き換えたり、 条件2 が真になるように書き換えたりしてやっていきます (または、そういうパターンを別ファイルで用意してもよいと思います) 。
まずは期待されるアセンブリコードを考えます。 ちょっと長い。
(※ 最終的にはラベルの部分が少し変わります。後述。)
# 条件1 を評価 set_reg_a 44 set_reg_b 45 compare jump_eq when_0 # 条件1 の body にジャンプ # 条件2 を評価 set_reg_a 55 set_reg_b 56 compare jump_eq when_1 # 条件2 の body にジャンプ # 条件3 を評価 set_reg_a 0 set_reg_b 0 compare jump_eq when_2 # 条件3 の body にジャンプ jump end_case # すべての条件が偽だった場合 # 条件1 の body label when_0 set_reg_a 11 jump end_case # 条件2 の body label when_1 set_reg_a 22 jump end_case # 条件3 の body label when_2 set_reg_a 33 jump end_case label end_case
はい、では入力となる vgtコードを用意して (上のコードを main 関数の中に入れただけ)、
// 21_case.vgt.json ["stmts" , ["func", "main" , [] , [ ["case" , [ ["eq", 44, 45] // 条件1 , ["set_reg_a", 11] // 条件1 が真だった場合に実行 ] , [ ["eq", 55, 56] // 条件2 , ["set_reg_a", 22] // 条件2 が真だった場合に実行 ] // else節に相当 , [ ["eq", 0, 0] , ["set_reg_a", 33] ] ] ] ] ]
コード生成処理を修正していきましょう。
まずは codegen_case()
を追加。
ちょっと分かりにくいかもしれませんが、
それぞれの条件が真だった場合の処理をアセンブリコードの形で
then_bodies
という配列に溜めておいて、
label end_case
の前でまとめて出力するようにしてみました。
# ※ codegen_func_def() の前に追加 def codegen_case(when_blocks) alines = [] when_idx = -1 then_bodies = [] when_blocks.each do |when_block| when_idx += 1 cond, *rest = when_block cond_head, *cond_rest = cond alines << " # 条件 #{when_idx}: #{cond.inspect}" case cond_head when "eq" alines << " set_reg_a #{cond_rest[0]}" alines << " set_reg_b #{cond_rest[1]}" alines << " compare" alines << " jump_eq when_#{when_idx}" then_alines = ["label when_#{when_idx}"] rest.each {|stmt| then_alines << " " + stmt.join(" ") } then_alines << " jump end_case" then_bodies << then_alines else raise not_yet_impl("cond_head", cond_head) end end # すべての条件が偽だった場合 alines << " jump end_case" then_bodies.each {|then_alines| alines += then_alines } alines << "label end_case" alines end
codegen_func_def()
から呼び出すようにします。
--- a/vgcg.rb +++ b/vgcg.rb @@ -108,6 +108,8 @@ def codegen_func_def(rest) when "return" val = stmt_rest[0] alines << " set_reg_a #{val}" + when "case" + alines += codegen_case(stmt_rest) else raise not_yet_impl("stmt_head", stmt_head) end
Vm.num_args_for()
に jump_eq
, jump
,
compare
がなくてエラーになったので追加。
--- a/vgvm.rb +++ b/vgvm.rb @@ -255,9 +255,9 @@ class Vm case operator when "cp" 2 - when "set_reg_a", "set_reg_b", "label", "call", "push", "pop", "add_sp", "sub_sp" + when "set_reg_a", "set_reg_b", "label", "call", "push", "pop", "add_sp", "sub_sp", "jump_eq", "jump" 1 - when "ret", "exit", "add_ab" + when "ret", "exit", "add_ab", "compare" 0 else raise "Invalid operator (#{operator})"
vgtコードを書き換えて、 条件1が真になる場合、条件2が真になる場合、 すべての条件が偽になる場合、などの動作を確認して、 よしよし大丈夫だな……とやっていたのですが、 実はまだダメなんですよ。
よしできたぞーどんどん次に進むぞ―といってこのまま進んでしまうと、 忘れた頃に
_人人人人人人人人人人_
> 複数のcase文 <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
が登場し、謎の挙動にハマってびっくりすることになります (なりました)。
どういうことかというと、case 文が複数存在した場合でも
使っているラベルが when_0
とか end_case
とかなので、
ある case 文から別の case 文のラベルにジャンプしてしまうわけですね。
たとえばこんなコードを動かしてみると、
// 21_cases.vgt.json ["stmts" , ["func", "main" , [] , [ ["case" , [ ["eq", 11, 12] , ["set_reg_a", 22] ] , [ ["eq", 0, 0] , ["set_reg_a", 33] // ここは実行されない ] ] , ["case" , [ ["eq", 44, 45] , ["set_reg_a", 55] ] , [ ["eq", 0, 0] , ["set_reg_a", 66] ] ] ] ] ]
1つ目の case 文の else から 2つ目の case 文の else 節にジャンプしてしまって、 その後すぐ終了してしまいます。
これはダメですね。対策しないと。
それでどうしたかというと、グローバルなインデックスを安直に用意して、 ラベル名に含めるようにしました。
$label_id
を直に使わずに label_id
にコピーして使っているのは
入れ子の case文を見越しているため。
あと、 $label_id
は case 文以外の while 文などでも共通で使う想定です。
while文の中に case文、みたいなのも登場してくるでしょう。
--- a/vgcg.rb +++ b/vgcg.rb @@ -6,8 +6,12 @@ require 'json' require './common' +$label_id = 0 + def codegen_case(when_blocks) alines = [] + $label_id += 1 + label_id = $label_id when_idx = -1 then_bodies = [] @@ -16,20 +20,20 @@ def codegen_case(when_blocks) when_idx += 1 cond, *rest = when_block cond_head, *cond_rest = cond - alines << " # 条件 #{when_idx}: #{cond.inspect}" + alines << " # 条件 #{label_id}_#{when_idx}: #{cond.inspect}" case cond_head when "eq" alines << " set_reg_a #{cond_rest[0]}" alines << " set_reg_b #{cond_rest[1]}" alines << " compare" - alines << " jump_eq when_#{when_idx}" + alines << " jump_eq when_#{label_id}_#{when_idx}" - then_alines = ["label when_#{when_idx}"] + then_alines = ["label when_#{label_id}_#{when_idx}"] rest.each {|stmt| then_alines << " " + stmt.join(" ") } - then_alines << " jump end_case" + then_alines << " jump end_case_#{label_id}" then_bodies << then_alines else rasie not_yet_impl("cond_head", cond_head) @@ -37,13 +41,13 @@ def codegen_case(when_blocks) end # すべての条件が偽だった場合 - alines << " jump end_case" + alines << " jump end_case_#{label_id}" then_bodies.each {|then_alines| alines += then_alines } - alines << "label end_case" + alines << "label end_case_#{label_id}" alines end
OKです! 次に行きましょう!!