コンパイラ作成(65) 剰余演算子

今回の目標

久々に演算子を追加するよ。

// 剰余
int main()
{
    printf("17 * 4 = %d\n",17*4);
    printf("17 / 4 = %d\n",17/4);
    printf("17 % 4 = %d\n",17%4);
}

剰余演算子だよ。

gettoken

修正いくよ。

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

演算子を追加。

read_modify_el

modify_elに"%"を追加。

  # 式の最後までのトークンを読み込み、変形する
  def read_modify_el(fkind,fstr,skind,sstr)
    el, kind, str = read_el fkind, fstr, skind, sstr
    puts to_str(el) if $opt_d   # デバッグ用
    el = modify_el_unaryop el, ["+","-"]
    el = modify_el el, ["*","/","%"]
    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   # デバッグ用
    return el, kind, str
  end

優先順位は乗算・除算といっしょだよね。C の演算子の優先順位 - cppreference.comをもう一回見て確かめたよ。

codegen_els

コード生成は除算とほとんど一緒。

  # 式のコード生成(二項演算の右側被演算子)
  def codegen_els(op, operand, type_l)
    if op.str == "+" then
      ostr = "add "
    elsif op.str == "-" then
      ostr = "sub "
    elsif op.str == "*" then
      ostr = "imul"
    elsif op.str == "/" then
      ostr = "idiv"
    elsif op.str == "%" then
      ostr = "idiv"
    elsif op.str == "==" then
      ostr = "cmp "
    elsif op.str == "!=" then
      ostr = "cmp "
    elsif op.str == "<" || op.str == "<" || op.str == ">" || op.str == "<=" || op.str == ">=" then
      ostr = "cmp "
    else
      perror "unknown operator \"" + op.str + "\""
    end

    # 右被演算子を評価
    type_r = "int"
    if operand.kind_of?(Array) then
      if operand[0].size == 2 && operand[0].kind == TK::ID && operand[1].str == "()" then
        codegen "  sub  rsp, 8"
        codegen "  push rax"
        type_r = codegen_func operand
        codegen "  mov  r10d, eax"
        codegen "  pop  rax"
        codegen "  add  rsp, 8"
      else
        codegen "  sub  rsp, 8"
        codegen "  push rax"
        type_r = codegen_el operand
        codegen "  mov  r10d, eax"
        codegen "  pop  rax"
        codegen "  add  rsp, 8"
      end
      str = "r10d"
    elsif operand.kind == TK::ID then
      v = @lvars[operand.str]
      if v == nil then
        perror "undeclared variable \"" + operand.str + "\""
      end
      type_r = v[0]
      if type_r == "char*"
        str = "qword ptr [rbp - " + v[1].to_s + "]"
      else
        str = "dword ptr [rbp - " + v[1].to_s + "]"
      end
    elsif operand.kind == TK::NUMBER then
      str = operand.str
    elsif operand.kind == TK::STRING then
      type_r = "char*"
      label = addliteral operand.str
      codegen "  lea  r10, "+label
      str = "r10"
    else
      perror
    end

    # 型チェック
    if type_l != type_r then
      if type_l == "char*" && type_r == "int" then
        if op.str != "+" && op.str != "-" then
          perror "mismatched types to binary operation"
        end
      elsif type_l == "int" && type_r == "char*" then
        if op.str != "+" && op.str != "-" then
          perror "mismatched types to binary operation"
        end
      else
        perror "mismatched types to binary operation"
      end
    elsif type_l == "char*" then
      perror "mismatched types to binary operation"
    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 == "<" then
      codegen "  " + ostr + " eax, " + str
      codegen "  setl al"
      codegen "  and  eax, 1"
    elsif op.str == ">" then
      codegen "  " + ostr + " eax, " + str
      codegen "  setg al"
      codegen "  and  eax, 1"
    elsif op.str == "<=" then
      codegen "  " + ostr + " eax, " + str
      codegen "  setle al"
      codegen "  and  eax, 1"
    elsif op.str == ">=" then
      codegen "  " + ostr + " eax, " + str
      codegen "  setge al"
      codegen "  and  eax, 1"
    elsif op.str == "*" || op.str == "/" || op.str == "%" then
      if str != "r10d" then codegen "  mov  r10d, " + str end
      codegen "  mov  r11, rdx"
      if op.str == "/" || op.str == "%" then
        codegen "  cdq"
      end
      codegen "  " + ostr + " r10d"
      if op.str == "%" then
        codegen "  mov  eax, edx"
      end
      codegen "  mov  rdx, r11"
    else
      if type_l == "char*" && type_r == "int" then
        if str == op.str then
          codegen "  " + ostr + " rax, " + str
        elsif str == "r10d" then
          codegen "  movsx r10, r10d"
          codegen "  " + ostr + " rax, r10"
        else
          codegen "  mov  r10d, " + str
          codegen "  movsx r10, r10d"
          codegen "  " + ostr + " rax, r10"
        end
      elsif type_l == "int" && type_r == "char*" then
        codegen "  movsx rax, eax"
        codegen "  " + ostr + " rax, " + str
        type_l = "char*"
      else
        codegen "  " + ostr + " eax, " + str
       end
    end
    return type_l
  end

このメソッド大分ごちゃごちゃしてきたなあ。サポートする型を増やすともっとひどくなりそうだよ。うーむ、どうしたもんかな。

動作テスト

そいではテスト。

~/myc$ myc m28.myc
~/myc$ ./m28
17 * 4 = 68
17 / 4 = 4
17 % 4 = 1
~/myc$ 

大丈夫だね。違うのもやってみるよ。

// FizzBuzz
int main()
{
    int i;
    for(i = 1; i <= 30; i = i + 1) {
        if( i % 15 == 0 )
            printf("FizzBuzz ");
        else if( i % 3 == 0 )
            printf("Fizz ");
        else if( i % 5 == 0 )
            printf("Buzz ");
        else
            printf("%d ",i);
    }
    printf("\n");
}
~/myc$ myc m29.myc
~/myc$ ./m29
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz 
~/myc$ 

おお、できたできた。でもこれ%演算子のテストにはあんまりなってないのかな。else ifとかのテストになってるからまあ良いか。
mycも大分C言語っぽくなってきたよ。まだやらなきゃいけないこと多くて全然ゴールは見えないけど、ちょっとずつ頑張ってるよ。