Cの構文解析(宣言構文)

C言語構文解析では宣言構文もなかなか大変なので紹介。処理系を作る人の参考になれば。

変数を宣言・定義する場合, 普通に考えれば

[型] [変数名];
[型] [変数名] = [初期値];

という構文を採用するのが自然だと思う。例えばJavaD言語などの新しい言語では

int[] x;

の様な構文を採用している。


しかし,CやC++では[型]の部分が構文的に独立していない。例えば

int x[10];

はもちろんけれども,

int *x;

も構文上は

[int] + [*x];

となっている。*1
従って,この構文を正しく読むと「int*型のxの宣言」ではなく「int型のポインタ注釈付きxの宣言」となる。
このx[10]や*xの部分を「宣言子」と言い, 他には関数の仮引数列が含まれる。


このような構文を取るため, C言語では異なる型の識別子を同一の宣言文で宣言することができる。

int x, *y;
double a, b[10], c(void);

構文解析の手順

以上の事情からこれらの構文を解析する際には, ベースとなる型と宣言子を別々に取得した後に, 実際の型を再構築する必要がある。宣言子の構造を保持する為に余分なデータ構造を用意しなければならないけれども, 再構築自体は単純で以下の様になる。

  • 宣言子の一番外側の要素から順にベース型に適用して(移して)いく。
  • 一番外の定義は
    • ポインタ注釈'*'が最優先
    • 次いで配列"[..]"と関数"(...)"

イメージとしてはこんな感じ。

int *x[10]
 ↓
int - *x[10]  型と宣言子を別々に得る
 ↓
(Pointer int) - x[10] 一番外の'*'を左に移す
 ↓
(Array 10 (Pointer int)) -  x  一番外の[10]を左に移す

ということでxの型「intへのポインタの配列(長さ10)」が得られる。

いくつかの例

int (*x)[10]
 ↓
(Array 10 int)  -  *x
 ↓
(Pointer (Array 10 int)) - x

よりこれは「intの配列(長さ10)へのポインタ」となる。

int f(int x)[10];
 ↓
(Array 10 int) -  f(int x)
 ↓
(Function [int x] (Array 10 int)) - f

よりfの型は「intの配列(長さ10)を返し, 仮引数がint xの関数」となる。Cの関数は配列を返せないのでこれはエラーである。

void (*f(int x))(int x)
 ↓
(Function [int x] void) - *f(int x)
 ↓
(Pointer (Function [int x] void)) - f(int x)
 ↓
(Function [int x] (Pointer (Function [int x] void))) - f

よりfの型は関数ポインタを返す関数となる。

という感じで, 宣言子側から型側へと飾りを移すという作業を再帰的に行う事で変換ができる。

*1:これはC++の参照「&」,C++0xの右辺値参照「&&」でも同様。