コンパイラ作成(22) 減算除算
expr
今回、式の処理を大幅に変更することにした。初めはすっきりしてたルーチンが色々やってるうちにごちゃごちゃしちゃったんで、今後の機能追加を考えて一から書き直すよ。
# 式の構文解析で使う構造体 Token = Struct.new(:kind, :str) do def to_s str end end # デバッグ用出力を分かりやすくするためのルーチン def to_str(a) "["+a.map{|x| if x.kind_of?(Array) then to_str(x) else x.to_s end }.join(", ")+"]" end
式の構文解析で使う構造体とその関連のルーチン。
# 式の構文解析 def expr2(fkind,fstr,skind,sstr) el, kind, str = read_el fkind, fstr, skind, sstr # puts to_str(el) el = modify_el el, ["*","/"] # puts to_str(el) codegen_el el return kind, str end # 式の最後までのトークンを読み込む # 返り値 # el : expression' token list # kind : 処理しなかったトークン(セミコロンもしくは閉じ括弧) # str : 処理しなかったトークン(セミコロンもしくは閉じ括弧) def read_el(fkind,fstr,skind,sstr) el = [] if fkind == TK::ID && sstr == "(" then # 関数呼出の処理 sel = [] sel << Token.new(fkind,fstr) sel << Token.new(skind,"()") el << sel skind, sstr = @lex.gettoken if skind != TK::SYMBOL || sstr != ")" then perror end skind, sstr = @lex.gettoken elsif fstr == "(" then # 括弧の処理 fkind, fstr = skind, sstr skind, sstr = @lex.gettoken sel, skind, sstr = read_el fkind, fstr, skind, sstr el << sel skind, sstr = @lex.gettoken else el << Token.new(fkind,fstr) end loop do if skind == TK::EOF then break end if sstr == ";" then break end if sstr == ")" then break end if sstr == "}" then break end fkind, fstr = skind, sstr skind, sstr = @lex.gettoken if fkind == TK::ID && sstr == "(" then # 関数呼出の処理 sel = [] sel << Token.new(fkind,fstr) sel << Token.new(skind,"()") el << sel skind, sstr = @lex.gettoken if skind != TK::SYMBOL || sstr != ")" then perror end skind, sstr = @lex.gettoken elsif fstr == "(" then # 括弧の処理 fkind, fstr = skind, sstr skind, sstr = @lex.gettoken sel, skind, sstr = read_el fkind, fstr, skind, sstr el << sel skind, sstr = @lex.gettoken else el << Token.new(fkind,fstr) end end return el, skind, sstr end # elの変形 # [2, +, 3, *, 5] # =>[2, +, [3, *, 5]] def modify_el(el, opl) mel = [] tel = [] tel << el.shift loop do if el == [] then break end if opl.include? el[0].str then tel << el.shift tel << el.shift else if tel.size == 1 then mel << tel[0] else mel << tel end tel = [] mel << el.shift tel << el.shift end end if tel.size == 1 then mel << tel[0] else mel << tel end return mel end # 式のコード生成 def codegen_el(el) codegen_elf el.shift loop do if el == [] then break end op = el.shift operand = el.shift codegen_els op, operand end end # 式のコード生成(二項演算の左側被演算子) def codegen_elf(operand) if operand.kind_of?(Array) then if operand[0].size == 2 && operand[0].kind == TK::ID && operand[1].str == "()" then codegen " call " + operand[0].str else codegen_el operand end elsif operand.kind == TK::NUMBER then codegen " mov eax, " + operand.str end end # 式のコード生成(二項演算の右側被演算子) 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 " 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 == "*" || 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 == "*" || 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
前のコードは構文解析しながらコード生成もやってたんだけど、今回はそれをやめて式のトークンを全部読み取って、構文解析して、それをもとにコード生成するようにしたよ。
動作チェック
それでは動作チェック行ってみるよ。
~/myc$ myc i16.myc ~/myc$ ./i16 2 ~/myc$
えーと、計算式は(3 + 4) * 2 / (9 - 2)だったから答えは2で合ってるよね。これで四則演算ができるようになった。例によってテストが不十分だけどね。
今回、構文解析とコード生成を分離したわけだけどさ、世の中の真っ当なコンパイラはみんなそうしてるよね。mycも初めからそうすればよかったかな。コード生成まで一緒くたにやるのはやっぱり無理があったよ。でもこれは一遍やってみて分かったことだからそれなりに意味はあったのかな。
今回の変更でコンパイラのコードは586行まで増えたよ。変更前から60行ぐらい増えてるのかな。減算と除算を組み込んでだからそれほど変わってないのかな。まあ、なにはともあれ、今後演算子を増やしていきやすくなったと思うよ。さて次回は何やろうかな。演算子のサポートを増やすか、全然違うことをするか。いつも通りその時の気分で決めるか。