コンパイラ作成(35) if-else文

今日の目標

else付きのif文行ってみるよ。

// if文
int main()
{
    int a;
    a = 0;
    if(a == 0)
        printf("%d is zero\n",a);
    else
        printf("%d is not zero\n",a);
    a = 1;
    if(a == 0) {
        printf("%d is zero\n",a);
        printf("%d is zero\n",a);
    }
    else {
        printf("%d is not zero\n",a);
        printf("%d is not zero\n",a);
    }
}

if文二つで、二つ目はブロックのタイプだよ。

statement、function

今回、statementメソッドのインターフェースを変更したよ。

  # 文の構文解析
  def statement(kind, str)
    if kind == TK::RESERVE && str == "return" then
      # return文の処理
      kind, str = @lex.gettoken
      kind, str = expr kind, str
      codegen "  jmp  .RET_" + @funcname
      if kind != TK::SYMBOL || str != ";" then
        perror "expected ';' after return statement"
      end
    elsif kind == TK::RESERVE && str == "goto" then
      # goto文の処理
      kind, str = @lex.gettoken
      if kind == TK::ID then
        codegen "  jmp .LBB_"+ @funcname + "_" + str
      else
        perror "expected identifier"
      end
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != ";" then
        perror "expected ';' after goto"
      end
    elsif kind == TK::RESERVE && str == "if" then
      # if文の処理
      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
      @labelcnt += 1
      e_label = @labelcnt
      codegen "  jz   .LBB_" + @funcname + "_" + @labelcnt.to_s
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == "{" then
        kind, str = @lex.gettoken
        loop do
          kind, str = statement kind, str
          if kind == TK::SYMBOL && str == "}" then
            kind, str = @lex.gettoken
            break
          end
        end
      else
        kind, str = statement kind, str
      end
      if kind == TK::RESERVE && str == "else" then
        @labelcnt += 1
        codegen "  jmp  .LBB_" + @funcname + "_" + @labelcnt.to_s
        codegen ".LBB_" + @funcname + "_" + e_label.to_s + ":"
        kind, str = @lex.gettoken
        if kind == TK::SYMBOL && str == "{" then
          kind, str = @lex.gettoken
          loop do
            kind, str = statement kind, str
            if kind == TK::SYMBOL && str == "}" then
              kind, str = @lex.gettoken
              break
            end
          end
        else
          kind, str = statement kind, str
        end
        codegen ".LBB_" + @funcname + "_" + @labelcnt.to_s + ":"
      else
        codegen ".LBB_" + @funcname + "_" + e_label.to_s + ":"
      end
      return kind, str
    elsif kind == TK::RESERVE && str == "int" then
      # 変数宣言の処理
      loop do
        kind, str = @lex.gettoken
        if kind != TK::ID then perror end
        print "var "+str+"\n" if $opt_d
        @lvarsize += 4
        if @lvars[str] then perror "redefinition variable \"" + str +"\"" end
        @lvars[str] = ["int",@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
      if kind != TK::SYMBOL || str != ";" then
        perror "expected ';' after variables"
      end
    elsif kind == TK::RESERVE && str == "print" then
      # 組み込み関数printの処理
      @needprint = true
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != "(" then perror end
      kind, str = @lex.gettoken
      kind, str = expr kind, str
      codegen "  mov edi, eax"
      codegen "  call print"
      if kind != TK::SYMBOL || str != ")" then perror end
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != ";" then
        perror "expected ';' after builtin function"
      end
    elsif kind == TK::RESERVE && str == "puts" then
      # 標準関数putsの処理
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != "(" then perror end
      kind, str = @lex.gettoken
      if kind == TK::STRING then
        label = addliteral str
        codegen "  lea rdi, "+label
        codegen "  call puts"
      end
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != ")" then perror end
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != ";" then
        perror "expected ';' after function"
      end
    elsif kind == TK::RESERVE && str == "printf" then
      # 標準関数printfの処理
      regs32 = ["edi", "esi","edx","ecx","r8d","r9d"]
      regs64 = ["rdi", "rsi","rdx","rcx","r8", "r9" ]
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != "(" then perror end
      kind, str = @lex.gettoken
      i = 0
      loop do
        if kind == TK::STRING then
          label = addliteral str
          codegen "  lea  "+regs64[i]+", "+label
          kind, str = @lex.gettoken
        else
          kind, str = expr kind, str
          codegen "  mov  "+regs32[i]+", eax"
        end
        if kind == TK::SYMBOL && str == ")" then break end
        if kind != TK::SYMBOL || str != "," then perror end
        kind, str = @lex.gettoken
        i += 1
      end
      codegen "  mov  al, 0"
      codegen "  call printf"
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != ";" then
        perror "expected ';' after function"
      end
    elsif kind == TK::ID then
      # 式/ラベルの処理
      idname = str
      skind, sstr = @lex.gettoken
      if skind == TK::SYMBOL && sstr == ":" then
        # ラベルの処理
        codegen ".LBB_" + @funcname + "_" + idname + ":"
      else
        # 式の処理
        kind, str =expr2 kind, str, skind, sstr
        if kind != TK::SYMBOL || str != ";" then
          perror "expected ';' after expression"
        end
      end
    elsif kind == TK::STRING then
      # 文字列リテラルの処理
      lblstr = addliteral(str)
      kind, str = @lex.gettoken
      if kind != TK::SYMBOL || str != ";" then
        perror "expected ';' after builtin function"
      end
    elsif kind == TK::RESERVE then
       perror "unsupported reserved word"
    else
      kind, str = expr kind, str
      if kind != TK::SYMBOL || str != ";" then
        perror "expected ';' after expression"
      end
    end
    return @lex.gettoken;
  end

  # 関数の構文解析
  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 = @lex.gettoken
    loop do
      kind, str = statement kind, str
      if kind == TK::SYMBOL && str == "}" then break end
    end
    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

exprと同じように処理しなかったトークンを返すようにした。前回の記事でちょこっと書いた件ね。elseの処理自体はifと同じような感じだよ。

動作テスト

今回はあちこち修正したんでまず今までのテストが全部通るかをチェックしたよ。OKだったんでelse付きif文いってみるよ。

~/myc$ myc n2.myc
~/myc$ ./n2
0 is zero
1 is not zero
1 is not zero
~/myc$

うまくいったよ。次はwhileやforかな。あ、その前に比較演算子増やしたいなあ。