コンパイラ作成(21) 式中の関数

今回の目標

前に関数の呼び出しできるようにしたけど、式の中に関数書けなかった。今回はその辺を攻めてみるよ。

// 関数の呼び出し
int main()
{
    print(answer());
}

int answer()
{
    //  Answer to the Ultimate Question of Life, the Universe, and Everything
    return 42;
}

そいじゃ行ってみるよ。

関数の返値の型

まずはここから。機能的には新しいことができるわけじゃないんだけど、型を指定できるようにしたよ。これを入れた理由はclangで同じソースをコンパイルした時にワーニングが出てたのが気になったから。

  # 関数の構文解析
  def function()
    kind, str = @lex.gettoken
    if kind == 	TK::ID && str == "int" then
      kind, str = @lex.gettoken
    end
    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"
    @funcname = nil
    return true
  end

今んとこ指定できるのはintだけだし、指定されてても単に読み飛ばしてるだけで何もしてないよ。将来はちゃんと指定された型を記録しておいてreturn文でチェックするようにしたいよなあ。ああ、そもそも今はintしか返せないからチェックとか要らないんだった。えーと、その前に型情報をどうやって持つか考えないと駄目だな。intみたいな単純なのは良いけどintを指すポインタの配列とかどうやって表わしたら良いんだろうか。引数にintとdoubleをとってintを返す関数ポインタとか考えてると頭痛くなってくるよ。あれ?関数ポインタを返す関数ってどうやって宣言するんだっけ?昔々、そんなプログラミングをしたことある気がするけど全く思い出せないなあ。なんでそんな変態プログラミングしたんだっけ?
まあ良いか。この件は置いといてとっとと進もう。

expr

てことでexprの修正いくよ。

  # 式の構文解析
  def expr2(fkind,fstr,skind,sstr)
    if fkind == TK::NUMBER then
      codegen "  mov eax, " + fstr
    elsif fkind == TK::ID then
      idname = fstr
      # 関数呼出の処理
      if skind != TK::SYMBOL || sstr != "(" then perror end
      skind, sstr = @lex.gettoken
      if skind != TK::SYMBOL || sstr != ")" then perror end
      codegen "  call "+idname
      skind, sstr = @lex.gettoken
    elsif fkind == TK::SYMBOL && fstr == "(" then
      kind, str = expr skind, sstr
      if kind != TK::SYMBOL || str != ")" then perror end
      skind, sstr = @lex.gettoken
    end
    loop do
      if skind == TK::SYMBOL && sstr == "+" then
        kind, str = @lex.gettoken
        nkind, nstr = @lex.gettoken
        if nkind == TK::SYMBOL && nstr == "*" then
          codegen "  push rax"
          skind, sstr = expr2 kind, str, nkind, nstr
          codegen "  pop  rbx"
          codegen "  add  eax, ebx"
        elsif kind == TK::NUMBER then
          codegen "  add eax, " + str
          skind, sstr = nkind, nstr
        elsif kind == TK::ID then
          idname = str
          # 関数呼出の処理
          if nkind != TK::SYMBOL || nstr != "(" then perror end
          nkind, nstr = @lex.gettoken
          if nkind != TK::SYMBOL || nstr != ")" then perror end
          codegen "  push rax"
          codegen "  call "+idname
          codegen "  pop  rbx"
          codegen "  add  eax, ebx"
          skind, sstr = @lex.gettoken
        elsif kind == TK::SYMBOL && str == "(" then
          codegen "  push rax"
          kind, str = expr nkind, nstr
          if kind != TK::SYMBOL || str != ")" then perror end
          nkind, nstr = @lex.gettoken
          loop do
            if nkind != TK::SYMBOL || nstr != "*" then break end
            kind, str = @lex.gettoken
            if kind == TK::NUMBER then
              codegen "  mov ebx, " + str
              codegen "  mul ebx"
              nkind, nstr = @lex.gettoken
            elsif kind == TK::SYMBOL && str == "(" then
              codegen "  push rax"
              nkind, nstr = @lex.gettoken
              kind, str = expr nkind, nstr
              if kind != TK::SYMBOL || str != ")" then perror end
              codegen "  mov  ebx, eax"
              codegen "  pop  rax"
              codegen "  mul  ebx"
              nkind, nstr = @lex.gettoken
            end
          end
          codegen "  pop  rbx"
          codegen "  add  eax, ebx"
          skind, sstr = nkind, nstr
        end
      elsif skind == TK::SYMBOL && sstr == "*" then
        kind, str = @lex.gettoken
        if kind == TK::NUMBER then
          codegen "  mov ebx, " + str
          codegen "  mul ebx"
          skind, sstr = @lex.gettoken
        elsif kind == TK::ID then
          idname = str
          skind, sstr = @lex.gettoken
          # 関数呼出の処理
          if skind != TK::SYMBOL || sstr != "(" then perror end
          skind, sstr = @lex.gettoken
          if skind != TK::SYMBOL || sstr != ")" then perror end
          codegen "  push rax"
          codegen "  call "+idname
          codegen "  mov  ebx, eax"
          codegen "  pop  rax"
          codegen "  mul ebx"
          skind, sstr = @lex.gettoken
        elsif kind == TK::SYMBOL && str == "(" then
          codegen "  push rax"
          kind, str = @lex.gettoken
          kind, str = expr kind, str
          codegen "  mov  rbx, rax"
          codegen "  pop  rax"
          codegen "  mul ebx"
          if kind != TK::SYMBOL || str != ")" then perror end
          skind, sstr = @lex.gettoken
        end
      else
        return skind, sstr
      end
    end
  end

うーん、ごちゃごちゃ。それになんかコーディングが足りない気がするなあ。バグってるかも。

動作テスト

とりあえずできたとこまでで動かしてみるよ。

~/myc$ myc l.myc 
~/myc$ ./l
42
~/myc$

お、動いた。最初にテストした時、セミコロンが無いってコンパイルエラーが出ちゃって、どこにバグがあるのか分からなくて悩んだんだけどさ、良く見たらテスト用のソースにセミコロンが抜けてるバグがあったよ。まいったねこら。
もうちょっと複雑なのも試してみる。

// 関数の呼び出し
int main()
{
    print(10 + answer());
    print(10 * answer());
}

int answer()
{
    //  Answer to the Ultimate Question of Life, the Universe, and Everything
    return 42;
}

これもちゃんと動いたよ。もっと色々テストする必要あるけど、今日のところはここまで。