コンパイラ作成(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 $?

できた!