C言語完全に理解したい
C言語なんもわからん≒C言語のコンパイラ書ける
C言語完全に理解≒ズルすればC言語のコンパイラ書ける
C言語完全に理解できますか?
およそコンピュータに関わる人でC言語に触れたことがない人はいないだろうが、C言語なんもわからんレベルで理解できる人はそう多くないだろうし、すなわちCコンパイラ書けますよなんて人はあんまりいないだろう。
Cコンパイラを書くということはすなわちC言語の仕様を学ぶということなので、これをやるだけで経験値がどんどん溜まっていくという腹筋ローラーなみにすごいトレーニングらしいのだが、いかんせんハードルが高い。なのでLLVMや、Boostライブラリを使って楽してズルしてCコンパイラを書きたいと思う。つまり低レイヤーについてはあんまり学べない。
C言語の文法について
最高にわかりやすい記事にかかれているようにC言語は非常に単純な要素から構成されている(余談その1)
変数の宣言;
関数の宣言
関数のプロトタイプ宣言;
変数の宣言
一番簡単なのは変数の宣言で、七面倒臭いstaticとかconstを除けば
Typename Identifier
といういわゆる終端記号のみで構成されているので解析が簡単だ。(ほんとうは初期化子もあるんだけれども)
関数のプロトタイプ宣言
関数のプロトタイプ宣言は変数の宣言に毛が生えたようなもので、引数のリストがあるかないかくらいの違いで、あるいは引数はなくてもよい。
Typename Identifier(Typename, Typename...)
関数の宣言
関数の宣言はケタ違いに難しい。
Typename Identifier(Typename Identify...)
{
Typename Identifier;
...
Sentence
Sentence
...
}
関数の中身では変数の宣言の他に、変数の代入、流れ制御(if, for文)、関数の呼び出し、returnなどが行われる(余談その2)。これらのことをまとめてSentenceとか呼ぶらしい。SentenceのなかにはSentenceを{}ブロックで囲ったものやセミコロンだけのものも含まれている。SentenceはSentenceであり、Sentenceを{}で囲ったものもSentenceなんだよ!もう何でも有りである。
今回の目標
今回は関数の宣言の一部を解析するプログラムを作る。具体的には
int forty_two()
{
return 42;
}
こんなプログラムをコンパイル、もといllvm言語に変換できるくらいの昨日を実装したい。見ればわかるけどこれは
関数の宣言、Sentenceの中のreturn文
についてだけ解析すれば十分である。ちなみにこのプログラムをclangでLLVM IR(中間表現 : intermediate representation)に出力すると
define dso_local i32 @forty_two() #0 {
ret i32 42
}
うーん、defineしてDynamic Shared Objectでlocalで戻り値はi32で名前はforty_twoで引数は無しで #0はよくわからん。
実装してみよう
#include <iostream>
#include <string>
#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;
using std::string;
using std::vector;
int parser_program(std::string::iterator, std::string::iterator);
int parser_func(std::string::iterator &, std::string::iterator &,string);
int main()
{
string s("int forty_two(){return 42;}");
auto first = s.begin(), last = s.end();
parser_program(first, last);
}
int parser_program(std::string::iterator first, std::string::iterator last)
{
static auto func_decl = (qi::string("int"))
>> *(qi::char_ - qi::lit("(")) >> qi::lit("(") >> *(qi::char_-qi::lit(")")) >> qi::lit(")") >> qi::lit("{");
string type;
vector<char> Id;
vector<char> args;
if(qi::phrase_parse(first, last, func_decl, qi::space,type, Id, args))
{
if(!type.compare("int"))
type = string("i32");
string s;
if(!args.size())
s = string("void");
else
string s = string (args.data());
auto arg_first = s.begin(), arg_last = s.end();
static auto no_arg = (qi::string("void"));
if(qi::phrase_parse(arg_first, arg_last, no_arg, qi::space))
{
// if the func takes NO args..
std::cout << "define dso_local";
std::cout << " " << type;
std::cout << " @" << string(Id.data()) << "()";
std::cout << " {" << std::endl;
if(!parser_func(first,last,type))
return 0;
std::cout << "}" << std::endl;
} else {
// if the func takes args..
}
} //func_decl end
return 1;
}
int parser_func(std::string::iterator &first, std::string::iterator &last,string rvtype)
{
for(;;){
static auto sentence_ret = qi::lit("return")
>> *(qi::char_ - qi::lit(";"))
>> qi::lit(";");
static auto func_end = qi::lit("}");
vector<char> expr;
if(qi::phrase_parse(first, last, sentence_ret, qi::space, expr))
{ // return statement
std::cout << "ret" << " " << rvtype << " ";
std::cout << string(expr.data()) << ";" << std::endl;
} // return statement end
if(qi::phrase_parse(first, last, func_end, qi::space))
return 1;
}
return 0;
}
頭を抱えそうな悪文ですな。boost::spiritはあんまり高度なことしていないのでまた今度ということでひとつ。
実行結果
$ ./a.out
define dso_local i32 @forty_two() {
ret i32 42;
}
はい、すばらしい結果ですね。次はboost::spiritを利用した数式の扱いについて書こうと思います。
余談
余談その1
変数の宣言;
関数の宣言
関数のプロトタイプ宣言;
の繰り返しがC言語なのだが、関数の宣言だけは最後のセミコロンがつかない。C++でも同じだけど、クラスの宣言にはセミコロンつけるのはなんかややこしいな。
余談その2
ところで上のコードではSentenceより先に変数の宣言をしていることに気づいただろうか?なんとC89ではlocal変数は関数の一番上で宣言しなければならなかったのである。今どきのキラキラ☆C++erは平気でfor文の初期化式で変数の宣言をするけど、こんなことは絶対に許されていなかったのである。C99でもfor文の初期化式で変数の宣言は許されていなかったと思うけどC11でどうなったかはC言語わかってないので知らないです。