コンパイラ作成(16) ちょっと複雑な式

今回の目標

前回の続きで式の解析だけど、ちょっとだけ複雑な式に挑戦するよ。

main()
{
    print(32 + 47 + 12 + 6);
    print(32 * 47 * 12);
}

複雑って言ってもちょっとだけだよ。それと前回作ったいい加減な式の処理をもうちょっとまともにしたいなあ。

expr

前回雑に作り過ぎたんで反省してちゃんとコーディング。

  # 式の構文解析
  def expr(fkind,fstr)
    kind, str = @lex.gettoken
    if kind == TK::SYMBOL && str == ";" then
      return expr2 fkind,fstr,kind,str
    elsif kind == TK::SYMBOL && str == "+" then
      return expr2 fkind,fstr,kind,str
    elsif kind == TK::SYMBOL && str == "*" then
      return expr2 fkind,fstr,kind,str
    end
  end

  # 式の構文解析
  def expr2(fkind,fstr,skind,sstr)
    if fkind == TK::NUMBER then
      codegen "  mov eax, " + fstr
      loop do
        if skind == TK::SYMBOL && sstr == "+" then
          kind, str = @lex.gettoken
          if kind == TK::NUMBER then
            codegen "  add eax, " + str
          end
        elsif skind == TK::SYMBOL && sstr == "*" then
          kind, str = @lex.gettoken
          if kind == TK::NUMBER then
            codegen "  mov ebx, " + str
            codegen "  mul ebx"
          end
        else
          return skind, sstr
        end
        skind, sstr = @lex.gettoken
#        if kind == TK::SYMBOL && str == ";" then return kind, str end
#        if kind == TK::SYMBOL && str == ")" then return kind, str end
      end
    end
  end

返値で処理しなかったトークン(多分、セミコロンか閉じ括弧)を返すようにしたよ。コンパイルエラーの対処のためにね。前回はこのトークンを握りつぶしちゃってたんで、すごく気持ちの悪いコーディングになってたよ。

statement

こっちはあんまり変わってないよ。

  # 文の構文解析
  def statement()
    kind, str = @lex.gettoken
    if kind == TK::SYMBOL && str == "}" then return false end
    if kind == TK::ID && str == "return" then
      # return文の処理
      kind, str = @lex.gettoken
      kind, str = expr kind, str
      codegen "  ret"
      if kind == TK::SYMBOL && str == ";" then return true end
      perror "expected ';' after return statement"
      return true;
    elsif kind == TK::ID && str == "goto" then
      # goto文の処理
      kind, str = @lex.gettoken
      if kind == TK::ID then
        codegen "  jmp .LBB_"+ @funcname + "_" + str
      end
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == ";" then return true end
      perror "expected ';' after goto"
      return true;
    elsif kind == TK::ID && str == "print" then
      # 組み込み関数printの処理
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != "(" then perror end
      kind, str = @lex.gettoken
      kind, str = expr kind, str
      codegen "  mov edi, eax"
      codegen "  call print"
      if kind != TK::SYMBOL || str != ")" then perror end
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == ";" then return true end
      perror "expected ';' after builtin function"
      return true;
    elsif kind == TK::ID && str == "puts" then
      # 標準関数putsの処理
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != "(" then perror end
      kind, str = @lex.gettoken
      if kind == TK::STRING then
        label = addliteral str
        codegen "  lea rdi, "+label
        codegen "  call puts"
      end
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != ")" then perror end
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == ";" then return true end
      perror "expected ';' after function"
      return true;
    elsif kind == TK::ID then
      # 関数/ラベルの処理
      idname = str
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == ":" then
        # ラベルの処理
        codegen ".LBB_" + @funcname + "_" + idname + ":"
      else
        # 関数呼出の処理
        if kind != TK::SYMBOL || str != "(" then perror end
        kind, str = @lex.gettoken
        if kind != TK::SYMBOL || str != ")" then perror end
        codegen "  call "+idname
        kind, str = @lex.gettoken
        if kind == TK::SYMBOL && str == ";" then return true end
        perror "expected ';' after function"
      end
      return true;
    elsif kind == TK::STRING then
      # 文字列リテラルの処理
      lblstr = addliteral(str)
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == ";" then return true end
      perror "expected ';' after builtin function"
      return true;
    end
    kind, str = expr kind, str
    if kind == TK::SYMBOL && str == ";" then return true end
    perror "expected ';' after expression"
    return true;
  end

exprから返ってきた値をチェックするようにして、エラーをチェックしてる。あと、return文にも式を書けるように変更した。

動作チェック

早速、動かしてみるよ。ソースは最初のやつだよ。どや?

~/myc$ ./myc.rb i6.myc 
~/myc$ clang-5.0 i6.s print.o -o i6
~/myc$ ./i6
97
18048
~/myc$

お、できたよ。計算結果合ってるのか確かめてないけどなんとなくそれっぽいし良いんじゃないかな。(ちゃんと確かめろよ俺)
この後しばらくエラーのあるソースをコンパイルして色々やってみたんだけど、コーディングするより動作チェックする方がどうしても時間掛かるねえ。でもこれサボるとダメだしなあ。

main()
{
    15 + 52;
    print(2 * 3 + 5);
    return 7 * 9;
}

今んとこ式が書けるのはこの三か所。これもチェック。

~/myc$ ./myc.rb i8.myc 
~/myc$ clang-5.0 i8.s print.o -o i8
~/myc$ ./i8
11
~/myc$ echo $?
63
~/myc$

試したらこれもちゃんと動いたよ。

問題点
  • 式の解釈が演算子の優先順位を無視した電卓方式になってる
main()
{
    print(2 * 3 + 5);
    print(2 + 3 * 5);
}

下の式が問題だよ。これを修正するにはもう一個先のトークンまで見に行くようにしないと駄目だよね。簡単に修正できるだろうか。ここまで良く考えずに行きあたりばったりコンパイラを作ってきたけど、壁にぶつかった気分だよ。さてどうしたもんじゃろか。