コンパイラ作成(56) char*型+int型の式
今回の目標
型をミックスした式のサポートを頑張るよ。
// char* + int int main() { int i; for(i = 0; i < 10; i = i + 1) puts("Hello, World!" + i); }
前々回、char*型をサポートしたけど、対応が不十分なとこがいくつもあった。で、今回少しだけ改善するよ。
それと今回の目標とは関係ないんだけど、おまけで2件修正するよ。
print、puts
まずはおまけその1。今までprint、putsはstatementメソッドの中で特別扱いしてたんだけど、もうこの処理要らなくなったんで削除することにした。
=begin @reservedword = [ "return","goto","if","else","for","while","until","do","break", "print","puts","printf" ] =end @reservedword = [ "return","goto","if","else","for","while","until","do","break", "printf" ]
予約語から削除。普通の関数として扱うよ。printfに関してはまだ特別扱いのまま。printfは可変長引数なんだけど、exprにはまだこの処理が入ってないからね。
=begin elsif kind == TK::RESERVE && str == "print" then # 組み込み関数printの処理 kind, str = @lex.gettoken if kind != TK::SYMBOL || str != "(" then perror end kind, str = @lex.gettoken kind, str = expr kind, str codegen " mov edi, eax" codegen " call print" if kind != TK::SYMBOL || str != ")" then perror end kind, str = @lex.gettoken if kind != TK::SYMBOL || str != ";" then perror "expected ';' after builtin function" end elsif kind == TK::RESERVE && str == "puts" then # 標準関数putsの処理 kind, str = @lex.gettoken if kind != TK::SYMBOL || str != "(" then perror end kind, str = @lex.gettoken if kind == TK::STRING then label = addliteral str codegen " lea rdi, "+label elsif kind == TK::ID then v = @lvars[str] if v == nil then perror "undeclared variable \"" + str + "\"" end codegen " mov rdi, qword ptr [rbp - " + v[1].to_s + "]" else perror end codegen " call puts" kind, str = @lex.gettoken if kind != TK::SYMBOL || str != ")" then perror end kind, str = @lex.gettoken if kind != TK::SYMBOL || str != ";" then perror "expected ';' after function" end =end
statementから該当の処理を削除。printに関してはかなり初期に作ったんだよなあ。感慨深いものがあるよ。
エラーメッセージ
おまけその2。
int sub(int 123) { }
これをコンパイルしたときのメッセージをデフォルトから分かり易いものに変更した。
# 引数の処理 kind, str = @lex.gettoken loop do if kind == TK::SYMBOL && str == ")" then break end if kind == TK::TYPE then type = str kind, str = @lex.gettoken if kind == TK::SYMBOL && str == "*" then type += str kind, str = @lex.gettoken end if kind != TK::ID then perror "wrong parameter name" end print "para "+str+"\n" if $opt_d size = sizeof type @lvarsize += size parametersize << size if @lvars[str] then perror "redefinition parameter \"" + str +"\"" end @lvars[str] = [type,@lvarsize] else perror end
functionメソッドの引数の処理のところ。
コード生成部
それでは本題に行くよ。
# 式のコード生成 def codegen_el(el) type = "int" if !el[0].kind_of?(Array) && el[0].kind == TK::SYMBOL codegen_unaryop el[0], [el[1]] elsif (el.size > 1) && el[1].str == "=" then codegen_assign el else type = codegen_elf el.shift =begin if type != "int" && el != [] then perror end =end loop do if el == [] then break end op = el.shift operand = el.shift type = codegen_els op, operand, type end end return type end # 式のコード生成(二項演算の右側被演算子) 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 = "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" 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 == "/" then if str != "r10d" then codegen " mov r10d, " + str end codegen " mov r11, rdx" if op.str == "/" then codegen " cdq" end codegen " " + ostr + " r10d" 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
前々回手抜きしたcodegen_elsも型を意識しながら、コード生成するようにしたよ。まず計算式が有効かどうかをチェックしてる。その後、左側被演算子の型と右側被演算子の型を見比べながら、movsx使ったりして計算してるよ。まだ論理に色々と不足があるよ。比較演算に対応してないし、ポインタ同士の引算にも対応してないよ。対応してるのはchar*型とint型の加減算だけ。if( p != 0 )とかは必須だからこの辺は早めに対応したいなあ。
動作テスト
まずはおまけから。
~/myc$ myc i6.myc print.o ~/myc$ ./i6 97 18048 ~/myc$
print関数はちゃんと動いてるよ。
~/myc$ myc o9.myc ~/myc$ ./o9 Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! ~/myc$
puts関数も大丈夫だね。
~/myc$ myc err24.myc err24.myc:1:12 error: wrong parameter name ~/myc$
コンパイルエラーも分かり易いものになったよ。次は本題。
~/myc$ myc o10.myc ~/myc$ ./o10 Hello, World! ello, World! llo, World! lo, World! o, World! , World! World! World! orld! rld! ~/myc$
おお、動いたよ。演算の仕方をちょっと変えてみる。
// char* + int int main() { sub("Hello, World!"); } int sub(char *s) { int i; for(i = 0; i < 10; i = i + 1) puts(s + i); for(i = 9; i >= 0; i = i - 1) puts(i + s); }
文字列リテラルからポインタ変数というかポインタ引数にしてみたよ。
~/myc$ myc o12.myc ~/myc$ ./o12 Hello, World! ello, World! llo, World! lo, World! o, World! , World! World! World! orld! rld! rld! orld! World! World! , World! o, World! lo, World! llo, World! ello, World! Hello, World! ~/myc$
これも上手くいった。次はコンパイルエラーになる場合。
// 無効な型の計算 int main() { int i; for(i = 0; i < 10; i = i +1) puts("apple" + "pen"); }
C言語だからapplepenにはならないよ。
~/myc$ myc err26.myc err26.myc:6:30 error: mismatched types to binary operation ~/myc$
ちゃんとエラーになってる。これで型をミックスした計算の初歩ができるようになったよ。今回の変更をもうちょっと頑張れば任意の型のポインタとの計算ができるようになるのかな。でもその前にやらなきゃいけないこと多いな。char型とかサポートできてないし、そもそもint型以外の変数宣言できないもんなあ。そういや’A'の文字リテラルも扱えないよなあ。あと、char型ってsignedなのかunsignedなのかって問題もあるよなあ。clang先輩はどうだっけ?うーむ、悩ましいなあ。
さて次回は何にするかな。いつも通り簡単にできそうなところから攻めていくか。