コンパイラ作成(24) 予約語
今回の目標
今回は予約語のチェック。これもっと前にやっとくべきだったよね。
// 予約語のチェック int main() { puts("Error"); } int return() { }
関数名に予約語。
// 予約語のチェック int main() { int: }
ラベルに予約語
// 予約語のチェック int main() { goto for; }
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クラスを作ったときにちゃんと考えてれば必要なかったね。深く考えずその場のノリでコーディングしてるから、後からああすればよかったこうすればよかったってのがどうしても出てきちゃうよ。
しかしさあ、毎日ちょっとずつ作ってるコンパイラ、確かに毎日ちょっとずつ進歩してるんだけど、完全に亀の歩みだね。そこそこのプログラムがコンパイルできるようになるのはいつの日だろうか?