Explore "Full-Stack" in depth!

情報系の専門学校で、今は機械学習に的を絞って学習中。プログラミングを趣味でやりつつ、IT系のあらゆる知識と技術を身に付けるべく奮闘中。

フルスクラッチによるセルフホスティングCコンパイラ自作日記#1

目次

概要

以前から rui314/9ccをGoで再実装する勉強法を取っていた私ですが、

drumato.hatenablog.com

に述べたように、
Cプログラミングに慣れたいということと、
単純に自分で試行錯誤しながら作ってみたいというところから、
フルスクラッチコンパイラ自作をスタートしました。

github.com

ルールとしては、

  • C言語のコードは普通に調べる
    • ベストプラクティスを吸収したり
  • コンパイラ実装に関連するコードは、あくまでも 参考程度に
    • 概念・設計は参考にしてもいい
    • あくまで 自分の力で、 試行錯誤 することを念頭に

ことを掲げています。

今回は 字句解析コードについてお話します。

はてなブログMarkdownパーサーが壊れてるっぽいので、
全てGistのコード引用で紹介していきます。


本題

ヘッダファイル omo.h

まずはヘッダファイルを見ていきます。

例によって、全体のコードを貼った後に各コードについて解説を加えていきます。

util.c

様々な処理を簡易的に利用するための自作ライブラリという感じです。

ファイルストリームを開いたり、
内容を取得したり、ファイル情報を取得したり。

後は配列の長さを調べる関数とか、
文字列のチェックを行う関数ですね。

util.cに記述された実際の定義を見てみます。

このようにutilityを充実させることがプログラミングをスムーズに進めるコツだと思っていて、
普段からちょっとした処理を関数に切り出すようにしています。

今回一番重要なのは、

ですね。
これにはCプログラミングを行う上で注意しなければいけない点が詰まっています。

についてですが、

まずは(type*)malloc(size_t nmemb);のように
汎用ポインタを即座にキャストします。

これは JPCERTのセキュアコーディング規約 に記載がありました。

www.jpcert.or.jp

また、fread()関数はnull終端が保証されていない為に、
手動で終端に \0を入力する必要があります。

ヌルバイトを挿入しないことによる脆弱性については、

codezine.jp

が参考になると思います。

また、先述した セキュアコーディング規約にも、

www.jpcert.or.jp

www.jpcert.or.jp

www.jpcert.or.jp

などに、
nullバイトに関する記述がありました。


token

ここではトークンの構造体を定義しつつ、
enumによって定数を定義しています。

これは 種類を識別するため に使われるもので、
int型によって列挙しています。

トークンの構造体の各フィールドを紹介します。

  • ty列挙したトークンの種類の定数が代入される
  • intvalリテラルの値を評価し、ここに代入
    • floatval も未対応ではあるが使い方は同じ
    • 具体的な代入方法については後述
  • str… 文字列リテラル保有場所
    • これについても代入方法は後述
  • input… 具体的にどのような文字列をトークンとしたのか、というフィールド
    • 例を見せます

main.c

Token->inputをダンプしてみましょう。

main.cを見てみます。

-tオプション によって表示する事が出来るようになっています。

f:id:orangebladdy:20190502110306j:plain

16進数もきちんと表示できている事がわかるかと思います。

因みに-sオプションではcat <filepath>とした出力と同じものが得られます。


logger.c

これもほぼutil.cの考え方と同じです。

ログファイルに出力する関数を置いています。
これは ファイルが開けなかった時等のエラーをダンプするものではなく、

コンパイル時に入力されたコードの良し悪しを判断して、
Warningのような役割でログを吐くようにしようと思っています。

ほぼ手を付けていないので結構無駄がありそうですね。
変数宣言相当多いかも。


目玉:lex.c

今回の本題、 字句解析のコードを見ていきます。

コード量めちゃくちゃ多いので気をつけてくださいね。

  • number literal
  • string literal
  • identifier

等を解析する関数について見ていきましょう。

char型やり忘れてたので後でやります。

因みにこれら、
char の種類を判別する関数は、

ctype.h に定義されているんですよね。既に。

どうせなら自分でやろうと思って作りましたが、
その関数を使ったほうがいいと思います。

現在見ているASCIIコードが範囲内かどうかチェックしながら、
readchar()によってオフセットを進めるという典型的な実装です。

token->intval = strtol(token->input, NULL, base);として文字列を変換し、
Token構造体に格納しています。

次に文字列リテラルですね。

実装はとてもシンプルです。

最後に識別子の解析ですが、
鋭い人ならわかると思いますがC言語の仕様に準拠していません。

要は途中で数字とかを許容できていないんですよね。
これは今日中に直します。

解析した識別子は、
lookup_identifier()という関数を用いて
予約語なのかユーザ定義なのかをチェックします。


総評

ここまでで実に3日ぐらいかかっています。

Cプログラミング本当に下手なんです…許してください。

とてもやりがいがあるので、少しずつですが頑張っていきます。