コンパイラ作成(21) 式中の関数
今回の目標
前に関数の呼び出しできるようにしたけど、式の中に関数書けなかった。今回はその辺を攻めてみるよ。
// 関数の呼び出し int main() { print(answer()); } int answer() { // Answer to the Ultimate Question of Life, the Universe, and Everything return 42; }
そいじゃ行ってみるよ。
関数の返値の型
まずはここから。機能的には新しいことができるわけじゃないんだけど、型を指定できるようにしたよ。これを入れた理由はclangで同じソースをコンパイルした時にワーニングが出てたのが気になったから。
# 関数の構文解析 def function() kind, str = @lex.gettoken if kind == TK::ID && str == "int" then kind, str = @lex.gettoken end 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" @funcname = nil return true end
今んとこ指定できるのはintだけだし、指定されてても単に読み飛ばしてるだけで何もしてないよ。将来はちゃんと指定された型を記録しておいてreturn文でチェックするようにしたいよなあ。ああ、そもそも今はintしか返せないからチェックとか要らないんだった。えーと、その前に型情報をどうやって持つか考えないと駄目だな。intみたいな単純なのは良いけどintを指すポインタの配列とかどうやって表わしたら良いんだろうか。引数にintとdoubleをとってintを返す関数ポインタとか考えてると頭痛くなってくるよ。あれ?関数ポインタを返す関数ってどうやって宣言するんだっけ?昔々、そんなプログラミングをしたことある気がするけど全く思い出せないなあ。なんでそんな変態プログラミングしたんだっけ?
まあ良いか。この件は置いといてとっとと進もう。
expr
てことでexprの修正いくよ。
# 式の構文解析 def expr2(fkind,fstr,skind,sstr) if fkind == TK::NUMBER then codegen " mov eax, " + fstr elsif fkind == TK::ID then idname = fstr # 関数呼出の処理 if skind != TK::SYMBOL || sstr != "(" then perror end skind, sstr = @lex.gettoken if skind != TK::SYMBOL || sstr != ")" then perror end codegen " call "+idname skind, sstr = @lex.gettoken elsif fkind == TK::SYMBOL && fstr == "(" then kind, str = expr skind, sstr if kind != TK::SYMBOL || str != ")" then perror end skind, sstr = @lex.gettoken end loop do if skind == TK::SYMBOL && sstr == "+" then kind, str = @lex.gettoken nkind, nstr = @lex.gettoken if nkind == TK::SYMBOL && nstr == "*" then codegen " push rax" skind, sstr = expr2 kind, str, nkind, nstr codegen " pop rbx" codegen " add eax, ebx" elsif kind == TK::NUMBER then codegen " add eax, " + str skind, sstr = nkind, nstr elsif kind == TK::ID then idname = str # 関数呼出の処理 if nkind != TK::SYMBOL || nstr != "(" then perror end nkind, nstr = @lex.gettoken if nkind != TK::SYMBOL || nstr != ")" then perror end codegen " push rax" codegen " call "+idname codegen " pop rbx" codegen " add eax, ebx" skind, sstr = @lex.gettoken elsif kind == TK::SYMBOL && str == "(" then codegen " push rax" kind, str = expr nkind, nstr if kind != TK::SYMBOL || str != ")" then perror end nkind, nstr = @lex.gettoken loop do if nkind != TK::SYMBOL || nstr != "*" then break end kind, str = @lex.gettoken if kind == TK::NUMBER then codegen " mov ebx, " + str codegen " mul ebx" nkind, nstr = @lex.gettoken elsif kind == TK::SYMBOL && str == "(" then codegen " push rax" nkind, nstr = @lex.gettoken kind, str = expr nkind, nstr if kind != TK::SYMBOL || str != ")" then perror end codegen " mov ebx, eax" codegen " pop rax" codegen " mul ebx" nkind, nstr = @lex.gettoken end end codegen " pop rbx" codegen " add eax, ebx" skind, sstr = nkind, nstr end elsif skind == TK::SYMBOL && sstr == "*" then kind, str = @lex.gettoken if kind == TK::NUMBER then codegen " mov ebx, " + str codegen " mul ebx" skind, sstr = @lex.gettoken elsif kind == TK::ID then idname = str skind, sstr = @lex.gettoken # 関数呼出の処理 if skind != TK::SYMBOL || sstr != "(" then perror end skind, sstr = @lex.gettoken if skind != TK::SYMBOL || sstr != ")" then perror end codegen " push rax" codegen " call "+idname codegen " mov ebx, eax" codegen " pop rax" codegen " mul ebx" skind, sstr = @lex.gettoken elsif kind == TK::SYMBOL && str == "(" then codegen " push rax" kind, str = @lex.gettoken kind, str = expr kind, str codegen " mov rbx, rax" codegen " pop rax" codegen " mul ebx" if kind != TK::SYMBOL || str != ")" then perror end skind, sstr = @lex.gettoken end else return skind, sstr end end end
うーん、ごちゃごちゃ。それになんかコーディングが足りない気がするなあ。バグってるかも。
動作テスト
とりあえずできたとこまでで動かしてみるよ。
~/myc$ myc l.myc ~/myc$ ./l 42 ~/myc$
お、動いた。最初にテストした時、セミコロンが無いってコンパイルエラーが出ちゃって、どこにバグがあるのか分からなくて悩んだんだけどさ、良く見たらテスト用のソースにセミコロンが抜けてるバグがあったよ。まいったねこら。
もうちょっと複雑なのも試してみる。
// 関数の呼び出し int main() { print(10 + answer()); print(10 * answer()); } int answer() { // Answer to the Ultimate Question of Life, the Universe, and Everything return 42; }
これもちゃんと動いたよ。もっと色々テストする必要あるけど、今日のところはここまで。