コンパイラ作成(12) 関数の定義と呼出
今回の目標
前回のハロワの時に関数を定義してそれを呼びたいなって思ったんで、今回はそれに挑戦してみるよ。
main() { puts("Hi everyone!"); puts("Are you ready?"); sub(); } sub() { puts("Let's go!"); }
関数の定義
まずは関数の定義から。以前作ったParserクラスのmainメソッドはその名の通りmainって関数にしか対応してなかったけど、これをちょこっと変更して任意の関数名でも大丈夫なようにした。あとはそのメソッドをwhileでぐるぐる回して複数の関数に対応。
# 関数の構文解析 def function() kind, str = @lex.gettoken if kind == TK::EOF then return false end if kind != TK::ID then perror end funcname = str kind, str = @lex.gettoken if kind != TK::SYMBOL || str != "(" then perror end kind, str = @lex.gettoken if kind != TK::SYMBOL || str != ")" then perror end codegen (".global "+funcname) codegen (funcname+":") kind, str = @lex.gettoken if kind != TK::SYMBOL || str != "{" then perror end while statement do end codegen " ret" return true end # programの構文解析 def program begin @fout = File.open(@asmfname,"w") rescue SystemCallError => e puts %Q(class=[#{e.class}] message=[#{e.message}]) end codegen ".intel_syntax noprefix" while function do end genliterals @fout.close @lex.close end
メソッド名をmainからfunctionに変更したよ。それでは早速テスト。
main() { puts("Hi everyone!"); puts("Are you ready?"); } sub() { puts("Let's go!"); }
関数subの呼び出しはまだ入れてない。コンパイル結果はどうかな?
.intel_syntax noprefix .global main main: lea rdi, .L.str call puts lea rdi, .L.str.1 call puts ret .global sub sub: lea rdi, .L.str.2 call puts ret .L.str: .asciz "Hi everyone!" .L.str.1: .asciz "Are you ready?" .L.str.2: .asciz "Let's go!"
上手くいったっぽい。
関数の呼出
関数の定義はできたんで次は関数コール。これはprintやputsの処理と大して変わらないから簡単にできそう。
elsif kind == TK::ID then # 関数の処理 funcname = str kind, str = @lex.gettoken if kind != TK::SYMBOL || str != "(" then perror end kind, str = @lex.gettoken if kind != TK::SYMBOL || str != ")" then perror end codegen (" call "+funcname) kind, str = @lex.gettoken if kind == TK::SYMBOL && str == ";" then return true end perror "expected ';' after function" return true;
statementメソッドに処理を追加したよ。テストプログラムにsubの呼出を追加してコンパイルしてみる。
~/myc$ ./myc.rb g2.myc ~/myc$ clang-5.0 g2.s -o g2 ~/myc$ ./g2 Hi everyone! Are you ready? Let's go! ~/myc$
おお、ちゃんとsub側のputsも処理されてる。
.intel_syntax noprefix .global main main: lea rdi, .L.str call puts lea rdi, .L.str.1 call puts call sub ret .global sub sub: lea rdi, .L.str.2 call puts ret .L.str: .asciz "Hi everyone!" .L.str.1: .asciz "Are you ready?" .L.str.2: .asciz "Let's go!"
念のためコンパイル結果も確認してみたけど問題ないね。よしよし。
main() { puts("今日は良い天気です"); puts("明日も良い天気です"); sub(); } sub() { puts("サクラは咲いたかな?"); }
ついでにこんなのでも試してみたよ。
~/myc$ cp g2.myc g3.myc ~/myc$ ./myc.rb g3.myc ~/myc$ clang-5.0 g3.s -o g3 ~/myc$ ./g3 今日は良い天気です 明日も良い天気です サクラは咲いたかな? ~/myc$
なんだか急にC言語っぽくなってきたよ。(気のせい、気のせい)
問題点
今回ので関数を定義して呼び出すのは一応できるようになったよ。でもまだ不完全で色々問題が残ってる。
- returnの様な予約語の関数名をチェックしていない
- 関数名の重複をチェックしていない
- 引数付きの関数は定義も呼出もできない
この辺は将来的な課題。最初の二つはちゃんとチェックすれば良いだけなんだけど、三つめは色々と難しいよね。