- 目次ページに戻る / 前 / 次
- 前回からの差分をまとめて見る
Java版
を書いているときに codegen_stmts()
の微妙なところに気付いてしまいました。
def codegen_stmts(fn_arg_names, lvar_names, stmts) alines = [] stmts.each do |stmt| stmt_head, *stmt_rest = stmt case stmt_head when "call" alines += codegen_call(fn_arg_names, lvar_names, stmt_rest) when "call_set" alines += codegen_call_set(fn_arg_names, lvar_names, stmt_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" # ...
メソッドの引数 lvar_names
を変更しています。
なんでこうなってるかというと、
以前リファクタリングしたとき(第46回)
に雑にやってしまっていたからですね……。
Ruby の場合は参照渡し的な挙動になるので、 うまく動いてテストも通っていて(だからすぐ気づかなかったのですが)、特別まずいというわけでもないと思います。
def foo(arg_xs) arg_xs << 2 end xs = [1] foo(xs) foo(xs) p xs #=> [1, 2, 2]
とはいえ、呼び出し先で変更すると挙動が把握しにくかったりしますし、もうちょっといい感じにできないかなと。
というわけで修正します。
まず、codegen_stmts()
のループ内で行っている1つの文の処理を codegen_stmt()
に抽出。これは単純なリファクタリング。
--- a/vgcg.rb +++ b/vgcg.rb @@ -415,38 +415,44 @@ def codegen_comment(comment) ] end +def codegen_stmt(fn_arg_names, lvar_names, stmt) + stmt_head, *stmt_rest = stmt + + case stmt_head + when "call" + codegen_call(fn_arg_names, lvar_names, stmt_rest) + when "call_set" + codegen_call_set(fn_arg_names, lvar_names, stmt_rest) + when "var" + alines = [] + 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 + alines + when "set" + codegen_set(fn_arg_names, lvar_names, stmt_rest) + # when "eq" + # alines += codegen_exp(fn_arg_names, lvar_names, stmt) + when "return" + codegen_return(lvar_names, stmt_rest) + when "case" + codegen_case(fn_arg_names, lvar_names, stmt_rest) + when "while" + codegen_while(fn_arg_names, lvar_names, stmt_rest) + when "_cmt" + codegen_comment(stmt_rest[0]) + else + raise not_yet_impl("stmt_head", stmt_head) + end +end + def codegen_stmts(fn_arg_names, lvar_names, stmts) alines = [] stmts.each do |stmt| - stmt_head, *stmt_rest = stmt - - case stmt_head - when "call" - alines += codegen_call(fn_arg_names, lvar_names, stmt_rest) - when "call_set" - alines += codegen_call_set(fn_arg_names, lvar_names, stmt_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" - # alines += codegen_exp(fn_arg_names, lvar_names, stmt) - when "return" - alines += codegen_return(lvar_names, stmt_rest) - when "case" - alines += codegen_case(fn_arg_names, lvar_names, stmt_rest) - when "while" - alines += codegen_while(fn_arg_names, lvar_names, stmt_rest) - when "_cmt" - alines += codegen_comment(stmt_rest[0]) - else - raise not_yet_impl("stmt_head", stmt_head) - end + alines += codegen_stmt(fn_arg_names, lvar_names, stmt) end alines
次に、codegen_stmt()
で変数宣言を処理するのをやめます。
それだけだと変数宣言できなくなってしまうので、ではどうするかというと、
変数宣言だけ codegen_func_def()
で処理してしまうことにしました。
これで変数名を lvar_names
に追加する処理が codegen_func_def()
に戻りました。
これが今回の修正のメインとなるコミットです。
--- a/vgcg.rb +++ b/vgcg.rb @@ -423,14 +423,6 @@ def codegen_stmt(fn_arg_names, lvar_names, stmt) codegen_call(fn_arg_names, lvar_names, stmt_rest) when "call_set" codegen_call_set(fn_arg_names, lvar_names, stmt_rest) - when "var" - alines = [] - 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 - alines when "set" codegen_set(fn_arg_names, lvar_names, stmt_rest) # when "eq" @@ -475,7 +467,18 @@ def codegen_func_def(rest) lvar_names = [] - alines += codegen_stmts(fn_arg_names, lvar_names, body) + body.each do |stmt| + if stmt[0] == "var" + _, *stmt_rest = stmt + 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 + else + alines += codegen_stmt(fn_arg_names, lvar_names, stmt) + end + end alines << "" alines << " cp bp sp"
codegen_stmt()
は codegen_while()
と codegen_case()
からも呼ばれているため、
この変更により while 文や case 文の中で変数宣言できなくなってしまいます。
が、その点は特に困らないからいいだろうという判断にしました。
昔のC言語だと関数の先頭でしかローカル変数宣言できませんでしたし、 大丈夫なんじゃないかな……。
あとは変数宣言のコード生成処理を codegen_var()
に抽出して、
関数の body を処理する部分が次のようになりました。
body.each do |stmt| if stmt[0] == "var" _, *stmt_rest = stmt lvar_names << stmt_rest[0] alines += codegen_var(fn_arg_names, lvar_names, stmt_rest) else alines += codegen_stmt(fn_arg_names, lvar_names, stmt) end end
その他
移植のときに気付いたもののフィードバックなど。
codegen_while()
などで出てくるラベル名を変数化して DRY に- 式関連の変数名などがパーサでは
expr
、コード生成器ではexp
となっていて 不揃いだったのでexpr
に統一 - comment → vm_comment にリネーム
Token#type
の値を変更:reserved
→:kw
など