コンパイラ作成(43) バグ修正

バグ修正2件

if文、while文、for文とサポートできたけど、テストは不十分だった。でいくつかテストをしてたら案の定バグが見つかったよ。

// while文
int main()
{
    int i = 0;
    puts("start");
    while(i < 5) {
        printf("%d * %d = %d\n",i,i,i*i);
        i = i + 1;

1件目はブロックの閉じ中括弧が無い場合。これこの前も同じようなの修正したよなあ。学習能力ないのか、俺。

// if文
int main()
{
    int a1 = 0, a2 = 42, b, c;
    if( a1 != 0 ) {
        if( a2 == 42 ) {
            b = 1;
        }
        else {
            b = 2;
        }
        c = 1;
    }
    else {
        if( a2 == 42 ) {
            b = 1;
        }
        else {
            b = 2;
        }
        c = 2;
    }
    printf("b = %d\nc = %d\n", b, c);
    return 0;
}

2件目はネストしたif文。if文の処理のところのルーティン見直してたらなんか怪しい雰囲気だったんで、いろんなパターンでテストしてみたらやっぱり駄目な場合があったよ。自動生成するラベルがおかしくて、アセンブル時にエラーになってた。

block

同じこと各所でやってて同じようにTK::EOFのチェックが抜けてた。メソッドを1個追加して、それをコールするようにするよ。

  # blockの構文解析
  def block
    kind, str = @lex.gettoken
    loop do
      kind, str = statement kind, str
      if kind == TK::SYMBOL && str == "}" then break end
      if kind == TK::EOF then
        perror "expected '}'"
      end
    end
    return kind, str
  end

これが切り出したメソッド。

function

blockをコールするよう修正した。

  # 関数の構文解析
  def function()
    @labelcnt = 0
    @lvars = Hash.new
    @lvarsize = 0;
    rettype = nil
    kind, str = @lex.gettoken
    if kind == 	TK::RESERVE && str == "int" then
      rettype = str
      kind, str = @lex.gettoken
    end
    if kind == TK::EOF then return false end
    if kind != TK::ID then perror "expected identifier" 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+":"
    codegen "  push rbp"
    codegen "  mov  rbp, rsp"
    codegen "  sub  rsp, 64"
    kind, str = @lex.gettoken
    if kind != TK::SYMBOL || str != "{" then perror end
    kind, str = block
    codegen ".RET_" + @funcname + ":"
    codegen "  add  rsp, 64"
    codegen "  pop  rbp"
    codegen "  ret"
    if @functions[@funcname] != nil then perror "redefinition of \"" + @funcname + "\"" end
    @functions[@funcname] = [rettype,[]]
    p @lvars if $opt_d   # デバッグ用
    @funcname = nil
    return true
  end
statement

if文、while文、for文の処理部を修正。

    elsif kind == TK::RESERVE && str == "if" then
      # if文の処理
      @labelcnt += 1
      else_label = ".LBB_" + @funcname + "_" + @labelcnt.to_s
      @labelcnt += 1
      exit_label = ".LBB_" + @funcname + "_" + @labelcnt.to_s
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != "(" then perror end
      kind, str = @lex.gettoken
      kind, str = expr kind, str
      if kind != TK::SYMBOL || str != ")" then perror end
      codegen "  jz   " + else_label
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == "{" then
        kind, str = block
        kind, str = @lex.gettoken
      else
        kind, str = statement kind, str
      end
      if kind == TK::RESERVE && str == "else" then
        codegen "  jmp  " + exit_label
        codegen else_label + ":"
        kind, str = @lex.gettoken
        if kind == TK::SYMBOL && str == "{" then
          kind, str = block
          kind, str = @lex.gettoken
        else
          kind, str = statement kind, str
        end
      else
        codegen else_label + ":"
      end
      codegen exit_label + ":"
      return kind, str
    elsif kind == TK::RESERVE && str == "while" then
      # while文の処理
      @labelcnt += 1
      cond_label = ".LBB_" + @funcname + "_" + @labelcnt.to_s
      @labelcnt += 1
      exit_label = ".LBB_" + @funcname + "_" + @labelcnt.to_s
      codegen cond_label + ":"
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != "(" then perror end
      kind, str = @lex.gettoken
      kind, str = expr kind, str
      if kind != TK::SYMBOL || str != ")" then perror end
      codegen "  jz   " + exit_label
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == "{" then
        kind, str = block
        kind, str = @lex.gettoken
      else
        kind, str = statement kind, str
      end
      codegen "  jmp  " + cond_label
      codegen exit_label + ":"
      return kind, str
    elsif kind == TK::RESERVE && str == "for" then
      # while文の処理
      @labelcnt += 1
      cond_label = ".LBB_" + @funcname + "_" + @labelcnt.to_s
      @labelcnt += 1
      cont_label = ".LBB_" + @funcname + "_" + @labelcnt.to_s
      @labelcnt += 1
      body_label = ".LBB_" + @funcname + "_" + @labelcnt.to_s
      @labelcnt += 1
      exit_label = ".LBB_" + @funcname + "_" + @labelcnt.to_s
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != "(" then perror end
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != ";" then
        kind, str = expr kind, str
      end
      if kind != TK::SYMBOL || str != ";" then perror end
      codegen cond_label + ":"
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != ";" then
        kind, str = expr kind, str
        codegen "  jz   " + exit_label
      end
      codegen "  jmp  " + body_label
      if kind != TK::SYMBOL || str != ";" then perror end
      codegen cont_label + ":"
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != ")" then
        kind, str = expr kind, str
      end
      if kind != TK::SYMBOL || str != ")" then perror end
      codegen "  jmp  " + cond_label
      codegen body_label + ":"
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == "{" then
        kind, str = block
        kind, str = @lex.gettoken
      else
        kind, str = statement kind, str
      end
      codegen "  jmp  " + cont_label
      codegen exit_label + ":"
      return kind, str

ここもblockを呼ぶよう修正。if文についてはラベルの生成がネストに対応できてなかったから書き直したよ。

動作テスト

どうかな。まずは1件目のテスト。

~/myc$ myc err16.myc
err16.myc:8:19 error: expected '}'
~/myc$

大丈夫みたいだね。これはwhileの場合だけど、同じようにif文とfor文の場合もチェック。
続いて2件目のテスト。

~/myc$ myc n10.myc
~/myc$ ./n10
b = 1
c = 2
~/myc$

上手くいったよ。いろんなテストを考えるのは結構大変。そういやソース中のif文の数だけテストが必要って昔習ったなあ。あとテストするだけじゃなくて、プログラムコードを見直すのも大事だよね。一人レヴューだね。
次はbreak文かな。ああ、でも制御文に飽きたから何か違うことするか。ちなみにmyc.rbは現在875行だよ。