コンパイラ作成(45) break文

今回の目標

今回はberak文だよ。

// break文
int main()
{
    int i = 0, square;
    for(;;) {
        square = i * i;
        if(square >= 150) break;
        printf("%2d * %2d = %3d\n",i,i,square);
        i = i + 1;
    }
    return 0;
}

単純な場合のbreak文。

TK::RESERVE

まずはここ。

    @reservedword = [
      "return","goto","if","else","for","while","until","do","break",
      "int","char",
      "print","puts","printf"
    ]

この前breakも追加しとけば良かった。

initialize

breakの飛び先のラベルを覚えとく変数を追加。

  # コンストラクタ
  def initialize(fname)
    @fname = fname                        # ソースファイルのファイル名
    @asmfname = fname.sub(/\.myc$/,'.s')  # アセンブリコードのファイル名
=begin
    @exefname = fname.sub(/\.myc$/,'')    # 実行ファイル名
=end
    @lex = Lexer.new(@fname)              # 字句解析
    @funcname = nil                       # 現在処理している関数名
    @labelcnt = nil                       # 自動生成するラベルの個数(関数単位)
    @literalcnt = 0                       # 文字列リテラルの数
    @literaltable = []                    # 文字列リテラルのリスト
=begin
    @needprint = false                    # print.oのリンクが必要か
=end
    @functions = Hash.new                 # 関数
    @lvars = nil                          # ローカル変数
    @lvarsize = nil                       # スタックに確保する領域のサイズ
    @breaklabel = nil                     # breakの飛び先のラベル
  end

最後のbreaklabelってやつね。

while、forの処理部

statementメソッドのwhile、forの処理部でbreaklabelに飛び先を設定するよ。

    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
      breaklabelsave = @breaklabel
      @breaklabel = exit_label
      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 + ":"
      @breaklabel = breaklabelsave
      return kind, str
    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
      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 + ":"
      @breaklabel = breaklabelsave
      return kind, str

元々の値をセーブしておいて、ブロックから抜けたら元に戻すようにしてるよ。そうしないと多重ループのとき困ったちゃんになるからね。

breakの処理部

最後にbreak文の処理をstatementメソッドに追加して終わり。

    elsif kind == TK::RESERVE && str == "break" then
      # break文の処理
      if @breaklabel == nil then
        perror "break not in loop"
      end
      codegen "  jmp  "+ @breaklabel
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != ";" then
        perror "expected ';' after break"
      end

breaklabelに設定されてるところへ向けてjmpするだけ。ループの中じゃないときはnilが設定されてるんで、その場合はコンパイルエラーにするようチェックしてる。

動作テスト

それじゃ行くよ。

~/myc$ myc n11.myc
~/myc$ ./n11
 0 *  0 =   0
 1 *  1 =   1
 2 *  2 =   4
 3 *  3 =   9
 4 *  4 =  16
 5 *  5 =  25
 6 *  6 =  36
 7 *  7 =  49
 8 *  8 =  64
 9 *  9 =  81
10 * 10 = 100
11 * 11 = 121
12 * 12 = 144
~/myc$ 

ちゃんと動いたよ。whileの場合も同じようにチェックした。

// break文
int main()
{
    break;    // エラー
}

コンパイルエラーになる場合。

~/myc$ myc err19.myc
err19.myc:3:2 error: break not in loop
~/myc$ 

ちゃんとエラーになったよ。

// while文
int main()
{
    int i = 1, j;
    for(;;) {
        if(i > 9) break;
        j = 1;
        for(;;) {
            if(j > 9) break;
            printf("%2d ",i*j);
            j = j + 1;
        }
        printf("\n");
        i = i + 1;
    }
    return 0;
}

もうちょっと複雑な場合。多重ループだよ。

~/myc$ myc n14.myc
~/myc$ ./n14
 1  2  3  4  5  6  7  8  9 
 2  4  6  8 10 12 14 16 18 
 3  6  9 12 15 18 21 24 27 
 4  8 12 16 20 24 28 32 36 
 5 10 15 20 25 30 35 40 45 
 6 12 18 24 30 36 42 48 54 
 7 14 21 28 35 42 49 56 63 
 8 16 24 32 40 48 56 64 72 
 9 18 27 36 45 54 63 72 81 
~/myc$

これも上手くいったよ。break文、最初に考えたときは難しいかと思たんだけど、やってみたらそんなでもなかったよ。C言語の制御文で未サポートはdo-while文と、switch-case文かな。この二つもやればできると思うけど、他にやりたいことがあるんで後回しにするよ。
次回はスタック上に確保するローカル変数の領域が64byte固定になってる件をなんとかしたいなあ。積み残しで宿題になってることが増えてきちゃったけど、ちょっとずつでも片付けてくよ。