コンパイラ作成(71) ポインタのポインタ型
今回の目標
ポインタのポインタ型に対応するよ。
// 間接参照演算子 int main() { int a = 55; int *p = &a; int **pp = &p; printf("a = %d **pp = %d\n",a,**pp); **pp = 123; printf("a = %d **pp = %d\n",a,**pp); }
変数宣言と連続した単項演算子の扱いが問題。それと2件ばかしおまけがあるよ。
おまけその1
// void型変数 int main() { void a; }
void型の変数は拙いのでエラーになるよう修正するよ。
おまけその2
// int*型 + int*型 int main() { int *p; p + p; }
前回、修正漏れがあってこれのチェックができてなかったよ。
codegen_els
まずはおまけその2の修正行くよ。
# 型チェック if type_l != type_r then if is_pointer_type?(type_l) && type_r == "int" then if op.str != "+" && op.str != "-" then perror "mismatched types to binary operation" end elsif type_l == "int" && is_pointer_type?(type_r) then if op.str != "+" && op.str != "-" then perror "mismatched types to binary operation" end else perror "mismatched types to binary operation" end elsif is_pointer_type? type_l then perror "mismatched types to binary operation" end
型チェックのところが一か所is_pointer_type?になってなかった。
var_decl
続いて本編とおまけその1。
# 変数宣言の処理 def var_decl(kind, str) basetype = str loop do type = basetype kind, str = @lex.gettoken loop do if kind != TK::SYMBOL || str != "*" then break end type += str kind, str = @lex.gettoken end if kind != TK::ID then perror end print "var "+str+"\n" if $opt_d @lvarsize += sizeof(type) if check_var str then perror "redefinition variable \"" + str +"\"" end if type == "void" then perror "invalid type 'void'" end set_var str, [type,@lvarsize] skind, sstr = @lex.gettoken if skind == TK::SYMBOL && sstr == "=" then kind, str = expr2 kind, str, skind, sstr; else kind, str = skind, sstr; end if kind != TK::SYMBOL || str != "," then break end end return kind, str end
loop回して*が連続してても大丈夫なように修正したよ。それとおまけ1のvoid型を弾く処理も追加した。
function
引数の処理部。
# 引数の処理 kind, str = @lex.gettoken loop do if kind == TK::SYMBOL && str == ")" then break end if kind == TK::TYPE then if str == "extern" then perror "invalid 'extern'" end type = str kind, str = @lex.gettoken loop do if kind != TK::SYMBOL || str != "*" then break end type += str kind, str = @lex.gettoken end paratype << type 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 check_var str then perror "redefinition parameter \"" + str +"\"" end if type == "void" then perror "invalid type 'void'" end set_var str, [type,@lvarsize] else perror end kind, str = @lex.gettoken if kind == TK::SYMBOL && str == "," then kind, str = @lex.gettoken end end
var_declと同じように修正。これ処理内容ほとんど共通だよなあ。今後のこと考えたら一個に纏めた方が良いかな。完全に共通じゃないからちょっと面倒かな。
modify_el_unaryop
今回一番修正が面倒だったとこ。
# elの変形(単行演算子の処理) # [-, 2, +, 3] # =>[[-, 2], + 3] # [-, 2, +, -, 3] # =>[[-, 2], + [-, 3]] def modify_el_unaryop(el, opl) mel = [] prev = nil loop do if el.size == 0 then break end x = el.shift if ((prev == nil) || (!prev.kind_of?(Array) && (prev.kind == TK::SYMBOL))) \ && !x.kind_of?(Array) && x.kind == TK::SYMBOL then if opl.include? x.str then tel = [] tel << x x = el.shift if x.kind_of?(Array) then x = modify_el_unaryop x, opl elsif opl.include? x.str then sel = [] loop do sel << x if !opl.include? x.str then break end if el.size == 0 then break end x = el.shift end x = modify_el_unaryop sel, opl if x.size == 1 && x[0].kind_of?(Array) then x = x[0] end end tel << x mel << tel prev = tel else perror end else if x.kind_of?(Array) then x = modify_el_unaryop x, opl end mel << x prev = x end end return mel end
単項演算子が連続した場合の論理が入ってなかったんで追加した。数行追加したけどコーディングより、どういう処理だったか思い出す方が大変だったよ。
動作テスト
それではテスト。まずはおまけ1から。
~/myc$ myc err34.myc err34.myc:5:9 error: invalid type 'void' ~/myc$
よしよし。つづいておまけその2。
~/myc$ myc err36.myc err36.myc:6:10 error: mismatched types to binary operation ~/myc$
ちゃんとエラーになったよ。それでは本編。
~/myc$ myc o25.myc ~/myc$ ./o25 a = 55 **pp = 55 a = 123 **pp = 123 ~/myc$
おお、動いたよ。ちょっと変えてみる。
// 間接参照演算子 void sub(int **pp) { **pp = 123; } int main() { int a = 55; int *p = &a; int **pp = &p; printf("a = %d **pp = %d\n",a,**pp); sub(pp); printf("a = %d **pp = %d\n",a,**pp); }
ポインタのポインタを関数に渡してみたよ。
~/myc$ myc o26.myc ~/myc$ ./o26 a = 55 **pp = 55 a = 123 **pp = 123 ~/myc$
これも大丈夫だね。もういっちょ行くよ。
// コマンドライン int main(int argc, char **argv) { for(int i = 0; i < argc; i = i + 1) printf("argv[%d]=%s\n", i, *(argv+i)); }
これも行けるかな。char**型の例だけど。
~/myc$ myc o27.myc ~/myc$ ./o27 abc 123 12.5 argv[0]=./o27 argv[1]=abc argv[2]=123 argv[3]=12.5 ~/myc$
おおお、ちゃんとコマンドライン引数表示されてる。C言語だと最初は実行ファイル名だったか。最近Rubyばっかり触ってたからすっかり忘れてたよ。さて次回は何やるかな。配列のサポートへ進みたいけど、その前にchar型のサポートをしたいってのもあるなあ。char型はコード生成の修正箇所多そうだよなあ。そんなに難しくはないけど面倒くさそうだな。
ちなみにmyc.rbは今1500行だよ。こんなもんでも結構色々できるもんだね。最終的にどんくらいの行数になるんだろうか。ていうかどうなったらmyc完成って言えるんだろうか。はて、さて。うーむ。