コンパイラ作成(19) 括弧

今回の目標

引き続き式の構文解析。今回は括弧だよ。

main()
{
    print((42));
    print((4 + 2) * 3);
    print((4 + 2) + 3);
    print(5 * (4 + 2));
    print(5 + (4 + 2));
    print((5 + 7) * (4 + 2));
    print((5 + 7) + (4 + 2));
    print(5 * (4 + 2) * 3);
    print(5 + (4 + 2) * 3);
    print(5 * (4 + 2) + 3);
    print(5 + (4 + 2) + 3);
    print(2 + 3 + 5);
    print(2 * 3 * 5);
    print(2 + (5 + 7) * (4 + 2));
    print(2 + (5 + 7) + (4 + 2));
}

いっぱい用意してみたよ。

expr

簡単にできるかと思ってやってみたら結構難しかったよ。

  # 式の構文解析
  def expr(fkind,fstr)
    kind, str = @lex.gettoken
    return expr2 fkind,fstr,kind,str
  end

  # 式の構文解析
  def expr2(fkind,fstr,skind,sstr)
    if fkind == TK::NUMBER then
      codegen "  mov eax, " + fstr
    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::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::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 i9.myc
~/myc$ ./i9
42
18
9
30
11
72
18
90
23
33
14
10
30
74
20
~/myc$

うーん、合ってるのか分かりずらい。式いっぱい並べ過ぎたか。一個一個見てくの面倒なんで同じソースをclangでコンパイルしたやつ作って、二つの出力をdiffで比較したら大丈夫だったよ。しかしさあ、こんだけいろんなパターンでチェックしてみても、これで本当に充分なのか不安なんだよねえ。ちゃんと考えてやらないとやっぱ駄目かあ。
さて次はなにやろうかな。式の構文解析は飽きたからなんか違うことやるか。引算、割算は後回しにしよう。