C コンパイラをつくってみる (2)
低レイヤを知りたい人のためのCコンパイラ作成入門 ステップ1-3を参考にしています。
整数1つからなる言語
前回の、整数1つを a0
レジスタに入れて OS に返すアセンブリをもとにする。
ソースコード(ここではコマンドライン引数)として整数1つを受け取り、それを返すことのできる RISC-V アセンブリを出力する。
std::cout << ".global main\n" "\n" "main:\n" " li a0, " << argv[1] << "\n" " ret\n" << std::endl;
こんな感じにした。
整数の加算・減算
この言語の機能に、加算と減算を入れる。
加算と減算には、 RV32I の即値加算 addi
を使う。即値減算はない *1 。
// p は、ソースとなる文字列中の文字へのポインタ if(*p == '+'){ p++; std::cout << " addi a0, a0, " << std::strtol(p, &p, 10) << "\n"; continue; } if(*p == '-'){ p++; std::cout << " addi a0, a0, -" << std::strtol(p, &p, 10) << "\n"; continue; }
Tokenizer を導入する
空白が構文に影響しないように、整数を1つ、 +
のようなオペレータを1つといった単位(トークン)として扱うようにする。
上記の加算・減算もトークン単位で扱う。
std::vector<Token> tokenize(char const* p){ std::vector<Token> tokens; while(*p){ if(isspace(*p)){ p++; continue; } if(*p == '+'){ Token token; token.type = TokenType::OPERATOR_PLUS; token.input = p; tokens.push_back(token); p++; continue; } ....
for(size_t i=1; i<tokens.size() && tokens.at(i).type != TokenType::EOS; ){ if(tokens.at(i).type == TokenType::OPERATOR_PLUS){ i++; if(tokens.at(i).type != TokenType::NUMBER){ error("予期しないトークン: %s", tokens.at(i).input); } std::cout << " addi a0, a0, " << tokens.at(i).value << "\n"; i++; continue; } ....
10 + 13 - 4
のように空白を含んでいてもコンパイルできるようになった。