- 目次ページに戻る / 前 / 次
- 前回からの差分をまとめて見る
空の main 関数の実行の次は、 main から 別の関数の呼び出しをやってみましょう。
簡単そうですが、これも一歩ずつということで 2段階に分けます。
- 関数定義を並べるだけ(main からの呼び出しはしない)
- main から呼び出す
やっていきましょう。
関数定義を並べるだけ
まずは関数定義を並べるだけ。 vgt のコードはこんな感じにしました。
// 17_empty_main_sub.vgt.json ["stmts" , ["func", "main" , [] , [] ] , ["func", "fn_sub" , [] , [] ] ]
トップレベルというかルート要素的なものは1つにした方がいいかな? となんとなく考えて、
[ "stmts" , {文1} , {文2} , ... , {文n} ]
という構文を考えてみました。
stmts
は statements の略です。
この構文になっていたら、文1, 文2, ... 文n と順番に文を実行していく、というものです。
Lisp だと progn
でしょうか。
( 関数の定義は文なのか? というのはちょっと気になるところですが、 とりあえず文であるということにしておきます。 )
これをコンパイルしたらこんなアセンブリコードができてほしい。
call main exit label main ... label fn_sub ...
では、コンパイラというかコード生成部分を修正していきます。
stmts
の 文1, 文2, ..., 文n を順番に見ていって label 〜
の形で並べればいいだけなので
これは全然むずかしくなさそう。
修正の前にまずはリファクタリングします。
codegen()
に直接書いていた、
「関数定義に対応するアセンブリコードに変換する処理」の部分(ややこしいですね……)
をメソッド抽出しておきます。
diff が分かりやすくならないので リファクタリング後のコードを貼ります。
def codegen_func_def(rest) alines = [] fn_name = rest[0] body = rest[2] alines << "" alines << "label #{fn_name}" alines << " push bp" alines << " cp sp bp" alines << "" alines << " # 関数の処理本体" body.each {|stmt| alines << " # TODO" } alines << "" alines << " cp bp sp" alines << " pop bp" alines << " ret" alines end def codegen(tree) alines = [] alines << " call main" alines << " exit" head, *rest = tree alines += codegen_func_def(rest) alines end
head, *rest = tree
はこの先何度も出てくるイディオムで、
tree
(配列)の先頭の要素を head
に、
2番目以降の要素を rest
に代入するという操作です。
こんな感じの動作になります。
irb(main):001:0> tree = [1, 2, 3, 4] => [1, 2, 3, 4] irb(main):002:0> head, *rest = tree => [1, 2, 3, 4] irb(main):003:0> head => 1 irb(main):004:0> rest => [2, 3, 4]
それから、
alines += codegen_func_def(rest)
は
alines = alines + codegen_func_def(rest)
と同じで、
alines
と codegen_func_def(rest)
の返り値を繋げたもので
alines
を上書きする、という動作ですね。
irb(main):023:0> xs = [1, 2] => [1, 2] irb(main):024:0> xs += [3, 4] => [1, 2, 3, 4] irb(main):025:0> xs => [1, 2, 3, 4]
要するに codegen_func_def(rest)
の結果を
alines
の末尾に追加しているだけです。
これもこの先何度も使います。
はい、では先に進みましょう。
ルートは stmts
にすると決めたので、それに対応します。
今回の vgtコードは
- stmts - main() の定義 - fn_sub() の定義
のように、stmts
の直下に関数定義が2つぶら下がる形になっているので、
codegen_stmts()
というメソッドにルートのstmts
を渡して呼び出し、- その中で
codegen_func_def()
を呼び出す
ようにしてみます。
--- a/vgcg.rb +++ b/vgcg.rb @@ -4,6 +4,8 @@ require 'json' +require './common' + def codegen_func_def(rest) alines = [] @@ -29,6 +31,22 @@ def codegen_func_def(rest) alines end +def codegen_stmts(rest) + alines = [] + + rest.each do |stmt| + stmt_head, *stmt_rest = stmt + case stmt_head + when "func" + alines += codegen_func_def(stmt_rest) + else + raise not_yet_impl("stmt_head", stmt_head) + end + end + + alines +end + def codegen(tree) alines = [] @@ -36,7 +54,8 @@ def codegen(tree) alines << " exit" head, *rest = tree - alines += codegen_func_def(rest) + # assert head == "stmts" + alines += codegen_stmts(rest) alines end
期待するアセンブリコードに変換されるか試しましょう。
$ ruby vgcg.rb 17_empty_main_sub.vgt.json call main exit label main push bp cp sp bp # 関数の処理本体 cp bp sp pop bp ret label fn_sub push bp cp sp bp # 関数の処理本体 cp bp sp pop bp ret
いいですね!
ちなみにこの状態で ./run.sh 17_empty_main_sub.vgt.json
を実行すると、 main
だけ実行されて fn_sub
の部分は実行されずに exit します。
fn_sub
を呼び出していないので当然そうなりますね、という動作です。
これはこれで OK です(今の段階では)。
main から 別の関数を呼び出す
次は main
から fn_sub
を呼び出すようにします。
関数を呼び出すための
["call", "{呼び出したい関数名}"]
という構文を新たにでっちあげて
main
の「関数の処理本体」のところに追加します。
--- a/17_empty_main_sub.vgt.json +++ b/17_empty_main_sub.vgt.json @@ -2,7 +2,10 @@ , ["func", "main" , [] - , [] + , [ + // 関数の処理本体 + ["call", "fn_sub"] + ] ] , ["func", "fn_sub"
これを変換してこういうアセンブリコードを出力してほしい。
call main exit label main push bp cp sp bp # 関数の処理本体 call fn_sub # ← これが追加されるだけ cp bp sp pop bp ret label fn_sub push bp cp sp bp # 関数の処理本体 cp bp sp pop bp ret
おや? これは楽勝なのでは?
やってみましょうか。
--- a/vgcg.rb +++ b/vgcg.rb @@ -20,7 +20,14 @@ def codegen_func_def(rest) alines << "" alines << " # 関数の処理本体" body.each {|stmt| - alines << " # TODO" + stmt_head, *stmt_rest = stmt + case stmt_head + when "call" + fn_name = stmt_rest[0] + alines << " call #{fn_name}" + else + raise not_yet_impl("stmt_head", stmt_head) + end } alines << ""
run.sh
で実行してみると……問題なさそうです。
楽勝すぎてちょっと拍子抜けしてしまいましたが
ともかく、vgtコードで関数呼び出しが書けるようになりました!
以下は終了時の状態です。
================================ reg_a(0) reg_b(0) reg_c(0) zf(0) ---- memory (main) ---- 00 ["call", 5] pc => 02 ["exit"] 03 ["label", "main"] 05 ["push", "bp"] 07 ["cp", "sp", "bp"] 10 ["call", 20] 12 ["cp", "bp", "sp"] 15 ["pop", "bp"] 17 ["ret"] 18 ["label", "fn_sub"] 20 ["push", "bp"] 22 ["cp", "sp", "bp"] 25 ["cp", "bp", "sp"] 28 ["pop", "bp"] 30 ["ret"] ---- memory (stack) ---- 41 0 42 0 43 0 44 0 45 47 46 12 47 49 48 2 sp bp => 49 0 exit