コンパイラ作成(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型の計算かな。