コンパイラ作成(61) 引数のある関数の修正

バグ

テストをしてたらバグを見つけたよ。

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

これなんだけど、計算結果が合わない。

~/myc$ myc -d i7.myc print.o
[[print, (), [2, *, 3, +, 5]]]
[[print, (), [2, *, 3, +, 5]]]
[[print, (), [2, +, 3, *, 5]]]
[[print, (), [2, +, 3, *, 5]]]
{}
{"main"=>["int", []]}
~/myc$ ./i7
11
25
~/myc$

二番目の式が電卓式計算になっちゃってる。以前はちゃんと計算合ってたのに退化してるじゃん。うーん、なんでだろ。gitから古いバージョンを順番に取り出して調べてみたら、printとputsの処理を削除したときに紛れ込んだバグだった。もう全部exprで処理できると思ったんだけど論理が足りなかったみたい。
あれ式が変形されてないじゃん。あそうか、関数の引数処理するときread_elした後modify_elを呼んでないからか。なるほどなるほど。

expr

原因が分かったのでさっそく修正。

  # 式の構文解析
  def expr2(fkind,fstr,skind,sstr)
    el, kind, str = read_modify_el fkind, fstr, skind, sstr
    codegen_el el
    return kind, str
  end

  # 式の最後までのトークンを読み込み、変形する
  def read_modify_el(fkind,fstr,skind,sstr)
    el, kind, str = read_el fkind, fstr, skind, sstr
    puts to_str(el) if $opt_d   # デバッグ用
    el = modify_el_unaryop el, ["+","-"]
    el = modify_el el, ["*","/"]
    el = modify_el el, ["+","-"]
    el = modify_el el, ["<",">","<=",">="]
    el = modify_el el, ["==","!="]
    el = modify_el el, ["="], :r_to_l
    puts to_str(el) if $opt_d   # デバッグ用
    return el, kind, str
  end

メソッドを二つに分けたよ。

reaf_el

関数呼出の処理部を修正。

  # 式の最後までのトークンを読み込む
  #   返り値
  #     el   : expression's token list
  #     kind : 処理しなかったトークン(セミコロンもしくは閉じ括弧)
  #     str  : 処理しなかったトークン(セミコロンもしくは閉じ括弧)
  def read_el(fkind,fstr,skind,sstr)
    el = []
    if fkind == TK::ID && sstr == "(" then
      # 関数呼出の処理
      sel = []
      sel << Token.new(fkind,fstr)
      sel << Token.new(skind,"()")
      skind, sstr = @lex.gettoken
      loop do
        if skind == TK::SYMBOL && sstr == ")" then break end
        fkind, fstr = skind, sstr
        skind, sstr = @lex.gettoken
        pel, skind, sstr = read_modify_el fkind, fstr, skind, sstr
        sel << pel
        if skind == TK::SYMBOL && sstr == "," then
          skind, sstr = @lex.gettoken
        elsif skind != TK::SYMBOL || sstr != ")" then
          perror
        end
      end
      el << sel
      if skind != TK::SYMBOL || sstr != ")" then perror end
      skind, sstr = @lex.gettoken

read_elを呼んでたのをread_modify_elを呼ぶようにしたよ。これでちゃんと演算子の優先順位に基づいた計算がされるはず。

動作テスト
~/myc$ myc -d i7.myc print.o
[2, *, 3, +, 5]
[[[2, *, 3], +, 5]]
[[print, (), [[[2, *, 3], +, 5]]]]
[[print, (), [[[2, *, 3], +, 5]]]]
[2, +, 3, *, 5]
[[2, +, [3, *, 5]]]
[[print, (), [[2, +, [3, *, 5]]]]]
[[print, (), [[2, +, [3, *, 5]]]]]
{}
{"main"=>["int", []]}
~/myc$ ./i7
11
17
~/myc$

2+3*5の計算がちゃんと17になってる。無事修正できたよ。次は何やろうかな。printfのextern宣言かな。ああ、その前に引数の型チェックか。こっち先にやらないと駄目だな。