コンパイラ作成(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

おまけにアセンブリコードも確認してみた。うんうん、思った通りのコードが生成されてる。
前回の変更で演算子の追加は大分楽になったよ。サポートしなきゃいけない比較演算子は残ってるけどそれは後回しにして、次回は別の事したいなあ。