コード生成器で気になっていた部分を修正しました。
codegen_stmts() まわりの整理
修正前の状況を見てみましょう。
呼び出しの関係が次のようになっています。
呼び出す側 => 呼び出される側
codegen => codegen_stmts
codegen_case => codegen_stmts
codegen_while => codegen_stmts
まず codegen_stmts
を codegen_top_stmts
にリネーム。
リネーム後はこうなります:
codegen => codegen_top_stmts
codegen_case => codegen_top_stmts
codegen_while => codegen_top_stmts
codegen => codegen_top_stmts
はいいとして、
codegen_case
、 codegen_while
から codegen_top_smtms
を呼んでいるのは変ですね。
これだと、たとえば case 文や while 文の中で関数が定義できてしまいます
(できるけどそういうコードを書かないようにしていた、という状態)。
リネームによっておかしなところが目立つようになりました。
おかしいので是正していきます。
まずは codegen_func_def
内で関数の処理本体のコード生成を行っていた部分を
改めて codegen_stmts
としてメソッド抽出します。
トップレベル以外の場所にある文の連なりの処理です。
(追記 2020-08-25) テストが通っていたので雑にやってしまいましたが、 lvar_names
の扱いがおかしいことに気付きました。あとで修正します……。
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
end
alines
end
こうなりました:
codegen => codegen_top_stmts
codegen_case => codegen_top_stmts
codegen_while => codegen_top_stmts
codegen_func_def => codegen_stmts
あとは
codegen_case
、 codegen_while
から codegen_top_smtms
ではなく
codegen_stmts
を呼ぶようにすると、こうなります:
codegen => codegen_top_stmts
codegen_case => codegen_stmts
codegen_while => codegen_stmts
codegen_func_def => codegen_stmts
これで意味的に変だったところがまともになりました。
テストが壊れていないので動作の方も問題なさそうです。
たぶん大丈夫。
ライフゲーム動いてるから。
これで codegen_top_stmts
の呼び出し元が codegen
だけになりました。
トップレベルに書けるのは今のところ関数定義とコメントだけでよいので、
それ以外の分岐は消してしまいます(上述の修正で不要になったので消してOK)。
これにより引数 fn_arg_names
, lvar_names
も不要になったのでこれも除去。
--- a/vgcg.rb
+++ b/vgcg.rb
@@ -428,7 +428,7 @@ def codegen_func_def(rest)
alines
end
-def codegen_top_stmts(fn_arg_names, lvar_names, rest)
+def codegen_top_stmts(rest)
alines = []
rest.each do |stmt|
@@ -436,16 +436,6 @@ def codegen_top_stmts(fn_arg_names, lvar_names, rest)
case stmt_head
when "func"
alines += codegen_func_def(stmt_rest)
- 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 "set"
- alines += codegen_set(fn_arg_names, 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
@@ -464,7 +454,7 @@ def codegen(tree)
head, *rest = tree
# assert head == "stmts"
- alines += codegen_top_stmts([], [], rest)
+ alines += codegen_top_stmts(rest)
alines
end
第38回
で codegen_stmts()
と codegen_func_def()
の共通化
について触れたときは、なんか変だなとは思っていたもののこの形がまだ見えていませんでした。
慌てていじらずに寝かせておいてよかった気がします。
codegen_exp()
が長くて(106行……)ちょっと辛いのと、
二項演算の左右の項をスタックに push する処理が重複していたので、
メソッド抽出して整理して、最終的に次のようになりました。すっきり。
def codegen_exp(fn_arg_names, lvar_names, exp)
alines = []
operator, *args = exp
arg_l = args[0]
arg_r = args[1]
alines += _codegen_exp_push(fn_arg_names, lvar_names, arg_l)
alines += _codegen_exp_push(fn_arg_names, lvar_names, arg_r)
case operator
when "+"
alines += _codegen_exp_add()
when "*"
alines += _codegen_exp_mult()
when "eq"
alines += _codegen_exp_eq()
when "neq"
alines += _codegen_exp_neq()
else
raise not_yet_impl("operator", operator)
end
alines
end
その他
codegen_set()
: caseの分岐を他の箇所に揃えた
- Vm#copy: 記述位置の移動のみ
- vgparser.rb:
tokenize()
の記述位置の移動のみ
- パーサのコードを調べてるとき、末尾に
tokenize()
があると邪魔くさかったので