コンパイラ作成(37) 非等価演算子

今回の目標

比較演算子を増やすよ。

// 非等価演算子
int main()
{
    int a, b;
    int cmp;
    a = 6;
    b = 7;
    cmp = a != b;
    printf("cmp = %d\n",cmp);
    cmp = a != 6;
    printf("cmp = %d\n",cmp);
}

非等価演算子

Lexerクラス

まずはここから。

      # symbolの切り出し
      elsif m = @line[@idx,@line.length].match(/^(==|!=|=|\+|\-|\*|\/|:|,|;|\(|\)|\{|\})/)  then
        str = m.to_s
        @idx += str.length
        return TK::SYMBOL, str
      end

!=演算子を追加。

expr

次はここ。

  # 式の構文解析
  def expr2(fkind,fstr,skind,sstr)
    el, kind, str = read_el fkind, fstr, skind, sstr
    puts to_str(el) if $opt_d   # デバッグ用
    el = modify_el el, ["*","/"]
    el = modify_el el, ["+","-"]
    el = modify_el el, ["==","!="]
    el = modify_el el, ["="], :r_to_l
    puts to_str(el) if $opt_d   # デバッグ用
    codegen_el el
    return kind, str
  end

こっちにも!=演算子を追加した。

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 "
    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
      str = "ebx"
    elsif operand.kind == TK::ID then
      v = @lvars[operand.str]
      if v == nil then
        perror "undeclared variable \"" + operand.str + "\""
      end
      str = "dword ptr [rbp - " + v[1].to_s + "]"
    elsif operand.kind == TK::NUMBER then
      str = operand.str
    end

    if op.str == "==" then
      codegen "  " + ostr + " eax, " + str
      codegen "  sete al"
      codegen "  and  eax, 1"
    elsif op.str == "!=" then
      codegen "  " + ostr + " eax, " + str
      codegen "  setne al"
      codegen "  and  eax, 1"
    elsif op.str == "*" || op.str == "/" then
      if str != "ebx" then codegen "  mov  ebx, " + str end
      codegen "  mov  r11, rdx"
      if op.str == "/" then
        codegen "  xor  edx, edx"
      end
      codegen "  " + ostr + " ebx"
      codegen "  mov  rdx, r11"
    else
      codegen "  " + ostr + " eax, " + str
    end
  end

今回、このメソッドを書き換えたよ。今まで同じ様なコードを3ヵ所書いてて、無駄だなあと思ってたんだよね。ちょっと考えたら共通化できたよ。

動作テスト

さてどうかな。

~/myc$ myc -d m17.myc
var a
var b
var cmp
[a, =, 6]
[[a, =, 6]]
[b, =, 7]
[[b, =, 7]]
[cmp, =, a, !=, b]
[[cmp, =, [a, !=, b]]]
[cmp]
[cmp]
[cmp, =, a, !=, 6]
[[cmp, =, [a, !=, 6]]]
[cmp]
[cmp]
{"a"=>["int", 4], "b"=>["int", 8], "cmp"=>["int", 12]}
{"main"=>["int", []]}
~/myc$ ./m17
cmp = 1
cmp = 0
~/myc$

上手く動いたよ。今回は本当は関係演算子をがばっと増やしたかったんだけど、一個しか増やせなかったよ。色々気になることとか調べてたりするうちにどんどん時間経っちゃうよ。