Cの理解出来ない文法

Cのパーサを書いた経験から, Cの全く理解不可能な仕様を幾つか紹介。

まずはこれ

int const long typedef volatile long volatile unsigned x;

これは

const volatile unsigned long long int

のxへのtypedefだけれども,Cの規格上正当でもちろんgccも通す文法.
規格ではここの文法がどう定義されているかというと,

  • 記憶域クラス指定子 : static extern typedef ..
  • 型指定子 : char short int .. unsigned ..
  • 型修飾子 : const volatile ..
  • 関数指定子 : inline

となっていて, 宣言文の最初に

  • (記憶クラス指定子|型指定子|型修飾子|関数指定子)*

を付けられるという文法になっている。
「文法上の都合」からtypedefは記憶域クラス指定子に含まれるので, typedefを途中に書くこともできる。

さらに, 型修飾子と関数指定子は何回書いても1個と見なすという奇妙な規則があり,例えば

int inline inline inline inline const const const const f(){
    ...
}

も正当な文法. inlineをたくさん書くとコンパイラがinlineに積極的になるとかそういうこともない。



まぁそれでも構文解析をするだけならば単純で、意味解析をするときにすごく面倒になる。
例えば並び順が決まっていたら,

const unsigned long int

int -> long int -> unsigned (long int ) -> const (unsigned (long int))

と自然に解析できるけれども, 並び順が任意だと「全ての修飾子をリストに入れる」->「ソートする」->「解析する」という事をしなければならない。(実際はパースしながらインサーションソートしつつ重複も同時に除くみたいな事をしたりする)
さらにunsigned単独ならunsigned intになる等、型なのか修飾子なのかが文脈で変化するという事も考慮しなければならない。

もっと問題なのが, 同じ構文が違う意味を持つという点。
例えば

static int x;
typedef int x;

構文解析上は同じだが, 意味が全く違う。他にも

struct x {
  ...
};

struct {
  ...
};

int;

構文解析上は同じだが,1つめは正当で2つめは警告で3つめはエラー。規格上2番目は禁止されてないので, エラーにしてはいけない。

struct x;
static struct x;
long struct x;

では2つめが警告で3つめはエラーなど,構文規則上区別できないものが違う意味を持つという事が多々ある。


Cの構文解析には他にも名前空間の問題とかもあるけれども、理解不能という意味ではこのあたりが一番だった。
パーサが書きやすくなるわけでもないし、Rubyの様にユーザが書きやすいようにパーサで苦労するというのとも違うし。yaccで書かなければ面倒じゃないのかもしれない。gccもcoinsも構文解析器使っていないのはそういう事情があるのかも。