コンパイラ作成(19) 括弧
今回の目標
引き続き式の構文解析。今回は括弧だよ。
main() { print((42)); print((4 + 2) * 3); print((4 + 2) + 3); print(5 * (4 + 2)); print(5 + (4 + 2)); print((5 + 7) * (4 + 2)); print((5 + 7) + (4 + 2)); print(5 * (4 + 2) * 3); print(5 + (4 + 2) * 3); print(5 * (4 + 2) + 3); print(5 + (4 + 2) + 3); print(2 + 3 + 5); print(2 * 3 * 5); print(2 + (5 + 7) * (4 + 2)); print(2 + (5 + 7) + (4 + 2)); }
いっぱい用意してみたよ。
expr
簡単にできるかと思ってやってみたら結構難しかったよ。
# 式の構文解析 def expr(fkind,fstr) kind, str = @lex.gettoken return expr2 fkind,fstr,kind,str end # 式の構文解析 def expr2(fkind,fstr,skind,sstr) if fkind == TK::NUMBER then codegen " mov eax, " + fstr 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::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::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 i9.myc ~/myc$ ./i9 42 18 9 30 11 72 18 90 23 33 14 10 30 74 20 ~/myc$
うーん、合ってるのか分かりずらい。式いっぱい並べ過ぎたか。一個一個見てくの面倒なんで同じソースをclangでコンパイルしたやつ作って、二つの出力をdiffで比較したら大丈夫だったよ。しかしさあ、こんだけいろんなパターンでチェックしてみても、これで本当に充分なのか不安なんだよねえ。ちゃんと考えてやらないとやっぱ駄目かあ。
さて次はなにやろうかな。式の構文解析は飽きたからなんか違うことやるか。引算、割算は後回しにしよう。