Plan 9には cpp(1) というANSIに準拠したCプリプロセッサが用意されていますが、Plan 9標準のCコンパイラはコンパイラ自身が簡略化したプリプロセッサを持っているので基本的には使いません。ただし、Cコンパイラが持っているプリプロセッサは設計思想の影響もあり大幅に簡略化されているので、ANSI準拠のプリプロセッサが必要な場合は cpp を使います。8c(1)の場合は -p
オプションが渡されれば cpp が使われるようになりますし、APE(ANSI/POSIX Environment)用のCコンパイラpcc(1)は特に何もしなくても cpp が使われます。*1
この cpp に #include_next
というGCC拡張ディレクティブを追加したくて関連するコードを読みました。
必要な理由
なんで #include_next
を追加する必要があったのかというと、いくつかUnix由来のソースコードをPlan 9へ移植していた時に、#include_next
が使われているものがありました。この拡張はシステム標準のヘッダファイルから一部を書き換えたい場合に使うことを想定しています。例えば uint32_t
型がシステムによって提供されていない場合、
#include_next <stdint.h> typedef ulong uint32_t;
という内容を stdint.h というファイル名で #include
のサーチパスに入れておくと、ソースコードからは単純に#include <stdint.h>
とすればuint32_t
が使える状態で読み込まれるという便利なものです。
Plan 9の cpp は、実行されない場所に書かれたディレクティブであっても解析はするので、
#if 0 #include_next <stdint.h> #endif
上記のコードでも #include_next
がパースされて、結果として不明なディレクティブなのでエラーになってしまいます。このエラーを簡単に避ける方法がありませんでした。
データ構造
#include
のサーチパスは includelist[]
によって表現されます。
typedef struct includelist { char deleted; // 1なら参照しない char always; // 0の場合は "file.h" のみ対象 char *file; // ディレクトリ名(例: /sys/include) } Includelist; #define NINCLUDE 64 Includelist includelist[NINCLUDE];
デフォルトでは /$objtype/include と /sys/include がサーチパスに入っています。また、$include 環境変数がセットされていれば、その内容も含まれます。特にオプションを渡さない場合、以下のような配列になります。
[0] file=/$objtype/include always=1 [1] file=/sys/include always=1 [2] file=$include(1) always=1 [3] file=$include(2) always=1 ... [63] file=. always=0
cpp に -I
オプションを渡した場合、includelist
の末尾に追加されていきます。例えば cpp -I/a/include -I/b/include
の場合は以下のようになります。
[0] file=/$objtype/include always=1 [1] file=/sys/include always=1 [2] file=$include(1) always=1 [3] file=$include(2) always=1 ... [61] file=/b/include always=1 [62] file=/a/include always=1 [63] file=. always=0
もう一つ、Source *cursource
も重要なデータで、現在処理中のファイルのスタックを表します。
typedef struct source { char *filename; /* name of file of the source */ int line; /* current line number */ int lineinc; /* adjustment for \\n lines */ uchar *inb; /* input buffer */ uchar *inp; /* input pointer */ uchar *inl; /* end of input */ int ins; /* input buffer size */ int fd; /* input source */ int ifdepth; /* conditional nesting in include */ struct source *next; /* stack for #include */ } Source; Source *cursource;
これはリンクリストになっていて、現在処理中のファイルが先頭です。
検索
検索する場合は、例えば #include <stdio.h>
なら、includelist
を後ろから検索していきます*2。この時、deleted
が1の場合は常に無視し、always
が0の場合は #include <xx>
の対象となりません(#include "xx"
なら対象)。そうして、stdio.h が見つかったら探索を止めて cursource
を更新します*3。
#include
がネストした場合は、もう一度 includelist
を後ろから検索してファイルを探します。見つかったら cursource
のリストが増えて、処理し終われば取り除かれて cursource
が以前処理していたファイルに戻ります。