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
とはいえ、呼び出し先で変更すると挙動が把握しにくかったりしますし、もうちょっといい感じにできないかなと。
というわけで修正します。
まず、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
の値を変更