コンパイラ作成(67) for文での変数宣言

今回の目標

前回の続きでfor文の頭での変数宣言だよ。

// for文での変数宣言
extern int puts(char *str);

int main()
{
    int a = 10, b = 20;
    printf("1: a = %d b = %d\n",a,b);
    for(int i = 0; i < 3; i = i +1) {
        int a = 55;
        printf("2: a = %d b = %d\n",a,b);
    }
    int c = 30;
    printf("3: a = %d b = %d c = %d\n",a,b,c);
    a = 100;
    printf("4: a = %d b = %d c = %d\n",a,b,c);
}

処理としては普通の変数宣言と同じなんでそう苦労することなくできると思う。その前にC言語の仕様をチェック。
JIS X 3010:2003 プログラム言語Cの6.8.5繰返し文を参照すると、変数宣言できるのはfor(a;b;c)のaの部分だけだね。それとwhile文では変数宣言はできないんだね。この辺のことは何となくは知ってたけど、細かいところまでは知らなかったよ。

var_decl

新しいメソッドを追加。

  # 変数宣言の処理
  def var_decl(kind, str)
    basetype = str
    loop do
      type = basetype
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == "*" then
        type += str
        kind, str = @lex.gettoken
      end
      if kind != TK::ID then perror end
      print "var "+str+"\n" if $opt_d
      @lvarsize += sizeof(type)
      if check_var str then
        perror "redefinition variable \"" + str +"\""
      end
      set_var str, [type,@lvarsize]
      skind, sstr = @lex.gettoken
      if skind == TK::SYMBOL && sstr == "=" then
        kind, str = expr2 kind, str, skind, sstr;
      else
        kind, str = skind, sstr;
      end
      if kind != TK::SYMBOL || str != "," then break end
    end
    return kind, str
  end

statementの変数宣言の処理部から切り出したよ。for文でもこれを呼べば変数宣言できるようになるはず。ただちょっと問題があるのはfor文の方はstatic変数は宣言できない点。これもさっきの仕様書を読んで仕入れた知識だよ。今のところstatic変数はサポートしてないから関係ないんだけど、頭の片隅に入れとかなきゃいけない注意事項だね。

statement

変数処理部

    elsif kind == TK::TYPE then
      # 変数宣言の処理
      kind, str = var_decl kind, str
      if kind != TK::SYMBOL || str != ";" then
        perror "expected ';' after variables"
      end

新しいメソッドを呼ぶだけ。続いてfor文の処理部。

    elsif kind == TK::RESERVE && str == "for" then
      # for文の処理
      @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
      breaklabelsave = @breaklabel
      @breaklabel = exit_label
      @numblock += 1
      @blocks.unshift "B#{@numblock}#"
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != "(" then perror end
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != ";" then
        if kind == TK::TYPE then
          kind, str = var_decl kind, str
        else
          kind, str = expr kind, str
        end
      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 + ":"
      @blocks.shift
      @breaklabel = breaklabelsave
      return kind, str

こっちもvar_declを呼ぶようにしてる。

動作テスト

ほいじゃテスト。

~/myc$ myc m34.myc
~/myc$ ./m34
1: a = 10 b = 20
2: a = 55 b = 20
2: a = 55 b = 20
2: a = 55 b = 20
3: a = 10 b = 20 c = 30
4: a = 100 b = 20 c = 30
~/myc$

お、上手くいった。さて次回は何やろうかな。