コンパイラ作成(24) 予約語

今回の目標

今回は予約語のチェック。これもっと前にやっとくべきだったよね。

// 予約語のチェック
int main()
{
    puts("Error");
}

int return()
{
}

関数名に予約語

// 予約語のチェック
int main()
{
int:
}

ラベルに予約語

// 予約語のチェック
int main()
{
    goto for;
}

goto文に予約語。この3パターンのチェックをしてコンパイルエラーになるように頑張るよ。

TK::RESERVE

まずはここから。

# トークンの種類 enumの代わり
module TK
  EOF     = 1    # End of file
  ID      = 2    # Identifier
  NUMBER  = 3    # Number
  SYMBOL  = 4    # Symbol
  STRING  = 5    # String
  RESERVE = 6    # Reserved word
  UNKNOWN = 7    # Unkown token
end

トークンの種類を一個増やしたよ。

予約語のリスト

チェックするときに使うデータのArray。

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

これをLexerクラスのinitializeメソッドに追加。とりあえずこんだけリストに入れてみたけど、C言語予約語はもっといっぱいあるんだけどね。これだけでもサポートするの大変そう。全部できたら将来頑張ってもっと増やそうと思ってる。C言語にないuntilもなんとなく入れてみたよ。ところでさ、予約語とキーワードって別ものなんだね。予約語 - Wikipedia。そう言えば昔fortranでDO=10とかやってみたことあったなあ。コンパイラ作ってると普段は深く考えてないことに気付かせられたりするね。
printとputsも便宜的に予約語の扱いにしてるよ。これはまだ関数宣言ができないから。それに引数のある関数の呼び出しもできないしね。この辺りがちゃんとできるようになったら、予約語から削除するつもりだよ。

gettoken

gettokenのIDの処理を変更。

      # identifireの切り出し
      if @line[@idx].match(/\p{alpha}/) then
        str += @line[@idx]
        @idx += 1
        loop do
          if !@line[@idx] then break end
          if !@line[@idx].match(/\p{ascii}/) then break end
          if !@line[@idx].match(/\p{alnum}/) then break end
          str += @line[@idx]
          @idx += 1
        end
        if @reservedword.include?(str) then
          return TK::RESERVE, str
        else
          return TK::ID, str
        end

やってることは単純。ここまでで予約語の場合TK::RESERVEが返るようになったよ。

Parserクラスのfunction

TK::RESERVEに対応してちょこちょこっと修正。

  # 関数の構文解析
  def function()
    kind, str = @lex.gettoken
    if kind == 	TK::RESERVE && str == "int" then
      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+":"
    kind, str = @lex.gettoken
    if kind != TK::SYMBOL || str != "{" then perror end
    while statement do
    end
    codegen "  ret"
    @funcname = nil
    return true
  end

エラーメッセージもそれっぽいのにしてみたよ。

Parserクラスのstatement
  # 文の構文解析
  def statement()
    kind, str = @lex.gettoken
    if kind == TK::SYMBOL && str == "}" then return false end
    if kind == TK::RESERVE && str == "return" then
      # return文の処理
      kind, str = @lex.gettoken
      kind, str = expr kind, str
      codegen "  ret"
      if kind == TK::SYMBOL && str == ";" then return true end
      perror "expected ';' after return statement"
      return true;
    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 return true end
      perror "expected ';' after goto"
      return true;
    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 return true end
      perror "expected ';' after builtin function"
      return true;
    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 return true end
      perror "expected ';' after function"
      return true;
    elsif kind == TK::ID then
      # 関数/ラベルの処理
      idname = str
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == ":" then
        # ラベルの処理
        codegen ".LBB_" + @funcname + "_" + idname + ":"
      else
        # 関数呼出の処理
        if kind != TK::SYMBOL || str != "(" then perror end
        kind, str = @lex.gettoken
        if kind != TK::SYMBOL || str != ")" then perror end
        codegen "  call "+idname
        kind, str = @lex.gettoken
        if kind == TK::SYMBOL && str == ";" then return true end
        perror "expected ';' after function"
      end
      return true;
    elsif kind == TK::STRING then
      # 文字列リテラルの処理
      lblstr = addliteral(str)
      kind, str = @lex.gettoken
      if kind == TK::SYMBOL && str == ";" then return true end
      perror "expected ';' after builtin function"
      return true;
    end
    if kind == TK::RESERVE then perror "unsupported reserved word" end
    kind, str = expr kind, str
    if kind == TK::SYMBOL && str == ";" then return true end
    perror "expected ';' after expression"
    return true;
  end

結構あっちこっち修正が必要だったよ。

動作チェック

三本立てでチェックするよ。

~/myc$ myc err5.myc
err5.myc:7:4 error: expected identifier
~/myc$ myc err6.myc
err6.myc:3:2 error: unsupported reserved word
~/myc$ myc err7.myc
err7.myc:4:9 error: expected identifier
~/myc$

上手くいったよ。上から関数定義、ラベル、goto文の場合ね。二番目のエラーメッセージはまだ関数内でintが使えないんでこうなってるよ。将来、変数が宣言できるようになったら、別のメッセージになるんだろうね。ああ、早く変数サポートしたいなあ。
今回は修正箇所が多かったけど、最初にLexerクラスを作ったときにちゃんと考えてれば必要なかったね。深く考えずその場のノリでコーディングしてるから、後からああすればよかったこうすればよかったってのがどうしても出てきちゃうよ。
しかしさあ、毎日ちょっとずつ作ってるコンパイラ、確かに毎日ちょっとずつ進歩してるんだけど、完全に亀の歩みだね。そこそこのプログラムがコンパイルできるようになるのはいつの日だろうか?