コンパイラ作成(72) char型

今回の目標

char型のサポートを頑張るよ。

// char型

int main()
{
    char ch = 'A';
//  char ch = '\n';
    printf("ch = %c\n",ch);
}

今回は文字リテラルとchar型への代入と参照までだよ。一気にやるのはしんどいからちょっとずつ。

Lexerクラス

まずは文字リテラルを扱えるようにするよ。

      # 文字リテラルの切り出し
      elsif m = @line[@idx,@line.length].match(/^'(.)'/) then
        @idx += m.to_s.length
        str = m[1].to_s.ord.to_s
        return TK::NUMBER, str
      # 文字リテラルの切り出し
      elsif m = @line[@idx,@line.length].match(/^'(\\.)'/) then
        @idx += m.to_s.length
        str = "\"#{m[1].to_s}\"".undump.ord.to_s
        return TK::NUMBER, str

文字リテラル文字コードに変換してTK::NUMBERを返す仕様にした。エスケープシーケンスを処理するためにundumpってのを使ってるよ。Rubyだと簡単にできるけどC言語だとどうしたら良いんだろ。自力でゴリゴリ頑張るしかないのかな。それとも車輪の再発明しなくてもどっかに素敵な関数とかあるんだろうか。

codegen_assign
  # 代入のコード生成
  def codegen_assign(el)
    if el.size != 3 then perror end
    type_r = codegen_el [el[2]]
    if el[0].kind_of?(Array) then
      if el[0][0].str == "*" then
        codegen "  sub  rsp, 8"
        codegen "  push rax"
        type_l = codegen_el [el[0][1]]
        if !is_pointer_type? type_l then
          perror
        end
        type_l = type_l[0,type_l.length-1]
        codegen "  mov  r10, rax"
        codegen "  pop  rax"
        codegen "  add  rsp, 8"
        if type_r == "void*" && is_pointer_type?(type_l) then
          type_r = type_l    # 暗黙の型変換
        elsif type_r == "int" && type_l == "char" then
          type_r = type_l    # 暗黙の型変換
        end
        if type_l != type_r then perror end
        if is_pointer_type? type_l then
          codegen "  mov  qword ptr [r10], rax"
        elsif type_l == "char"
          codegen "  mov  byte ptr [r10], al"
        else
          codegen "  mov  dword ptr [r10], eax"
        end
      else
        perror
      end
    else
      if el[0].kind != TK::ID then perror end
      v = get_var el[0].str
      if v == nil then
        perror "undeclared variable \"" + el[0].str + "\""
      end
      type_l = v[0]
      if type_r == "void*" && is_pointer_type?(type_l) then
        type_r = type_l    # 暗黙の型変換
      elsif type_r == "int" && type_l == "char" then
        type_r = type_l    # 暗黙の型変換
      end
      if type_l != type_r then perror end
      if is_pointer_type? type_l then
        codegen "  mov  qword ptr [rbp - " + v[1].to_s + "], rax"
      elsif type_l == "char"
        codegen "  mov  byte ptr [rbp - " + v[1].to_s + "], al"
      else
        codegen "  mov  dword ptr [rbp - " + v[1].to_s + "], eax"
      end
    end
    return type_l
  end

char型だったら8bitで処理するようにしてるだけ。

codegen_elf
  # 式のコード生成(二項演算の左側被演算子)
  def codegen_elf(operand)
    type = "int"
    if operand.kind_of?(Array) then
      if !operand[0].kind_of?(Array) && operand[0].kind == TK::ID && operand[1].str == "()" then
        type = codegen_func operand
      else
        type = codegen_el operand
      end
    elsif operand.kind == TK::NUMBER then
      codegen "  mov  eax, " + operand.str
    elsif operand.kind == TK::ID then
      v = get_var operand.str
      if v == nil then
        perror "undeclared variable \"" + operand.str + "\""
      end
      type = v[0]
      if is_pointer_type? type then
        codegen "  mov  rax, qword ptr [rbp - " + v[1].to_s + "]"
      elsif type == "char" then
        codegen "  mov  al, byte ptr [rbp - " + v[1].to_s + "]"
      else
        codegen "  mov  eax, dword ptr [rbp - " + v[1].to_s + "]"
      end
    elsif operand.kind == TK::STRING then
      type = "char*"
      label = addliteral operand.str
      codegen "  lea  rax, "+label
    else
      perror
    end
    return type
  end

char型の参照の処理。

codegen_func
  # 関数コールのコード生成
  def codegen_func operand
    rettype = "int"
    f = @functions[operand[0].str]
    if @numuseregs != 0 then
      if @numuseregs % 2 == 1 then codegen "  sub  rsp, 8" end
      (0...@numuseregs).each do |i| codegen "  push #{@regs64[i]}" end
    end
    if f != nil then
      if operand.size - 2 != f[1].size then
        perror "wrong number of parameters"
      end
    end
    (0...operand.size-2).each do |i|
      save = @numuseregs
      @numuseregs = i
      type = codegen_el operand[i+2]
      @numuseregs = save
      if f != nil then
        if f[1][i] == "size_t" then
          codegen "  movsx rax, eax"
          type = f[1][i]
        elsif f[1][i] == "void*" && is_pointer_type?(type) then
          type = f[1][i]
        end
        if type != f[1][i] then perror "incompatible type parameter" end
      end
      if type == "int" || type == "char" then
        codegen "  mov  #{@regs32[i]}, eax"
      elsif type == "size_t"
        codegen "  mov  #{@regs64[i]}, rax"
      elsif is_pointer_type?(type) then
        codegen "  mov  #{@regs64[i]}, rax"
      else
        perror
      end
    end
    if f == nil then
      codegen "  mov  al, 0"
    end
    codegen "  call " + operand[0].str
    if f != nil then
      rettype = f[0]
    end
    if @numuseregs != 0 then
      (0...@numuseregs).reverse_each do |i| codegen "  pop  #{@regs64[i]}" end
      if @numuseregs % 2 == 1 then codegen "  add  rsp, 8" end
    end
    return rettype
  end

printfにchar型を渡すためにここにもちょっと手を入れたよ。

動作テスト

それではいってみるよ。

~/myc$ myc p.myc
~/myc$ ./p
ch = A
~/myc$ 

できたよ。ってことでもういっちょ。

// char型
extern void *malloc(size_t size);
extern void free(void *ptr);
extern void strcpy(char *s1, char *s2);
int main()
{
    char *p = malloc(256);
    strcpy(p,"hat");
    printf("%s%c",p,'\n');
    *p = 'c';
    printf("%s%c",p,'\n');
    *(p+2) = 'p';
    printf("%s%c",p,'\n');
    free(p);
}

エスケープシーケンスの処理のテストだよ。

~/myc$ myc p2.myc
~/myc$ ./p2
hat
cat
cap
~/myc$

動いたよ。\nもちゃんと処理されてるね。今回はここまで。次回はchar型の計算かな。