コンパイラ作成(34) if文
今回の目標
変数関連は飽きたんでif文を追加してみるよ。
// if文 int main() { int i = 0; lp: printf("Hello, World!\n"); i = i + 1; if(i == 5) goto ret; goto lp; ret: return 0; }
一番簡単なif文。まずはここから。
initialize
if文で自動生成するラベル用のデータを追加。
# コンストラクタ def initialize(fname) @fname = fname # ソースファイルのファイル名 @asmfname = fname.sub(/\.myc$/,'.s') # アセンブリコードのファイル名 @exefname = fname.sub(/\.myc$/,'') # 実行ファイル名 # print "asmfname=",@asmfname,"\n" @lex = Lexer.new(@fname) @funcname = nil # 現在処理している関数名 @labelcnt = nil # 自動生成するラベルの個数(関数単位) @literalcnt = 0 # 文字列リテラルの数 @literaltable = [] # 文字列リテラルのリスト @needprint = false # print.oのリンクが必要か @functions = Hash.new # 関数 @lvars = nil # ローカル変数 @lvarsize = nil # スタックに確保する領域のサイズ end
@labelcntってやつね。これでif文が何個もあったときにも対応できるようにするよ。
function
お次はここ。
# 関数の構文解析 def function() @labelcnt = 0 @lvars = Hash.new @lvarsize = 0;
@labelcntを初期化。関数単位で数えて行くよ。
statement
実際にif文を処理してるとこ。
elsif kind == TK::RESERVE && str == "if" then # if文の処理 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 @labelcnt += 1 codegen " jz .LBB_" + @funcname + "_" + @labelcnt.to_s statement codegen ".LBB_" + @funcname + "_" + @labelcnt.to_s + ":" return true;
やってることは単純だね。exprコールして、分岐してる。それと@labelcntを使ってラベルを定義してるだけ。
動作テスト
そいじゃ行くよ。
~/myc$ myc n.myc ~/myc$ ./n Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! ~/myc$
動いた動いた。ちゃんと5回表示されてる。goto文追加したときにやりたかったことがやっとできたよ。
続けてelseもやっちゃおうかと思ったんだけどちょっと厄介なんで今回は諦めたよ。else付きif文を解析するためには先のトークンを読み込まなきゃいけないんだけど、読み込んだトークンがelseじゃなかったときの処理が問題。exprと同じように未処理のトークンを返すようにすれば良いんだけど、あっちょこっちに手を入れないといけないんで面倒なんだよなあ。うーん、もうちょっと悩もう。
それともう一個問題があって、if( a )みたいな比較演算子が無い場合にちゃんと動かないんだよなあ。今のコード生成だとフラグが立たないんでね。cmp eax, 0を入れちゃえば良いんだけど、無条件でいつも入れちゃうのは無駄な気がするんだよなあ。やっぱりちゃんと比較演算子の結果をboolにして、intと区別しないとダメかな。いずれexprに手を入れて型を意識しながらコード生成するようにしないといけないんで、その時この件も対応するかな。なんだか中途半端だけど、とりあえず今日はここまで。