コンパイラ作成(45) break文
今回の目標
今回はberak文だよ。
// break文 int main() { int i = 0, square; for(;;) { square = i * i; if(square >= 150) break; printf("%2d * %2d = %3d\n",i,i,square); i = i + 1; } return 0; }
単純な場合のbreak文。
TK::RESERVE
まずはここ。
@reservedword = [ "return","goto","if","else","for","while","until","do","break", "int","char", "print","puts","printf" ]
この前breakも追加しとけば良かった。
initialize
breakの飛び先のラベルを覚えとく変数を追加。
# コンストラクタ def initialize(fname) @fname = fname # ソースファイルのファイル名 @asmfname = fname.sub(/\.myc$/,'.s') # アセンブリコードのファイル名 =begin @exefname = fname.sub(/\.myc$/,'') # 実行ファイル名 =end @lex = Lexer.new(@fname) # 字句解析 @funcname = nil # 現在処理している関数名 @labelcnt = nil # 自動生成するラベルの個数(関数単位) @literalcnt = 0 # 文字列リテラルの数 @literaltable = [] # 文字列リテラルのリスト =begin @needprint = false # print.oのリンクが必要か =end @functions = Hash.new # 関数 @lvars = nil # ローカル変数 @lvarsize = nil # スタックに確保する領域のサイズ @breaklabel = nil # breakの飛び先のラベル end
最後のbreaklabelってやつね。
while、forの処理部
statementメソッドのwhile、forの処理部でbreaklabelに飛び先を設定するよ。
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 breaklabelsave = @breaklabel @breaklabel = exit_label 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 + ":" @breaklabel = breaklabelsave return kind, str elsif kind == TK::RESERVE && str == "for" then # for文の処理 @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 breaklabelsave = @breaklabel @breaklabel = exit_label 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 + ":" @breaklabel = breaklabelsave return kind, str
元々の値をセーブしておいて、ブロックから抜けたら元に戻すようにしてるよ。そうしないと多重ループのとき困ったちゃんになるからね。
breakの処理部
最後にbreak文の処理をstatementメソッドに追加して終わり。
elsif kind == TK::RESERVE && str == "break" then # break文の処理 if @breaklabel == nil then perror "break not in loop" end codegen " jmp "+ @breaklabel kind, str = @lex.gettoken if kind != TK::SYMBOL || str != ";" then perror "expected ';' after break" end
breaklabelに設定されてるところへ向けてjmpするだけ。ループの中じゃないときはnilが設定されてるんで、その場合はコンパイルエラーにするようチェックしてる。
動作テスト
それじゃ行くよ。
~/myc$ myc n11.myc ~/myc$ ./n11 0 * 0 = 0 1 * 1 = 1 2 * 2 = 4 3 * 3 = 9 4 * 4 = 16 5 * 5 = 25 6 * 6 = 36 7 * 7 = 49 8 * 8 = 64 9 * 9 = 81 10 * 10 = 100 11 * 11 = 121 12 * 12 = 144 ~/myc$
ちゃんと動いたよ。whileの場合も同じようにチェックした。
// break文 int main() { break; // エラー }
コンパイルエラーになる場合。
~/myc$ myc err19.myc err19.myc:3:2 error: break not in loop ~/myc$
ちゃんとエラーになったよ。
// while文 int main() { int i = 1, j; for(;;) { if(i > 9) break; j = 1; for(;;) { if(j > 9) break; printf("%2d ",i*j); j = j + 1; } printf("\n"); i = i + 1; } return 0; }
もうちょっと複雑な場合。多重ループだよ。
~/myc$ myc n14.myc ~/myc$ ./n14 1 2 3 4 5 6 7 8 9 2 4 6 8 10 12 14 16 18 3 6 9 12 15 18 21 24 27 4 8 12 16 20 24 28 32 36 5 10 15 20 25 30 35 40 45 6 12 18 24 30 36 42 48 54 7 14 21 28 35 42 49 56 63 8 16 24 32 40 48 56 64 72 9 18 27 36 45 54 63 72 81 ~/myc$
これも上手くいったよ。break文、最初に考えたときは難しいかと思たんだけど、やってみたらそんなでもなかったよ。C言語の制御文で未サポートはdo-while文と、switch-case文かな。この二つもやればできると思うけど、他にやりたいことがあるんで後回しにするよ。
次回はスタック上に確保するローカル変数の領域が64byte固定になってる件をなんとかしたいなあ。積み残しで宿題になってることが増えてきちゃったけど、ちょっとずつでも片付けてくよ。