コンパイラ作成(18) 式の解析の改良
今回の目標
今回は演算子の優先順位を正しく評価するよう頑張るよ。
main() { print(2 * 3 + 5); print(2 + 3 * 5); }
問題なのは二番目の方で、電卓式計算になっちゃってたやつね。
バグ発見
本題に行く前にもう一点なんだけど、式の解析を組み込んだ時からバグってたのを見つけたよ。
main()
{
print(42);
}
一番簡単な式。これのチェックが漏れててさあ、mycに掛けるとSyntax errorになっちゃってた。今回これも併せて修正するよ。
expr
式の評価部の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 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 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 end else return skind, sstr end end end end
もう一個先のトークンを読み込んで、それが*演算子だったらexpr2を再帰で呼ぶようにした。注意点は、既にeaxに途中までの計算結果が入ってること。これを保存しなきゃいけないんで、push/popのコードを生成するようにしたよ。
動作チェック
さてどうかな。
~/myc$ myc i7.myc ~/myc$ ./i7 11 17 ~/myc$
お、正しい結果になったよ。二番目の式は2+3*5だから17であってるよね。今回の修正だけど、手を付ける前は難しそうだなと思ってたんだけど、やってみたら割と簡単にできたよ。案ずるより産むが易しだね。
今回の修正と同じようにすれば他の演算子の場合も対応できるかな。えーと、C言語の演算子って全部でいくつあったっけ?かなり多いよね。全部サポートするのは大変だよなあ。うーむ、何とか四則演算ぐらいは早めにサポートしたいよ。
テスト用スクリプト
テスト漏れをなくすために、簡単なスクリプトを作ってみたよ。
#!/bin/bash echo c.myc ------------------------- myc c.myc ./c echo f.myc ------------------------- myc f.myc ./f echo g.myc ------------------------- myc g.myc ./g
こんな感じでずらずらっと並べただけだけど、それでもテストが楽になったよ。今は13個並んでるけど今後どんどん増えてくんだろうなあ。
気になる点
前から気になってるんだけど掛算にmul使ってるけど、これimulにした方が良いんだろうか。そもそもmycはマイナスの数は扱えないよなあ。当面、mulで行ってマイナスの数を扱えるようになった時にimulに修正するか。