コンパイラ作成(43) バグ修正
バグ修正2件
if文、while文、for文とサポートできたけど、テストは不十分だった。でいくつかテストをしてたら案の定バグが見つかったよ。
// while文 int main() { int i = 0; puts("start"); while(i < 5) { printf("%d * %d = %d\n",i,i,i*i); i = i + 1;
1件目はブロックの閉じ中括弧が無い場合。これこの前も同じようなの修正したよなあ。学習能力ないのか、俺。
// if文 int main() { int a1 = 0, a2 = 42, b, c; if( a1 != 0 ) { if( a2 == 42 ) { b = 1; } else { b = 2; } c = 1; } else { if( a2 == 42 ) { b = 1; } else { b = 2; } c = 2; } printf("b = %d\nc = %d\n", b, c); return 0; }
2件目はネストしたif文。if文の処理のところのルーティン見直してたらなんか怪しい雰囲気だったんで、いろんなパターンでテストしてみたらやっぱり駄目な場合があったよ。自動生成するラベルがおかしくて、アセンブル時にエラーになってた。
block
同じこと各所でやってて同じようにTK::EOFのチェックが抜けてた。メソッドを1個追加して、それをコールするようにするよ。
# blockの構文解析 def block kind, str = @lex.gettoken loop do kind, str = statement kind, str if kind == TK::SYMBOL && str == "}" then break end if kind == TK::EOF then perror "expected '}'" end end return kind, str end
これが切り出したメソッド。
function
blockをコールするよう修正した。
# 関数の構文解析 def function() @labelcnt = 0 @lvars = Hash.new @lvarsize = 0; rettype = nil kind, str = @lex.gettoken if kind == TK::RESERVE && str == "int" then rettype = str kind, str = @lex.gettoken end if kind == TK::EOF then return false end if kind != TK::ID then perror "expected identifier" end @funcname = str kind, str = @lex.gettoken if kind != TK::SYMBOL || str != "(" then perror end kind, str = @lex.gettoken if kind != TK::SYMBOL || str != ")" then perror end codegen ".global "+@funcname codegen @funcname+":" codegen " push rbp" codegen " mov rbp, rsp" codegen " sub rsp, 64" kind, str = @lex.gettoken if kind != TK::SYMBOL || str != "{" then perror end kind, str = block codegen ".RET_" + @funcname + ":" codegen " add rsp, 64" codegen " pop rbp" codegen " ret" if @functions[@funcname] != nil then perror "redefinition of \"" + @funcname + "\"" end @functions[@funcname] = [rettype,[]] p @lvars if $opt_d # デバッグ用 @funcname = nil return true end
statement
if文、while文、for文の処理部を修正。
elsif kind == TK::RESERVE && str == "if" then # if文の処理 @labelcnt += 1 else_label = ".LBB_" + @funcname + "_" + @labelcnt.to_s @labelcnt += 1 exit_label = ".LBB_" + @funcname + "_" + @labelcnt.to_s kind, str = @lex.gettoken if kind != TK::SYMBOL || str != "(" then perror end kind, str = @lex.gettoken kind, str = expr kind, str if kind != TK::SYMBOL || str != ")" then perror end codegen " jz " + else_label kind, str = @lex.gettoken if kind == TK::SYMBOL && str == "{" then kind, str = block kind, str = @lex.gettoken else kind, str = statement kind, str end if kind == TK::RESERVE && str == "else" then codegen " jmp " + exit_label codegen else_label + ":" kind, str = @lex.gettoken if kind == TK::SYMBOL && str == "{" then kind, str = block kind, str = @lex.gettoken else kind, str = statement kind, str end else codegen else_label + ":" end codegen exit_label + ":" return kind, str elsif kind == TK::RESERVE && str == "while" then # while文の処理 @labelcnt += 1 cond_label = ".LBB_" + @funcname + "_" + @labelcnt.to_s @labelcnt += 1 exit_label = ".LBB_" + @funcname + "_" + @labelcnt.to_s codegen cond_label + ":" kind, str = @lex.gettoken if kind != TK::SYMBOL || str != "(" then perror end kind, str = @lex.gettoken kind, str = expr kind, str if kind != TK::SYMBOL || str != ")" then perror end codegen " jz " + exit_label kind, str = @lex.gettoken if kind == TK::SYMBOL && str == "{" then kind, str = block kind, str = @lex.gettoken else kind, str = statement kind, str end codegen " jmp " + cond_label codegen exit_label + ":" return kind, str elsif kind == TK::RESERVE && str == "for" then # while文の処理 @labelcnt += 1 cond_label = ".LBB_" + @funcname + "_" + @labelcnt.to_s @labelcnt += 1 cont_label = ".LBB_" + @funcname + "_" + @labelcnt.to_s @labelcnt += 1 body_label = ".LBB_" + @funcname + "_" + @labelcnt.to_s @labelcnt += 1 exit_label = ".LBB_" + @funcname + "_" + @labelcnt.to_s kind, str = @lex.gettoken if kind != TK::SYMBOL || str != "(" then perror end kind, str = @lex.gettoken if kind != TK::SYMBOL || str != ";" then kind, str = expr kind, str end if kind != TK::SYMBOL || str != ";" then perror end codegen cond_label + ":" kind, str = @lex.gettoken if kind != TK::SYMBOL || str != ";" then kind, str = expr kind, str codegen " jz " + exit_label end codegen " jmp " + body_label if kind != TK::SYMBOL || str != ";" then perror end codegen cont_label + ":" kind, str = @lex.gettoken if kind != TK::SYMBOL || str != ")" then kind, str = expr kind, str end if kind != TK::SYMBOL || str != ")" then perror end codegen " jmp " + cond_label codegen body_label + ":" kind, str = @lex.gettoken if kind == TK::SYMBOL && str == "{" then kind, str = block kind, str = @lex.gettoken else kind, str = statement kind, str end codegen " jmp " + cont_label codegen exit_label + ":" return kind, str
ここもblockを呼ぶよう修正。if文についてはラベルの生成がネストに対応できてなかったから書き直したよ。
動作テスト
どうかな。まずは1件目のテスト。
~/myc$ myc err16.myc err16.myc:8:19 error: expected '}' ~/myc$
大丈夫みたいだね。これはwhileの場合だけど、同じようにif文とfor文の場合もチェック。
続いて2件目のテスト。
~/myc$ myc n10.myc ~/myc$ ./n10 b = 1 c = 2 ~/myc$
上手くいったよ。いろんなテストを考えるのは結構大変。そういやソース中のif文の数だけテストが必要って昔習ったなあ。あとテストするだけじゃなくて、プログラムコードを見直すのも大事だよね。一人レヴューだね。
次はbreak文かな。ああ、でも制御文に飽きたから何か違うことするか。ちなみにmyc.rbは現在875行だよ。