自作Cコンパイラ(未完成)

この記事は大阪工業大学 Advent Calendar 2023の4日目の記事です。

はじめに

これは今年ちまちま書いてたCコンパイラ(未完成)のゆるふわ解説です。
実装はリンク先にあります。

github.com

コンパイラアルゴリズムや仕組み、本記事を執筆する参考として低レイヤを知りたい人のためのCコンパイラ作成入門を参考にしています。リンク先の記事は非常に良いテキストなのでちゃんとした仕組みなどが知りたい場合はこの記事ではなく、リンク先の記事を読んでいただければと思います。

また、実装に関してはO'Reilly Japan - Go言語でつくるインタプリタを参考にしています。こちらの書籍はコンパイラではなくインタプリタですが、実装としては共通する部分が多いため非常に参考になりました。

コンパイラアセンブラ

コンパイラは人間が書いたソースコードを読み込んで、コンピュータが実行できる形式に変換してくれるプログラムです。C言語を書いたことがある人であれば$ cc -o a.out main.c のようなコマンドを実行したことがあると思います。

なぜわざわざコンパイルする必要があるのか?直接コンピュータが実行できる形式でプログラムを書けばいいのでは?と疑問思う人がいるかもしれません。(昔の人は直接書いてたみたいです。)

コンパイルを実行するのがメンドクサイと思うかもしれませんが、最近のコンピュータが直接実行できる命令は '0','1' の羅列からなる数列で表現されるており、機械語と呼ばれています。そのため、機械語を直接書くためには '0', '1' からなる数列を打ち込む必要があり非常に厳しいです。

そこで開発されたのが機械語に人間が読める形式で文字を当てはめたアセンブリです。アセンブリ機械語と一対一対応していますが、そのままでは実行できないためアセンブリ機械語に変換する処理が必要です。

この処理はアセンブルと呼ばれます。上記のccコマンドの例でもそうですが、最近のコンパイラはこのアセンブルの処理も同時に行っています。今回作成しているコンパイラではこのアセンブルは行わず、C言語ソースコードからアセンブリを出力するような構成になっています。

機械語アセンブリの例として次のadd関数を逆アセンブルした結果を載せておきます。

int add(int a, int b){
        return a + b;
}

add関数の逆アセンブル結果

話が少しずれましたが、アセンブラの登場により命令を人間が直接読める形式で記述できるようになりました。しかし、人間が直接読めるようになったとは言えアセンブリの表現力は低く、大きなプログラムを作成するのは非常に大変です。先ほどの例で挙げたadd関数を逆アセンブルした結果を見てもらえばわかるかと思います。

そのため、より高級で表現力の高い言語としてC言語などのプログラミング言語を人間が書いて、コンピュータが実行できる形式に変換するためにコンパイルを行う必要があるのです。

前置きが長くなりましたが、単純なコンパイラの処理を大きく分けると、
字句解析構文解析意味解析コード生成最適化に分けられると思います。

今回作成しているコンパイラでは最適化の処理をまだ作成しておらず、意味解析に関しては構文解析のおまけ程度で行っているので、それ以外の3つの処理について実装を交え簡単に解説していこうと思います。

字句解析(Lexer)

github.com

まず初めの字句解析では、単語をトークンと呼ばれる単位に分けます。トークンの種類としては数値や演算子、記号、予約語などです。これらのトークンは次の構文解析で利用します。

int a = 123 * b;

例えば上記のようなコードがあった場合、トークン列は次のようになります。

[ TYPE, IDENT, INT, ASTERISK, IDENT, SEMICOLON ]

字句解析を行う関数の実装としては、次のようにトークンとリテラルの値を持つ構造体を配列として作成して返却するような実装になっています。

type Token struct {
    Type    TokenType   // string
    Literal string
}

字句解析は特に複雑なアルゴリズムなども必要ないのであまり説明することもありませんが、文字列をどこまで読み込んだかなどの境界でバグらせやすいのでテストをちゃんと書くことが大切だと思います。(n敗)

構文解析(Parser)

github.com

抽象構文木

構文解析では字句解析で作成したトークン列を元に抽象構文木を作成します。
抽象構文木は括弧などの意味に関係のない情報を取り除き、演算子をノード、値を葉とするような木構造になります。

例として int a = 1 + 2 * 3; のような簡単な式を抽象構文木にしてみます。

抽象構文木

この例では =, *, +演算子です。ポイントとしては演算子のノードは葉だけでなく同じ、もしくは異なる演算子のノードを再帰的に持つことが出来ることです。もちろん、どんなノードでも持てるわけではなく言語仕様によって異なります。

また、この抽象構文木演算子の優先度をそのまま表しています。ある演算子のノードを評価するためには、そのノードが持つ子のノードを評価します。そのため、今回の例だとまず 2 * 3 を評価し得られた 61 + 6 のように評価したのち a に代入します。

実装に関した話をすると、参考にしているGo言語でつくるインタプリタの実装がかしこいなと思って真似しています。C言語のノードを表現する型を考えるときにノードの種類を大きく分けるとに分けられます。(宣言などもあります)

式は 1 + 2 * 3 のように評価されると値を生成するもので、文は return 123; のように評価しても値を生成しないものです。この式と文を表現するインターフェースを次のようにそれぞれ用意しておき、式と文を表すノードに実装させています。そうすることで、実際に構文木を作成する際に返り値の型を絞ったり、型アサーションを可能にしています。

type Node interface {
    node()
    String() string
}

type Statement interface {
    Node
    statementNode()
}

type Expression interface {
    Node
    expressionNode()
}

このような演算子の優先度を考慮したような抽象構文木を作成するために、再帰下降構文解析と呼ばれる方法で抽象構文木を構築します。

再帰下降構文解析

プログラミング言語の構文を記述する方法としてBNFと呼ばれるものがあります。詳しくは調べて欲しいのですが、BNFを用いることで文法を再帰的に定義することが出来ます。

C言語の文法をBNFで記述するとリンク先のようになります。

cs.wmich.edu

再帰下降構文解析の面白いところは、BNFにより文法がしっかりと記述されているならば、BNFに対応するように関数を実装するだけで構文解析が行えることです。 また、ボトムアップ式に構文解析を実装する場合も、後回しにしたい機能は構文規則に対応する空の関数を作っておけばいいので実装面でも書きやすいと思います。

このように文法規則に従って関数を実装するだけで演算子の順序を考慮したような抽象構文木が作れるの面白くないですか?面白いんですよね。

コード生成(Generator)

github.com

コード生成では構文解析で作成した抽象構文木を元にアセンブリのコードを生成しています。演算などは仮想的なスタックマシンとして実現しています。 コード生成に関しては参考にしている低レイヤを知りたい人のためのCコンパイラ作成入門とほとんど変わらないと思うのでそちらを読んでいただければと思います。(力尽きました)

おわりに

今回作成しているコンパイラは完成までまだまだ道のりが長いですが、それなりにちゃんとしたコンパイラを作成するとそのコンパイラで自分自身をコンパイルしたり、また別のコンパイラコンパイルしたりできるようになります。

今使われているコンパイラも他のコンパイラによってコンパイルされてるんだなぁーと思うと、生物の自己複製や進化の過程っぽくて感慨深くなっちゃいますね。 少しややこしくなりましたが、言いたかった事としてはコンパイラ再帰的にコンパイラを作るのって滅茶苦茶面白いと思うんですよね。

非常に個人的な意見ですがコンパイラなどの言語処理系を書く営みがプログラミングの中で一番面白いと思っています。構文解析で出てきた再帰下降構文解析もそうですし、コンパイラコンパイラコンパイルする流れなど再帰的な構造が好きなのかもしれないですね。

おまけ

今回のコンパイラGolangで実装しているのでWASMコンパイルできます。なので、ブラウザでデモを試せるようにGithub Actionを利用してmasterが更新されるとWASMにコンパイルし自動でデプロイするようにしています。
まだ実装していない構文やバグってる構文が割とありますが、テストに書いてある構文はコンパイルできるはずなので、興味があれば触ってみてください。

aoken7.github.io

2022年に食べたおいしいもの

はじめに

この記事は大阪工業大学 Advent Calendar 2022の1日目の記事です。

今年度から大学院生になり奨学金をたくさん借りて見かけ上のお金の余裕とストレスが増えたので外食やお出かけが増えました。なので今年食べたおいしいものを振り返ってみようと思います!

 

1~3月

1クォーター目は一店舗だけでした。コロナ感染、卒論、研究でしんでた気がします。

華蓮 大阪心斎橋店

goo.gl

一つ目はしゃぶしゃぶで後輩がおごってくれました。本当にありがとう...

結構お高めのお店でおしゃれな前菜が出てきてびっくりしました。お肉に関しても滅茶苦茶甘くておいしかったです。人生で食べたしゃぶしゃぶの中だと一番おいしかった気がします。

しゃぶしゃぶ

 

4~7月

2クォーター目は少し時間に余裕があったので香川行ったり地元の友人とごはん食べに行ったりしてた気がします。

本格手打うどん 麺むすgoo.g

二つ目はうどんの本場、香川県で食べたうどんです。香川に弾丸旅行した時に食べに行きました。お値段の割に結構ボリュームがあり美味しかったです。

香川のうどんは初めてだったので期待値が高かったんですが丸亀製麺と大きな違いが分かりませんでした()

これは多分僕が貧乏舌なのかもう一段グレードの高いお店に行くべき気がします。

うどん
骨付鳥 一鶴 高松店

goo.gl

三つ目は骨付き鶏肉です。これも香川旅行の一環で行きました。スパイスが効いてて食べ応えがあり美味しかったです。一緒に食べるキャベツがいい感じでした。あと一緒にとりめしを食べた気がするんですが写真が見当たりませんでした...

結構色々なところにお店があるみたいなのでオススメです。

とりにく
麺屋 たけ井 R1号店

goo.gl

四つ目はつけ麺です。学校の近くにあるつけ麵屋なんですがボリュームがありかなり美味しかったです。スープは魚介系?で柚子の香りがいい感じでした。麺は食べ応えのある太い麺でチャーシューも分厚くおなか一杯になりました。

関西にいくつかお店があるので関西に住んでる人にはオススメのつけ麵です。

つけめん
すしてつ 先斗町

goo.gl

五つ目は回らない寿司です。京都の鴨川沿いのお寿司屋さんでした。クジラなどの普段食べないネタなどもあり美味しかったです。ただクオリティに対するお値段はちょっと高めに感じたので観光客向けかもしれません(?)

すし

8~9月

金沢まいもん寿司 新神田店

goo.gl

六つ目もお寿司です。これは美味しい海鮮を求めて金沢旅行した時に行きました。多分回らなかったと思います。お寿司ももちろん美味しかったんですがあら汁がいいダシ効いてて良かったです。

休日だったのもありますが、金沢駅周辺のお寿司屋さんどこも人が沢山で予約なしには入れませんでした。

おすし

10〜12月

eX cafe(イクスカフェ)京都嵐山本店

goo.gl

七つ目はパフェです。これを含めて残りは京都(嵐山)に遊びに行ったときに食べました。パフェには紫いもアイスが入っててかなり美味しかったです。生じゃない八ツ橋が上に刺さってたんですがパフェとマッチしていい感じでした。

お店は嵐山の渡月橋近くにありとても雰囲気がありおしゃれでした。

ぱふぇ
嵐山よしむら

goo.gl

八つ目は蕎麦です。にしん蕎麦がとても食べたくとても悩んだんですが天ぷらを食べたい欲求に負けて天ぷらのセットにしました。蕎麦はざるそばと温蕎麦で選べました。

蕎麦もとても美味しかったんですが天ぷら盛りの中に舞茸の天ぷらがありとてもジューシーで美味しかったです。写真には写って無いですがこれに加えてご飯も少し出てきた気がします。

お店は渡月橋の近く桂川沿いにあり、運が良いと桂川が見える席に座れるんじゃないでしょうか。

 

そば
馬野郎

goo.gl

最後は馬肉です。人生初馬肉だったんですが馬刺し、ユッケ、馬肉唐揚げ、馬肉焼きなどなどのフルコースで色々食べられてとても美味しく満足しました。

馬刺しは醤油で食べたんですが風味の詰まったマグロの刺し身を食べているようでした。馬肉焼きはハラミに似た感じでしたがハラミに比べ臭みが少なくより風味があるような感じだったと思います。あと食べごたえがありお酒が進みそうな感じでした。

うま

おわりに

流行り病が少し落ち着いてきたのもあり、2022年は例年に比べ美味しいものを色々食べた気がします。来年はもっとフットワークを軽くして旅行やお出かけを増やして色々な美味しいもの食べたいと思います。