コンパイラ作成(4) 初めてのコンパイル
字句解析もできたので、いよいよコンパイルしてみるよ。って言っても簡単なプログラムだけど。
main() { return 42; }
これが今回用意したテスト用ソース。
早速、プログラミング開始。
fname = ARGV[0] lex = Lexer.new(fname) print ".intel_syntax noprefix\n" parsemain(lex) lex.close
これがメイン部。Lexerクラスのインスタンスを作ってるだけ。実際の処理はparsemainがやってる。
def parsemain(lex) kind, str = lex.gettoken if kind != TK::ID || str != "main" then perror end kind, str = lex.gettoken if kind != TK::SYMBOL || str != "(" then perror end kind, str = lex.gettoken if kind != TK::SYMBOL || str != ")" then perror end print ".global main\n"; print "main:\n"; kind, str = lex.gettoken if kind != TK::SYMBOL || str != "{" then perror end while parsestatement(lex) do end print " ret\n"; end
一般的なちゃんとしたコンパイラだと、構文解析とコード生成は別になってるんだけど、てっとりばやくやりたいんで一緒にしちゃった。やってることは単純でgettokenを呼んで、トークンを1個ずつ見て行ってる。
で、途中に出てくるparsestatementでreturn文の処理をしてる。
def parsestatement(lex) kind, str = lex.gettoken if kind == TK::SYMBOL && str == "}" then return false end if kind == TK::ID && str == "return" then kind, str = lex.gettoken if kind == TK::NUMBER then print " mov rax, ", str, "\n" print " ret\n"; end kind, str = lex.gettoken if kind == TK::SYMBOL && str == ";" then return false end return true; end perror return true; end
今のところmycはreturn文しか受け付けないけど、この関数を拡張して式の計算やなんかをできるようにしてくよ。
def perror puts "Syntax error" exit -1; end
これは構文エラーがあったとき呼ばれるヘルパー関数だけど、不親切だよね。どんなエラーがどこで起きたのか分からないし、エラーが1個起きたらそこで処理を打ち切っちゃってるし。ちゃんとしたコンパイラだと色々やんなきゃだめだよね。まあ当面はこれで良いか。
それじゃコンパイル行ってみるよ。
~/myc$ cat b.myc main() { return 42; } ~/myc$ ./myc.rb b.myc>b.s ~/myc$ cat b.s .intel_syntax noprefix .global main main: mov rax, 42 ret ret ~/myc$ clang-5.0 b.s ~/myc$ ./a.out ~/myc$ echo $? 42 ~/myc$ echo $?
できた!