コンパイラ作成(23) 等価演算子
今回の目標
今回は等号の演算子を追加するよ。
// 等価演算子 int main() { print((3 + 4) * 2 / (9 - 2) == 2); }
等価演算子って言い方あんま使わないよね。
Lexerクラス
SYMBOLの処理のところに”==”を追加するよ。
# operatorの切り出し elsif m = @line[@idx,@line.length].match(/==|=|\+|\-|\*|\/|:|;|\(|\)|\{|\}/) then str = m.to_s @idx += str.length return TK::SYMBOL, str
今までは一文字のSYMBOLにしか対応してなかったんで、ちょっとだけプログラムを弄ったよ。Rubyのmatchが返すのはstringじゃないんで、その点に注意が必要だよ。
expr
続いて式の構文解析を修正。
# 式の構文解析 def expr2(fkind,fstr,skind,sstr) el, kind, str = read_el fkind, fstr, skind, sstr # puts to_str(el) el = modify_el el, ["*","/"] el = modify_el el, ["+","-"] # puts to_str(el) codegen_el el return kind, str end
前回頑張って処理方法を修正したんで、今回はmodify_elをもう一回呼び出すだけで済んだよ。演算子の優先順位には注意が必要。modify_elの順序を間違えちゃうと計算結果がおかしくなっちゃうよ。C の演算子の優先順位 - cppreference.comでちゃんと確認しながらコーディング。
codegen_els
最期にコード生成部に等価演算子の処理を追加しておしまい。
# 式のコード生成(二項演算の右側被演算子) def codegen_els(op, operand) if op.str == "+" then ostr = "add " elsif op.str == "-" then ostr = "sub " elsif op.str == "*" then ostr = "mul " elsif op.str == "/" then ostr = "div " elsif op.str == "==" then ostr = "cmp " else perror "unknown operator \"" + op.str + "\"" end if operand.kind_of?(Array) then if operand[0].size == 2 && operand[0].kind == TK::ID && operand[1].str == "()" then codegen " push rax" codegen " call " + operand[0].str codegen " mov ebx, eax" codegen " pop rax" else codegen " push rax" codegen_el operand codegen " mov ebx, eax" codegen " pop rax" end if op.str == "==" then codegen " " + ostr + " eax, ebx" codegen " sete al" codegen " and eax, 1" elsif op.str == "*" || op.str == "/" then if op.str == "/" then codegen " xor edx, edx" end codegen " " + ostr + " ebx" else codegen " " + ostr + " eax, ebx" end elsif operand.kind == TK::NUMBER then if op.str == "==" then codegen " " + ostr + " eax, " + operand.str codegen " sete al" codegen " and eax, 1" elsif op.str == "*" || op.str == "/" then codegen " mov ebx, " + operand.str if op.str == "/" then codegen " xor edx, edx" end codegen " " + ostr + " ebx" else codegen " " + ostr + " eax, " + operand.str end end end
本来は比較演算の結果はbool型でC99では8bitの独自の型になってるけど、mycはまだ32bit整数しか扱えないんでeaxに結果を格納するコードを生成してるよ。
動作チェック
それじゃコンパイルしてみるよ。
~/myc$ myc i18.myc ~/myc$ ./i18 1 ~/myc$
お、ちゃんとtrueになってる。
.intel_syntax noprefix .global main main: mov eax, 3 add eax, 4 mov ebx, 2 mul ebx push rax mov eax, 9 sub eax, 2 mov ebx, eax pop rax xor edx, edx div ebx cmp eax, 2 sete al and eax, 1 mov edi, eax call print ret
おまけにアセンブリコードも確認してみた。うんうん、思った通りのコードが生成されてる。
前回の変更で演算子の追加は大分楽になったよ。サポートしなきゃいけない比較演算子は残ってるけどそれは後回しにして、次回は別の事したいなあ。