コンパイラ作成(12) 関数の定義と呼出

今回の目標

前回のハロワの時に関数を定義してそれを呼びたいなって思ったんで、今回はそれに挑戦してみるよ。

main()
{
    puts("Hi everyone!");
    puts("Are you ready?");
    sub();
}

sub()
{
    puts("Let's go!");
}
関数の定義

まずは関数の定義から。以前作ったParserクラスのmainメソッドはその名の通りmainって関数にしか対応してなかったけど、これをちょこっと変更して任意の関数名でも大丈夫なようにした。あとはそのメソッドをwhileでぐるぐる回して複数の関数に対応。

  # 関数の構文解析
  def function()
    kind, str = @lex.gettoken
    if kind == TK::EOF then return false end
    if kind != TK::ID then perror end
    funcname = str
    kind, str = @lex.gettoken
    if kind != TK::SYMBOL || str != "(" then perror end
    kind, str = @lex.gettoken
    if kind != TK::SYMBOL || str != ")" then perror end
    codegen (".global "+funcname)
    codegen (funcname+":")

    kind, str = @lex.gettoken
    if kind != TK::SYMBOL || str != "{" then perror end
    while statement do
    end
    codegen "  ret"
    return true
  end

  # programの構文解析
  def program
    begin
      @fout = File.open(@asmfname,"w")
    rescue SystemCallError => e
      puts %Q(class=[#{e.class}] message=[#{e.message}])
    end

    codegen ".intel_syntax noprefix"
    while function do
    end
    genliterals

    @fout.close
    @lex.close
  end

メソッド名をmainからfunctionに変更したよ。それでは早速テスト。

main()
{
    puts("Hi everyone!");
    puts("Are you ready?");
}

sub()
{
    puts("Let's go!");
}

関数subの呼び出しはまだ入れてない。コンパイル結果はどうかな?

.intel_syntax noprefix
.global main
main:
  lea rdi, .L.str
  call puts
  lea rdi, .L.str.1
  call puts
  ret
.global sub
sub:
  lea rdi, .L.str.2
  call puts
  ret
.L.str:
  .asciz  "Hi everyone!"
.L.str.1:
  .asciz  "Are you ready?"
.L.str.2:
  .asciz  "Let's go!"

上手くいったっぽい。

関数の呼出

関数の定義はできたんで次は関数コール。これはprintやputsの処理と大して変わらないから簡単にできそう。

    elsif kind == TK::ID then
      # 関数の処理
      funcname = str
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != "(" then perror end
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != ")" then perror end
      codegen ("  call "+funcname)
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == ";" then return true end
      perror "expected ';' after function"
      return true;

statementメソッドに処理を追加したよ。テストプログラムにsubの呼出を追加してコンパイルしてみる。

~/myc$ ./myc.rb g2.myc 
~/myc$ clang-5.0 g2.s -o g2
~/myc$ ./g2
Hi everyone!
Are you ready?
Let's go!
~/myc$

おお、ちゃんとsub側のputsも処理されてる。

.intel_syntax noprefix
.global main
main:
  lea rdi, .L.str
  call puts
  lea rdi, .L.str.1
  call puts
  call sub
  ret
.global sub
sub:
  lea rdi, .L.str.2
  call puts
  ret
.L.str:
  .asciz  "Hi everyone!"
.L.str.1:
  .asciz  "Are you ready?"
.L.str.2:
  .asciz  "Let's go!"

念のためコンパイル結果も確認してみたけど問題ないね。よしよし。

main()
{
    puts("今日は良い天気です");
    puts("明日も良い天気です");
    sub();
}

sub()
{
    puts("サクラは咲いたかな?");
}

ついでにこんなのでも試してみたよ。

~/myc$ cp g2.myc g3.myc
~/myc$ ./myc.rb g3.myc 
~/myc$ clang-5.0 g3.s -o g3
~/myc$ ./g3
今日は良い天気です
明日も良い天気です
サクラは咲いたかな?
~/myc$

なんだか急にC言語っぽくなってきたよ。(気のせい、気のせい)

問題点

今回ので関数を定義して呼び出すのは一応できるようになったよ。でもまだ不完全で色々問題が残ってる。

  1. returnの様な予約語の関数名をチェックしていない
  2. 関数名の重複をチェックしていない
  3. 引数付きの関数は定義も呼出もできない

この辺は将来的な課題。最初の二つはちゃんとチェックすれば良いだけなんだけど、三つめは色々と難しいよね。