実践・C コンパイラ演習

Core プロファイル — 言語仕様

第 2 版(2026 年改訂)。C サブセットコンパイラが対象とする言語仕様であり、 この教材では凍結された仕様として扱う。この文書がこの言語の唯一の規範で、 教材・雛形・テスト・発展課題はすべてこの版に従う。 ただし、標準トラックの実装完成は不正入力の全診断を保証しない。 規範としての診断要件と、教材で実装する診断範囲の区別は 「標準トラックの診断範囲」に示す。

設計原則:

目次(知りたいこと → 節)

この文書は通読用ではなく引くための文書である。左の列で当たりをつけて飛ぶ。

節 引きたいとき
字句 キーワードは何語か、コメントの書き方、a+++b のような切り出し
型 使える型、ポインタ、struct、型変換の可否
リテラル 整数リテラルの上限、文字・文字列リテラル、使えるエスケープ
演算子 使える演算子と優先順位、無い演算子(ビット演算・後置 ++ など)
宣言 宣言できる場所、初期化子、スコープ、再宣言・再定義
文・制御構造 if / while / for / break / continue の形、空文・ブロック
関数 引数の個数、プロトタイプ、main の扱い、再帰
プリプロセッサ #include / #define でどこまで書けるか
標準ライブラリ(lib.h) 呼べる関数(printf / malloc など)とその宣言
到達範囲: 機能 → 導入コマ → 検証テスト 「この機能はどのコマで書けるようになるか」「どのテストで確かめるか」
標準トラックの診断範囲 不正入力をどこまで検出するか、テスト全通で何が保証されるか
除外機能の一覧 「なぜこれが通らないのか」「配列・switch は書けるのか」
ISO C との関係と移行上の注意 gcc で通るのか、本物の C と違うのはどこか
実行時の意味 サイズ・アラインメント、評価順序、未定義動作
形式文法(EBNF) パーサを自作するとき、構文エラーの原因を突き止めるとき

コンパイルが通らない理由を探しているなら、まず 除外機能の一覧 を見る。 実装が動かない理由を探しているなら testing.md の症状表を見る。


字句


型

int          4 バイト符号付き整数(32 ビット 2 の補数)
char         1 バイト文字 / 整数(signed)
T *          ポインタ(多段可: int **pp)。サイズ = 8 バイト
void *       汎用ポインタ。任意の T * と相互に暗黙変換(キャスト不要)
void         関数の戻り値型としてのみ使用
struct Tag   複合型(タグ必須)

リテラル

42           整数リテラル(10 進のみ。上限 2147483647、超過はコンパイルエラー)
'a'          文字リテラル(int として扱う。1 文字ちょうど)
"hello"     文字列リテラル(静的領域に配置、char * として参照。内容の変更は未定義)

演算子

優先順位が高い順に示す。

一次:   リテラル  識別子  ( 式 )
後置:   p[i]  s.field  p->field  f(args)
単項:   - (負号)  ! (論理否定)  * (間接参照)  & (アドレス取得)
        ++ -- (前置のみ)  sizeof(型名)
乗除:   *  /  %
加減:   +  -   (ポインタ ± int を含む。尺度は指し先型のサイズ)
関係:   <  >  <=  >=   (int/char のみ。ポインタの関係比較はない)
等値:   ==  !=  (int/char 同士、ポインタ同士、ポインタと 0)
論理積: &&   (両辺を必ず評価する。短絡しない)
論理和: ||   (両辺を必ず評価する。短絡しない)
条件:   a ? b : c   (右結合)
代入:   =   (右結合。左辺は単項式で lvalue)

宣言

int x;              // 局所変数。初期化子は書けない(値は不定)
int *p;             // ポインタ変数
struct Node *q;     // struct へのポインタ
struct Point pt;    // struct 変数
int g;              // グローバル変数(0 に初期化される)

文・制御構造

if (cond) { ... }
if (cond) { ... } else { ... }   // else は最も内側の if に結合

while (cond) { ... }

for (init; cond; step) { ... }   // 3 式は各省略可。宣言は不可

return expr;
return;            // void 関数
break;             // ループ内のみ
continue;          // ループ内のみ。step へ合流

関数

// プロトタイプ(引数名必須)
int add(int a, int b);

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

// 可変長引数(外部プロトタイプ限定。固定引数 1 個以上の後にのみ書ける)
int printf(char *fmt, ...);

プリプロセッサ

#include "file.h"      // "..." 形式のみ。<...> はない
#define NAME value     // オブジェクト形式マクロのみ(定数置換)

標準ライブラリ(lib.h)

宣言のみ提供する。Linux/RV64(LP64)では全シンボルをそのまま libc に リンクして解決できる(追加実装なしで動く)。

struct FILE;                                    // 不透明型。前方宣言のみで完全化しない

int  printf(char *fmt, ...);                    // stdout へ書式出力
int  fprintf(struct FILE *f, char *fmt, ...);   // ストリームへ書式出力
struct FILE *fdopen(int fd, char *mode);        // fd をストリーム化(fd 2 = 標準エラー)
struct FILE *fopen(char *path, char *mode);     // ファイルを開く。失敗時 NULL
int  fread(void *buf, int size, int n, struct FILE *f);
int  fclose(struct FILE *f);
void *malloc(int size);                         // 失敗時 NULL
void exit(int code);

#define NULL 0

到達範囲: 機能 → 導入コマ → 検証テスト

区分 範囲
標準トラック(コマ1〜15) 正しい入力のコード生成。検証はfixed17と過去回の全テスト、診断範囲は別節に示す
発展トピック 本仕様の外側の機能・意味論(例: L2 複合代入、S1 短絡評価)、型・宣言検査の拡充(例: Q1)

そのうえで、本仕様の各機能を「どのコマで導入し、どのテストで検証するか」に 1 対 1 で対応させたのが以下の表である。この表がこの対応の唯一の出典であり、 getting_started.md の全体の流れと code_example.md の付録は、 コマ単位の要約としてこの表を参照する。

読み方:

字句とリテラル

機能 導入コマ 代表テスト 区分 備考
行コメント // コマ1 sessions/02_arithmetic_codegen/tests/div_round.c 標準 Lexer は提供済み
識別子・キーワード 12 語 コマ1 全テスト 標準 Lexer は提供済み。例外 E2
整数リテラル コマ2 sessions/02_arithmetic_codegen/tests/add.c 標準 10 進のみ
文字リテラル 'a' コマ8 sessions/08_types/tests/char_var.c 標準 '\0' は sessions/11_expr_walk_libc/tests/strlen_literal.c
文字列リテラルとエスケープ コマ10 sessions/10_strings_data_section/tests/printf_hello.c 標準 静的領域に置く

型

機能 導入コマ 代表テスト 区分 備考
int コマ2 sessions/02_arithmetic_codegen/tests/add.c 標準
char(昇格と縮小を含む) コマ8 sessions/08_types/tests/char_var.c 標準 lb / sb。型別のロード / ストアは load_store_ty.c
T *(単段) コマ7 sessions/07_lvalue_rvalue/tests/deref_read.c 標準
多段ポインタ int ** コマ9 sessions/09_pointer_arith/tests/ptr_to_ptr.c 標準 elem_ty_str を重ねるだけ
void * と T * の暗黙変換 コマ12 sessions/12_struct_malloc_list/tests/void_ptr.c 標準 キャストが無いのでこれが唯一の手段
戻り値型 void コマ7 sessions/07_lvalue_rvalue/tests/void_func.c 標準 return; と末尾到達
struct Tag の定義・変数・フィールド コマ12 sessions/12_struct_malloc_list/tests/dot_access.c 標準 ファイルスコープのみ
struct の自己参照・前方宣言 コマ12 sessions/12_struct_malloc_list/tests/list_sum.c 標準 不完全型 struct FILE はコマ11 の file_stream.c
struct 値の代入・実引数・戻り値 — — 仕様外 本仕様が禁止。ポインタで受け渡す。代入は発展 S3 が本仕様の禁止行を改訂して追加し、実引数・戻り値は発展 L5 が仕様の外に新しく足す

演算子

機能 導入コマ 代表テスト 区分 備考
算術 + - * / %・単項 -・括弧 コマ2 sessions/02_arithmetic_codegen/tests/prec.c 標準
/ % の丸め(0 方向へ切り捨て) コマ2 sessions/02_arithmetic_codegen/tests/div_round.c 標準 剰余の符号は被除数と同じ
関係 < > <= >= コマ4 sessions/04_if_else/tests/compare.c 標準 > >= は Parser が正規化
等値 == != コマ4 sessions/04_if_else/tests/compare.c 標準 ポインタと 0 の比較は sessions/12_struct_malloc_list/tests/list_sum.c
比較・論理演算の結果が int の 0 / 1 コマ4 sessions/04_if_else/tests/rel_value.c 標準
論理 && \|\|(短絡しない) コマ13 sessions/13_globals_scope/tests/logical_ops.c 標準 例外 E3
単項 ! コマ13 sessions/13_globals_scope/tests/logical_ops.c 標準
条件 ? : コマ4 sessions/04_if_else/tests/ternary.c 標準 選ばれた枝だけを評価する
代入 = コマ3 sessions/03_variables/tests/chain_assign.c 標準 右結合。左辺は lvalue
前置 ++ --(int) コマ5 sessions/05_loops/tests/incr_loop.c 標準
前置 ++(ポインタ、型対応) コマ9 sessions/09_pointer_arith/tests/ptr_incdec.c 標準 指し先型のサイズだけ進む
&(アドレス取得)・*(間接参照) コマ7 sessions/07_lvalue_rvalue/tests/deref_write.c 標準
ポインタ ± int(尺度は指し先型) コマ9 sessions/09_pointer_arith/tests/ptr_arith.c 標準
添字 p[i] コマ9 sessions/09_pointer_arith/tests/ptr_sum.c 標準 *(p + i) の略記
. と -> コマ12 sessions/12_struct_malloc_list/tests/arrow_access.c 標準
関数呼出し f(args) コマ6 sessions/06_functions_recursion/tests/call_add.c 標準 識別子直呼びのみ
sizeof(型名) コマ9 sessions/09_pointer_arith/tests/sizeof_type.c 標準 struct への適用はコマ12 の sizeof_test.c。例外 E1
複合代入 += -= *= /= %= 発展 L2 advanced/L2_compound_assign/tests/ 発展 本仕様の外
sizeof 式(sizeof x) 発展 L4 advanced/L4_sizeof_expr/tests/ 発展 本仕様の外
短絡する && \|\| 発展 S1 advanced/S1_shortcircuit/tests/ 発展 本仕様の外

宣言とスコープ

機能 導入コマ 代表テスト 区分 備考
ローカル変数宣言(初期化子なし) コマ3 sessions/03_variables/tests/single.c 標準 関数本体の先頭のみ
入れ子ブロックが新しいスコープを作らない コマ4 sessions/04_if_else/tests/nested_block.c 標準 2 層スコープ
グローバル変数(0 初期化) コマ13 sessions/13_globals_scope/tests/global_init.c 標準
ローカルによるグローバルの隠蔽 コマ13 sessions/13_globals_scope/tests/global_local_shadow.c 標準 隠蔽はこの 1 種類だけ
再宣言・再定義規則、仮引数との名前衝突 コマ13 — 標準 コンパイルエラーの診断。下の「テストを持たない項目」を参照

文と制御構造

機能 導入コマ 代表テスト 区分 備考
if / else / else if コマ4 sessions/04_if_else/tests/if_elseif.c 標準 else は最も内側の if に結合
ブロック { } コマ4 sessions/04_if_else/tests/nested.c 標準
while コマ5 sessions/05_loops/tests/while_sum.c 標準
for コマ5 sessions/05_loops/tests/for_count.c 標準 宣言は書けない
break コマ5 sessions/05_loops/tests/break_loop.c 標準
continue コマ5 sessions/05_loops/tests/continue_odd.c 標準 step へ合流
return expr; コマ2 sessions/02_arithmetic_codegen/tests/add.c 標準 共通エピローグはコマ4
return;(void 関数) コマ7 sessions/07_lvalue_rvalue/tests/void_func.c 標準
空文 ;・for の 3 式の省略 コマ5 — 標準 文法上は書けるが単独のテストは無い

関数

機能 導入コマ 代表テスト 区分 備考
関数定義・仮引数・戻り値 コマ6 sessions/06_functions_recursion/tests/call_add.c 標準 引数なしは ()
固定char引数・char戻り値の代入変換 コマ8 sessions/08_types/tests/char_argument.c / char_return.c 標準 可変長部を縮小しないことは varargs_promotion.c
条件式の共通型 コマ8 sessions/08_types/tests/cond_char_pointer.c 標準 尺度・多段参照はコマ9、structはコマ12
関数の引数上限8個 コマ6 sessions/06_functions_recursion/tests/eight_args.c 標準 超過入力の拒否は提供Parserの検査で確認
呼出し式への戻り値型の伝播 コマ8 sessions/08_types/tests/call_ptr.c 標準 後続回の call_ptr_arith.c / call_char_ptr.c / call_struct_ptr.c / multifile_return_ptr.c でも検査
プロトタイプ宣言・相互再帰 コマ6 sessions/06_functions_recursion/tests/mutual_rec.c 標準
再帰 コマ6 sessions/06_functions_recursion/tests/fib_rec.c 標準
可変長引数の外部プロトタイプと呼出し コマ10 sessions/10_strings_data_section/tests/printf_number.c 標準 定義は書けない
int main() コマ2 全テスト 標準
int main(int argc, char **argv) コマ9 sessions/09_pointer_arith/tests/main_argv.c 標準

プリプロセッサ

機能 導入コマ 代表テスト 区分 備考
#include "..." コマ10(lib.h)・コマ14(自作ヘッダ・入れ子) sessions/14_preprocess_multifile/tests/multifile_math.c 標準 前処理はスキャフォールド提供
#define(1 段置換) コマ14 sessions/14_preprocess_multifile/tests/define_min.c 標準
複数ソースファイルの同時コンパイル コマ14 sessions/14_preprocess_multifile/tests/multifile_global.c 標準
循環取込み・マクロ多段参照のエラー コマ14 — 標準 診断。下の「テストを持たない項目」を参照

標準ライブラリ(lib.h)

機能 導入コマ 代表テスト 区分 備考
printf コマ10 sessions/10_strings_data_section/tests/printf_number.c 標準
fprintf / fdopen / fopen / fread / fclose コマ11 sessions/11_expr_walk_libc/tests/file_stream.c 標準 struct FILE * は不透明ポインタ
malloc コマ9 sessions/09_pointer_arith/tests/ptr_sum.c 標準
exit コマ11 sessions/11_expr_walk_libc/tests/lib_exit.c 標準
NULL コマ12 sessions/12_struct_malloc_list/tests/list_sum.c 標準 #define NULL 0
strcmp / strlen 相当 — sessions/11_expr_walk_libc/tests/strlen_literal.c 対象外 提供しない。必要なら自作する

実行時の意味

機能 導入コマ 代表テスト 区分 備考
型のサイズとアラインメント・struct レイアウト コマ12 sessions/12_struct_malloc_list/tests/malloc_struct.c 標準 サイズは sizeof_test.c
グローバルの 0 初期化 コマ13 sessions/13_globals_scope/tests/global_init.c 標準
文字列リテラルの静的配置 コマ10 sessions/10_strings_data_section/tests/printf_hello.c 標準 内容の変更は未定義
評価順序(&& \|\| が両辺を評価する) コマ13 sessions/13_globals_scope/tests/logical_ops.c 標準 例外 E3
未定義動作 8 種 — — 対象外 「動作を定めない」ことを定めた項目なので、動作を確かめるテストは置かない

テストを持たない項目

上の表で代表テストが — の行は、次のいずれかの理由による。 正しい入力の機能と、不正入力の診断を区別する。診断の実装範囲は次節に示す。

項目 理由
再宣言・再定義規則、仮引数との名前衝突、循環取込み、マクロ多段参照 いずれもコンパイルエラーの診断である。テストランナーの判定手段は「終了コード」と「標準出力」の 2 つで、コンパイルが失敗すること自体を期待値にする仕組みを持たない
空文 ;・for の 3 式の省略 文法上は書けるが、他の機能と独立に観測できる振舞いがないため単独のテストを置いていない
未定義動作 8 種 動作を定めない項目であり、どの結果になっても仕様違反にならない
struct 値の代入・実引数・戻り値 仕様が受理しない書き方であり、機能として存在しない(代入は発展 S3 が本仕様の禁止行を改訂して追加し、実引数・戻り値は発展 L5 が仕様の外に新しく足す)

標準トラックの診断範囲

この仕様の「コンパイル時エラー」「不可」は、Core処理系が拒否すべき入力を定める。 一方、標準トラックは正しい入力のコード生成を中心に学び、以下の診断を実装する。 fixed17とコマ2〜14の全テスト通過は、Coreの全診断要件を満たす証明ではない。 検出しない入力も仕様上は不正なままで、コンパイルできたことを許可の根拠にしない。

検査 標準トラックでの扱い
字句・構文の違反、整数リテラルの上限、使用できないエスケープ 提供Lexer/Parserで検出する
循環取込み、未対応の前処理指令、マクロ多段参照、異なる本体でのマクロ再定義 提供前処理で検出する
void変数、struct値の戻り値宣言、可変長引数の定義・固定引数なしの宣言 提供Parserで検出する
未定義変数、左辺値でない代入先、存在しないstructメンバ コード生成側で検出する
宣言・定義・呼出しの引数が9個以上(可変長部を含む) 提供Parserで検出する
未宣言関数の呼出し、8個以内での宣言との引数個数・固定引数型の一致 診断を保証しない
同一スコープの重複宣言、仮引数とローカルの名前衝突、関数・タグ・メンバの再定義規則 診断を保証しない
代入・戻り値・演算子に対する型規則の完全な検査、struct値の代入・実引数の禁止 診断を保証しない

診断を保証しない項目は、アセンブラやリンカで偶然エラーになっても検査済みとはいえない。 例えば int main() { return missing(1); } は未宣言呼出しなので、 本来はコンパイラ自身が拒否すべき入力である。

完全な診断実装は発展として扱う。 発展Q1(型検査パス)で独立した検査パスを学び、 さらに関数シグネチャとスコープの宣言表を整え、 上表の保証しない項目を検査し、不正入力ごとにコンパイラが非ゼロ終了する負テストを追加する。 その検査では、単なるツール不足や内部例外と、意図した診断を区別する。 標準の test_runner.py は実行結果を比較するための道具なので、負テストには別の検査経路を用意する。


除外機能の一覧

機能 代替
配列(VLA 含む) malloc +ポインタ・添字 p[i]、連結リスト
typedef struct Tag 直書き
enum #define 定数
union なし
float double long short unsigned なし(int / char で完結)
switch if/else 連鎖
do-while / goto while / なし
後置 ++ / -- 前置形
複合代入 += -= *= /= %= x = x + 3 の形(発展 L2 で追加する)
ビット演算・シフト アラインメントは / と *、ハッシュは %、フラグは個別の int フィールド
(type)expr キャスト void * 暗黙変換
カンマ演算子 文の列
sizeof 式 sizeof(型名)(発展 L4 で追加する)
関数ポインタ 直接呼出し+分岐
可変長引数関数の定義 外部プロトタイプのみ許可
初期化子(大域・局所) 代入文(大域は 0 初期化)
複数宣言子(int a, b;) 分割宣言
(void) 引数表記 ()
関数形式マクロ / #ifdef なし

ISO C との関係と移行上の注意

基準は ISO C11(N1570)。本仕様の受理するプログラムは、次の 3 件を除き ISO C の真部分集合である(C23 を基準にしても採用・除外の判断は変わらない)。

例外 E1 — sizeof の結果型は int。 ISO では符号なしの size_t。本仕様は符号なし整数型を持たないため int とする。 このサブセットの範囲では観測できる差はないが、通常の C では比較や 書式指定(%zu)で差が出る。

例外 E2 — 予約語は使用 12 語のみ。 ISO では全キーワードが識別子として使えないが、本仕様では typedef や switch を識別子に使える。ただし次の命名規約に従うこと:

命名規約: C のキーワードを識別子に使わない。 予約されていない C キーワード(C23 の bool true false nullptr を含む)を 識別子に使ったプログラムは、本仕様では合法でも通常の C コンパイラでは コンパイルできない。セルフホストでは第 1 段(Stage 0)を gcc でビルドするため、 この規約を破るとビルドが壊れる。

例外 E3 — && || が短絡しない。 ISO では左辺だけで結果が定まる場合、右辺は評価されない。本仕様では両辺を必ず 評価する(演算子表・「評価順序」節を参照)。構文は ISO C と同じだが意味が違う ため、p != 0 && p->val > 0 のような ISO C では安全なイディオムが、本仕様では NULL 参照になる。この差を埋めるのが発展 S1 である。

このほか、通常の C から見ると次の点が狭い(いずれも「ISO で合法な書き方の 一部だけを受理する」制限であり、例外ではない):


実行時の意味

サイズ・アラインメント・レイアウト

型 サイズ アラインメント
char 1 1
int 4 4
ポインタ 8 8
struct 下記 最大フィールドのアラインメント

struct のフィールドは宣言順に配置し、各フィールドのオフセットはそのアラインメントへ 切り上げる。struct 全体のサイズは struct のアラインメントの倍数へ切り上げる。 sizeof はこの規則で定まる値を返す。null ポインタの表現は全ビット 0。

初期化と記憶域

評価順序

未定義動作

次はコンパイルエラーにならず、実行時の動作が未定義である:

  1. 符号付き整数のオーバーフロー
  2. 0 による除算・剰余、および商が int の表現範囲に収まらない除算・剰余 (INT_MIN / -1、INT_MIN % -1)。INT_MIN は単項 - と1個の整数リテラルでは 書けないが、-2147483647 - 1 のような式で表せる。
  3. 不正なポインタ参照(null・未確保領域・確保領域外、ポインタ演算の範囲逸脱)
  4. 文字列リテラルの内容の変更
  5. 未初期化の局所変数の値の使用
  6. 非 void 関数が return なしで終端に達した後、呼出し側が戻り値を使うこと
  7. 可変長部の実引数と printf 系書式の不一致
  8. 不完全型(定義のない前方宣言のみの struct。例: struct FILE)への sizeof の適用。診断必須エラーにはしない(実装が検出してエラーにするかは処理系 定義)。

これに対し、型規則違反・構文違反・整数リテラルの範囲超過・#define の 置換規則違反・循環取込みはコンパイル時エラー(診断必須)である。


形式文法(EBNF)

再帰下降パーサー実装の参照用。文法は文脈自由であり(typedef がないため)、 字句解析がシンボルテーブルを参照する必要はない。

この節は最終形である。各コマの時点で書ける範囲だけを抜き出した累積スナップショットは、 コマ1〜14 の各資料の「この回までの言語仕様(EBNF)」節にある。

記法:
  rule ::= ...    定義
  A | B           選択(A または B)
  { A }           A の 0 回以上の繰り返し
  [ A ]           省略可能(0 または 1 回)
  ( A )           グループ化
  'token'         終端記号(キーワード・記号)
  UPPER           字句トークン(下部に定義)

二項演算子列(binary_expr)の構文木は「演算子」節の優先順位で構成する。 右結合の規則(代入・条件)は再帰で直接表している。

前処理

指令行(行頭の # から改行まで)は構文解析より前に処理され、 以下の構文には現れない。

include_dir ::= '#' 'include' '"' FILENAME '"' NEWLINE
define_dir  ::= '#' 'define' IDENT { TOKEN } NEWLINE

FILENAME は " と改行を除く 1 文字以上の文字列。{ TOKEN } は改行までの トークン列(0 個以上)。

型

型は、書ける位置ごとに 4 つの非終端記号で表す。

stars       ::= '*' { '*' }

scalar_type ::= 'int'  [ stars ]
              | 'char' [ stars ]
              | 'void' stars
              | 'struct' IDENT stars

obj_type    ::= scalar_type
              | 'struct' IDENT

ret_type    ::= scalar_type
              | 'void'

type_name   ::= obj_type

この構成により次の制約が構文レベルで決まる: void 単独の変数・引数・フィールドは書けない(void は * を伴うか 戻り値型のみ)、struct 値の引数・戻り値・フィールドは書けない、 sizeof(void) は書けない。 struct 値の引数・戻り値は、この param と ret_type を拡張して発展 L5 が追加する。

トップレベル

program       ::= external_decl { external_decl }

external_decl ::= struct_decl
                | var_decl
                | func_proto
                | func_def

struct_decl   ::= 'struct' IDENT '{' field_decl { field_decl } '}' ';'
                | 'struct' IDENT ';'                /* 前方宣言 */

field_decl    ::= scalar_type IDENT ';'

var_decl      ::= obj_type IDENT ';'

プログラムは 1 個以上の外部宣言からなる(空プログラムは不可)。 struct 定義はファイルスコープのみ、タグ必須、フィールドは 1 個以上。 初期化子は存在しない。

関数

param       ::= scalar_type IDENT
param_list  ::= param { ',' param }

func_proto  ::= ret_type IDENT '(' [ param_list [ ',' '...' ] ] ')' ';'
func_def    ::= ret_type IDENT '(' [ param_list ] ')' func_body

func_body   ::= '{' { var_decl } { stmt } '}'

宣言・定義の固定仮引数は最大8個、呼出しの実引数も可変長部を含め最大8個である。 この個数制限は上のEBNFに加えて検査する。 引数なしは ()((void) は受理しない)。仮引数は名前必須。 ... はプロトタイプ限定で、1 個以上の固定引数の後にのみ書ける ((...) 単独形は導出できない)。局所変数の宣言は関数本体の先頭のみで、 入れ子ブロックは宣言を含まない。

文

stmt        ::= expr_stmt
              | block
              | if_stmt
              | while_stmt
              | for_stmt
              | 'break' ';'
              | 'continue' ';'
              | 'return' [ expr ] ';'

expr_stmt   ::= [ expr ] ';'

block       ::= '{' { stmt } '}'

if_stmt     ::= 'if' '(' expr ')' stmt [ 'else' stmt ]

while_stmt  ::= 'while' '(' expr ')' stmt

for_stmt    ::= 'for' '(' [ expr ] ';' [ expr ] ';' [ expr ] ')' stmt

else は最も内側の未対応 if へ結合する(dangling-else は最近傍優先)。

式

expr        ::= assign_expr     /* カンマ演算子はない */

assign_expr ::= unary_expr '=' assign_expr   /* 右結合。複合代入はない */
              | cond_expr

cond_expr   ::= binary_expr [ '?' expr ':' cond_expr ]   /* 右結合 */

binary_expr ::= unary_expr { bin_op unary_expr }

bin_op      ::= '*' | '/' | '%'
              | '+' | '-'
              | '<' | '>' | '<=' | '>='
              | '==' | '!='
              | '&&'
              | '||'

unary_expr  ::= postfix_expr
              | '-'  unary_expr          /* 負号 */
              | '!'  unary_expr          /* 論理否定 */
              | '*'  unary_expr          /* 間接参照 */
              | '&'  unary_expr          /* アドレス取得 */
              | '++' unary_expr          /* 前置インクリメント */
              | '--' unary_expr          /* 前置デクリメント */
              | 'sizeof' '(' type_name ')'

postfix_expr   ::= primary_expr { postfix_suffix }
postfix_suffix ::= '[' expr ']'          /* 添字 */
                 | '.'  IDENT            /* フィールドアクセス */
                 | '->' IDENT            /* ポインタ経由フィールドアクセス */

primary_expr ::= INT_LITERAL
               | CHAR_LITERAL
               | STRING_LITERAL
               | IDENT '(' [ arg_list ] ')'   /* 関数呼び出し(識別子直呼びのみ) */
               | IDENT
               | '(' expr ')'

arg_list    ::= assign_expr { ',' assign_expr }

字句トークン

INT_LITERAL    ::= '0' | NONZERO { DIGIT }
CHAR_LITERAL   ::= "'" ( char_ch | escape ) "'"
STRING_LITERAL ::= '"' { str_ch | escape } '"'
escape         ::= '\n' | '\t' | '\\' | "\'" | '\"' | '\0'

IDENT          ::= ( ALPHA | '_' ) { ALPHA | DIGIT | '_' }
                   /* ただしキーワード 12 語を除く */

ALPHA   = [a-zA-Z]
DIGIT   = [0-9]
NONZERO = [1-9]

char_ch は '・\・改行を除く任意の文字、str_ch は "・\・改行を 除く任意の文字。

実装の注記(再帰下降)

この資料の原稿を見る