コンパイラ作成(28) printf
今回の目標
前回ABIについて調べなおしたんで、それを活かしてprintfに挑戦してみるよ。
int main() { printf("Hello, World!\n"); }
printf版のハロワ。
Lexerクラス
まずLexerを二箇所修正。
@reservedword = [ "return","goto","if","else","for","while","until","do", "int","char", "print","puts","printf" ]
予約語にprintfを追加。
# symbolの切り出し elsif m = @line[@idx,@line.length].match(/^[==|=|\+|\-|\*|\/|:|,|;|\(|\)|\{|\}]/) then str = m.to_s @idx += str.length return TK::SYMBOL, str end
カンマを追加。この辺はいつもの修正だね。
statement
ここが肝心なとこ。
elsif kind == TK::RESERVE && str == "printf" then # 標準関数printfの処理 regs32 = ["edi", "esi","edx","ecx","r8d","r9d"] regs64 = ["rdi", "rsi","rdx","rcx","r8", "r9" ] kind, str = @lex.gettoken if kind != TK::SYMBOL || str != "(" then perror end kind, str = @lex.gettoken i = 0 loop do if kind == TK::STRING then label = addliteral str codegen " lea "+regs64[i]+", "+label kind, str = @lex.gettoken else kind, str = expr kind, str codegen " mov "+regs32[i]+", eax" end if kind == TK::SYMBOL && str == ")" then break end if kind != TK::SYMBOL || str != "," then perror end kind, str = @lex.gettoken i += 1 end codegen " mov al, 0" codegen " call printf" kind, str = @lex.gettoken if kind == TK::SYMBOL && str == ";" then return true end perror "expected ';' after function" return true;
printfに対応する処理を追加した。今回の修正はかなり安易で色々問題があるよ。まず、本来は式の処理として文字列リテラルを扱うべきなんだけど、別個に処理しちゃってること。exprの中で処理するためには、型を意識しながらコード生成しなきゃいけないね。うーん、面倒くさそう。二点目は引数6個までしか対応してないこと。7個目からはスタックに積んでいかなきゃなんないよ。三点目はprintfの呼び出し自体をexprの中でやるべきなこと。exprには既に関数コールの処理が入ってるからそれを引数に対応させれば良いんだよなあ。そうすればprintf以外の関数も呼べるようになるしね。この辺はおいおいやっていくつもり。ああ、宿題が溜まってく。
read_el
関数の引数に対応するためにちょっとだけ修正。
# 式の最後までのトークンを読み込む # 返り値 # el : expression's token list # kind : 処理しなかったトークン(セミコロンもしくは閉じ括弧) # str : 処理しなかったトークン(セミコロンもしくは閉じ括弧) def read_el(fkind,fstr,skind,sstr) el = [] if fkind == TK::ID && sstr == "(" then # 関数呼出の処理 sel = [] sel << Token.new(fkind,fstr) sel << Token.new(skind,"()") el << sel skind, sstr = @lex.gettoken if skind != TK::SYMBOL || sstr != ")" then perror end skind, sstr = @lex.gettoken elsif fstr == "(" then # 括弧の処理 fkind, fstr = skind, sstr skind, sstr = @lex.gettoken sel, skind, sstr = read_el fkind, fstr, skind, sstr el << sel skind, sstr = @lex.gettoken else el << Token.new(fkind,fstr) end loop do if skind == TK::EOF then break end if sstr == ";" then break end if sstr == ")" then break end if sstr == "," then break end if sstr == "}" then break end fkind, fstr = skind, sstr skind, sstr = @lex.gettoken if fkind == TK::ID && sstr == "(" then # 関数呼出の処理 sel = [] sel << Token.new(fkind,fstr) sel << Token.new(skind,"()") el << sel skind, sstr = @lex.gettoken if skind != TK::SYMBOL || sstr != ")" then perror end skind, sstr = @lex.gettoken elsif fstr == "(" then # 括弧の処理 fkind, fstr = skind, sstr skind, sstr = @lex.gettoken sel, skind, sstr = read_el fkind, fstr, skind, sstr el << sel skind, sstr = @lex.gettoken else el << Token.new(fkind,fstr) end end return el, skind, sstr end
引数のセパレータのカンマを意識するようにしたよ。ただこの修正だと将来カンマ演算子をサポートするとき問題になるね。通常は演算子扱い、引数の時はセパレータ扱いにするのは簡単じゃないなあ。C言語の仕様はいやらしいなあ。
動作テスト
それじゃ行ってみるよ。
~/myc$ myc m.myc ~/myc$ ./m Hello, World! ~/myc$
おお、できたよ。もうちょっと複雑なのもテストしてみるよ。
int main() { printf("Hello, World!\n"); printf("%s\n","Hello, World!"); printf("%d %d %d\n",1,2,3); printf("%d %d %d %d %d\n",1,2,3,4,5); printf("%d %d %d %d %d\n",1,2,3,4,2*5); }
どうかな。
~/myc$ myc m3.myc ~/myc$ ./m3 Hello, World! Hello, World! 1 2 3 1 2 3 4 5 1 0 3 4 10 ~/myc$
ああ、やっぱり駄目か。最後のprintf間違ってるね。edxに入れた数値が、2*5の計算の時に潰されてる。まあこうなると予想はついてたんだけどね。修正は簡単にできると思うけど、これも宿題にして今日はここまでにしよう。