コンパイル到達目標コード集
各コマ終了時点で「コンパイルできる最も複雑なプログラム」を示す(コマ1〜15)。
前提とする言語仕様: language_spec.md(第 2 版、2026 年改訂)。
ここに載せるコードは、すべてその版で受理される範囲に収めてある。
目次(コマ → 書けるようになるもの)
| コマ | テーマ | この回で書けるようになるもの |
|---|---|---|
| 0 | 環境構築 + RV64 手書きアセンブリ | 手書きアセンブリ(コンパイラはまだ無い) |
| 1 | AST 理解 + インタープリター | 算術式の評価(eval_ast()) |
| 2 | コード生成①:算術式 | 算術式 → アセンブリ |
| 3 | コード生成②:変数・代入 | 変数宣言・代入・複数文 |
| 4 | 制御構文①:if / else + 三項演算子 | if / else、? : |
| 5 | 制御構文②:while / for + 前置 ++/-- |
ループ、前置 ++ / -- |
| 6 | 関数呼び出し・再帰 | 関数定義・呼出し・再帰 |
| 7 | lvalue / rvalue + ポインタ | & / *、ポインタ経由の代入 |
| 8 | 型検査の導入 | char と int の使い分け、型別のロード / ストア |
| 9 | ポインタ演算とスケーリング | a[i]、*(p + 2)、malloc した領域 |
| 10 | 文字列リテラルと .data セクション |
文字列、printf |
| 11 | 式の走査と libc 活用 | 式の中の文字列リテラル、lib.h の残りの関数 |
| 12 | 構造体とヒープ | struct 定義・メンバアクセス・連結リスト |
| 13 | グローバル変数・スコープ管理 | グローバル変数 |
| 14 | 複数ファイル・前処理の概念 | #include / #define、複数ファイル |
| 15 | Python 版総合演習・mycc.py 統合 |
標準トラック完成(fixed17 テスト一覧) |
| — | 付録: 機能とコマの対応 | 機能名から回を引きたいとき |
機能名から回を引きたいときは
language_spec.md の対応表が唯一の出典である。
凡例
| 項目 | 説明 |
|---|---|
| フロントエンド | mycc.py(Python 版) |
| 期待する結果 | qemu-riscv64 ./out; echo $? の終了コード(標準出力がある場合は別途記載) |
| 検証コマンド | python3 mycc.py input.c \| riscv64-linux-gnu-gcc -x assembler -static - -o out |
コマ0(準備回): 環境構築 + RV64 手書きアセンブリ
コンパイラはまだ存在しない。手書きアセンブリを qemu で動かすことが目標。
# hello.s — 学習者が手で書く
.global main
main:
addi a0, zero, 42 # 戻り値 = 42
retriscv64-linux-gnu-gcc -static hello.s -o hello
qemu-riscv64 ./hello; echo $? # → 42コマ1(Phase 0): AST 理解 + インタープリター
コンパイラはまだ存在しない。教員提供の Lexer/Parser が返す AST を Python で評価する。
# 学習者が書くインタープリター(除算はゼロ方向切り捨て。教員提供の c_div を使う。
# Python の // は床除算で丸め方向が異なるため使わない)
def eval_ast(node):
if node.kind == 'Num': return node.val
if node.kind == 'Add': return eval_ast(node.lhs) + eval_ast(node.rhs)
if node.kind == 'Sub': return eval_ast(node.lhs) - eval_ast(node.rhs)
if node.kind == 'Mul': return eval_ast(node.lhs) * eval_ast(node.rhs)
if node.kind == 'Div': return c_div(eval_ast(node.lhs), eval_ast(node.rhs))評価できる式の例:
1 + 2 * 3 → 7
(10 - 3) * 2 → 14
100 / 4 + 1 → 26
コマ2(Phase 1): コード生成①:算術式
フロントエンド: mycc.py
新機能: 整数定数・四則演算・剰余・カッコ → RV64 アセンブリ出力
int main() {
return 1 + 2 * 3;
}期待する終了コード: 7
int main() {
return (100 - 3 * 7) / 4 + 1;
}期待する終了コード: 20 (3*7=21, 100-21=79, 79/4=19, 19+1=20)
コマ3(Phase 1): コード生成②:変数・代入・シンボルテーブル
フロントエンド: mycc.py
新機能: ローカル変数宣言・代入・複数変数の管理、関数本体(Block ノード)からの宣言収集とフレームサイズの計算
int main() {
int a;
int b;
int c;
a = 3;
b = 5;
c = a + b;
return c;
}期待する終了コード: 8
int main() {
int x;
int y;
x = 10;
y = x * 2 + 3;
x = y - x;
return x;
}期待する終了コード: 13 (y=23, x=23-10=13)
int main() {
int a;
int b;
int c;
int d;
int e;
int f;
a = 1;
b = 2;
c = 3;
d = 4;
e = 5;
f = 6;
return a + b + c + d + e + f;
}期待する終了コード: 21
確認ポイント: 6変数(48バイト)+ ra/s0(16バイト)= 64バイト(16の倍数 ✓)。 フレームサイズを
align_to(n, 16)で16バイト境界に揃え、ローカル変数が0個でも正しく動くこと。
コマ4(Phase 1): 制御構文①:if / else + 三項演算子
フロントエンド: mycc.py
新機能: if / else if / else、比較演算子(== != < > <= >=)、三項演算子 ?:、入れ子ブロック、複数の return
int main() {
int a;
int b;
a = 10;
b = 3;
if (a > b) {
return a - b;
} else {
return b - a;
}
}期待する終了コード: 7
int main() {
int score;
score = 75;
if (score >= 90) {
return 4;
} else if (score >= 70) {
return 3;
} else if (score >= 50) {
return 2;
} else {
return 1;
}
}期待する終了コード: 3
int main() {
int a;
int b;
a = 3;
b = 8;
return a > b ? a : b;
}期待する終了コード: 8
int main() {
int x;
int y; // 宣言は関数本体の先頭にまとめる
x = 3;
if (x == 3) {
y = 4; // 入れ子ブロックでは使うだけ
return x + y;
}
return 0;
}期待する終了コード: 7
宣言の位置: 宣言を書けるのは関数本体の先頭だけで、入れ子ブロックには文しか書けない。 入れ子ブロックは新しいスコープを作らないので、
ifの中のyは関数先頭で宣言したyそのものである。
式と文の違い:
ifは「実行する文」を選ぶ文であり、それ自体は値を持たない。 三項演算子?:は「値」を選ぶ式であり、式の中に書ける。 コード生成はどちらも同じ分岐(beqz+ ラベル)だが、?:は選ばれた枝の値が レジスタに残る点だけが異なる。
コマ5(Phase 1): 制御構文②:while / for + 前置 ++/--
フロントエンド: mycc.py
新機能: while、for、break、continue、前置 ++/--
int main() {
int i;
int sum;
i = 1;
sum = 0;
while (i <= 10) {
sum = sum + i;
i = i + 1;
}
return sum;
}期待する終了コード: 55
int main() {
int i;
int fib_a;
int fib_b;
int tmp;
fib_a = 0;
fib_b = 1;
for (i = 0; i < 10; ++i) {
tmp = fib_b;
fib_b = fib_a + fib_b;
fib_a = tmp;
}
return fib_a;
}期待する終了コード: 55(フィボナッチ数列の第10項)
前置
++:++iはiの値を 1 増やし、増やした後の値を式の値とする。 for の更新式の慣用形として使う。実装は「左辺値のアドレスを 1 回だけ求め、 ロード → +1 → ストア」であり、代入のコード生成の応用で書ける。
コマ6(Phase 1): 関数呼び出し・再帰
フロントエンド: mycc.py
新機能: ユーザー定義関数の宣言・定義・呼び出し、引数(a0〜a7)、再帰・相互再帰
int fib(int n) {
if (n <= 1) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
int main() {
return fib(10);
}期待する終了コード: 55
int add(int a, int b) {
return a + b;
}
int mul(int a, int b) {
int i;
int result;
result = 0;
for (i = 0; i < b; i = i + 1) {
result = add(result, a);
}
return result;
}
int main() {
return mul(6, 7);
}期待する終了コード: 42
コマ7(Phase 1): lvalue / rvalue + ポインタ
フロントエンド: mycc.py
新機能: codegen / codegen_lval の2関数設計、アドレス取得(&)、間接参照(*)
int main() {
int x;
int *p;
x = 42;
p = &x;
return *p;
}期待する終了コード: 42
int main() {
int a;
int *p;
a = 10;
p = &a;
*p = 20;
return a;
}期待する終了コード: 20(*p = 20 が a に書き込まれる)
コマ8(Phase 1): 型検査の導入
フロントエンド: mycc.py
新機能: ty_str 文字列による型の管理、型サイズ(int 4 / char 1 / T * 8)、型別のロード / ストア、char の昇格と縮小、文字リテラル 'a'
実装ポイント: このコマから型サイズの管理をコード生成器に導入する。 スキャフォールドでは
Typeクラスは使わず、ty_str文字列とsize_of_ty_str()/elem_ty_str()ヘルパーで型を扱う。 後半の C 移植ではstruct Typeに対応させる。
Cコード ty_strサイズ ロード / ストア int a;int4 lw/swchar c;char1 lb/sbint *p;int*8 ld/sd
charは式の中では int に昇格し、char変数への代入では下位 8 ビットに縮む。
int main() {
char c;
char d;
int n;
c = 'A';
d = c + 2;
n = d;
if (n != 67) {
return 1;
}
c = 321;
if (c != 65) {
return 2;
}
return n;
}期待する終了コード: 67('A' + 2 は int で 67。c = 321 は下位 8 ビットの 65 に縮む)
int main() {
char c;
int n;
int *p;
c = 9;
n = 4;
p = &n;
*p = 30;
return n + c;
}期待する終了コード: 39(char は lb / sb、int は lw / sw、ポインタは ld / sd)
コマ9(Phase 1): ポインタ演算とスケーリング
フロントエンド: mycc.py
新機能: ポインタ演算(p + n)、添字 p[i]、sizeof(型名)、多段ポインタ int **、malloc による連続領域の確保、型対応の前置 ++
実装ポイント: コマ8 で入れた型サイズを、ポインタを進めるときの尺度に使う。 連続した int の並びは、配列ではなく
malloc(sizeof(int) * N)で確保した 領域として作る。p[i]は*(p + i)の略記であり、どちらも同じコードになる。sizeof(型名)は翻訳時に値が決まる定数で、li1 命令に落ちる。
#include "lib.h"
int main() {
int *a;
int i;
int sum;
a = malloc(sizeof(int) * 5);
a[0] = 1;
a[1] = 2;
a[2] = 3;
a[3] = 4;
a[4] = 5;
sum = 0;
for (i = 0; i < 5; ++i) {
sum = sum + a[i];
}
return sum;
}期待する終了コード: 15
#include "lib.h"
int main() {
int *a;
int *p;
a = malloc(sizeof(int) * 4);
a[0] = 10;
a[1] = 20;
a[2] = 30;
a[3] = 40;
p = a;
return *(p + 2) + *(p + 3);
}期待する終了コード: 70(p + 2 は 2 * sizeof(int) バイト進む)
コマ10(Phase 1): 文字列リテラルと .data セクション
フロントエンド: mycc.py
新機能: 文字列リテラル、.data セクションへの出力とラベル参照、printf、#include "lib.h"
#include "lib.h"
int main() {
printf("Hello, World!\n");
printf("%d\n", 42);
return 0;
}期待する標準出力:
Hello, World!
42
コマ11(Phase 1): 式の走査と libc 活用
フロントエンド: mycc.py
新機能: 式のどこに書かれた文字列リテラルも .data に出す走査、不透明ポインタ struct FILE *、fprintf / fdopen / fopen / fread / fclose、exit
実装ポイント: 新しい仕組みはほとんど無い。コマ10 で文に対して書いた走査を、 式の種類の数だけ書き足して網羅する回である。ここまでで 「小さいが動く Python 版コンパイラ」が一度完成する。
int main() {
if ("abc"[1] != 'b') {
return 1;
}
if (*("abc" + 2) != 'c') {
return 2;
}
return 42;
}期待する終了コード: 42(添字・ポインタ加算の奥にある文字列リテラルも .data に出る)
int my_strlen(char *s) {
int n;
n = 0;
while (s[n] != '\0') {
n = n + 1;
}
return n;
}
int main() {
return my_strlen("abc");
}期待する終了コード: 3(strlen 相当は lib.h に無いので自作する)
コマ12(Phase 2): 構造体とヒープ(struct / . / -> / sizeof / malloc / 連結リスト)
フロントエンド: mycc.py
新機能: struct 定義(タグ必須)、メンバアクセス(. / ->)、構造体のレイアウトとパディング、sizeof(struct タグ)、構造体の malloc、void * の暗黙変換、自己参照構造体と連結リスト、NULL
struct Point {
int x;
int y;
};
int distance_sq(struct Point *p) {
return p->x * p->x + p->y * p->y;
}
int main() {
struct Point p;
p.x = 3;
p.y = 4;
return distance_sq(&p);
}期待する終了コード: 25(3² + 4² = 25)
#include "lib.h"
struct Node {
int val;
struct Node *next;
};
int list_sum(struct Node *head) {
int sum;
sum = 0;
while (head != NULL) {
sum = sum + head->val;
head = head->next;
}
return sum;
}
struct Node *new_node(int v) {
struct Node *n;
n = malloc(sizeof(struct Node)); // struct のサイズを sizeof で求める
n->val = v;
n->next = NULL;
return n;
}
int main() {
struct Node *a;
struct Node *b;
struct Node *c;
a = new_node(10);
b = new_node(20);
c = new_node(30);
a->next = b;
b->next = c;
return list_sum(a);
}期待する終了コード: 60
コマ13(Phase 2): グローバル変数・スコープ管理
フロントエンド: mycc.py
新機能: グローバル変数(.bss セクション。0 初期化が保証される)
#include "lib.h"
int call_count;
int total;
int add_and_count(int x) {
total = total + x;
call_count = call_count + 1;
return total;
}
int main() {
add_and_count(10);
add_and_count(20);
add_and_count(30);
printf("%d\n", total);
return call_count;
}0 初期化の保証: グローバル変数は明示的に代入しなくても 0 で始まる (ポインタなら null)。
.bssセクションに.zeroで配置することで、 OS がプログラム開始時に 0 埋めしてくれる仕組みをそのまま使っている。 期待する標準出力:60期待する終了コード:3
コマ14(Phase 2): 複数ファイル・前処理の概念
フロントエンド: mycc.py
新機能: #include "file.h"、オブジェクト形式 #define
// math_util.h
int my_abs(int x);
int my_max(int a, int b);
int my_pow(int base, int exp);// math_util.c
int my_abs(int x) {
if (x < 0) return -x;
return x;
}
int my_max(int a, int b) {
if (a > b) return a;
return b;
}
int my_pow(int base, int exp) {
int result;
int i;
result = 1;
for (i = 0; i < exp; i = i + 1) {
result = result * base;
}
return result;
}// main.c
#include "math_util.h"
#define BASE 2
#define EXP 6
int main() {
int a;
int b;
a = my_abs(-7);
b = my_pow(BASE, EXP);
return my_max(a, b);
}期待する終了コード: 64(max(7, 64) = 64)
コマ15(Phase 2): Python 版総合演習・mycc.py 統合 ← 標準トラック達成
フロントエンド: mycc.py
確認: コマ15 時点で python3 scaffold/test_runner.py(final/mycc.py + final/tests/)を実行し、fixed17 全17問の通過を標準トラック完成の目安とする。
fixed17 テスト一覧(全17問)
| # | ファイル | 出題意図 | 使用機能 |
|---|---|---|---|
| 1 | f01_arith.c | 四則演算の優先順位 | 加減乗除・カッコ |
| 2 | f02_vars.c | 複数ローカル変数の宣言・代入・演算 | ローカル変数・代入 |
| 3 | f03_if.c | if/else if/else チェーン + 関数呼び出し | if/else if/else・比較演算子・関数定義 |
| 4 | f04_while.c | while ループで累積和 | while・変数更新 |
| 5 | f05_for.c | for ループで階乗計算 | for・複数変数同時更新 |
| 6 | f06_break.c | break によるループ脱出 | break・for・if |
| 7 | f07_continue.c | continue によるスキップ | continue・for・if |
| 8 | f08_func.c | 多引数関数の合成呼び出し | 関数定義・引数・戻り値 |
| 9 | f09_recur.c | 再帰呼び出し(フィボナッチ) | 再帰・スタックフレーム |
| 10 | f10_ptr.c | ポインタ渡し swap | &・*・間接代入・void 関数 |
| 11 | f11_ptr_arith.c | malloc 領域の添字・ポインタ走査 | malloc・sizeof・添字・ポインタ引数 |
| 12 | f12_struct.c | 構造体 + ドット・アロー両方 | struct 定義・. / -> 演算子 |
| 13 | f13_global.c | グローバル変数 | .bss セクション・0 初期化・スコープ |
| 14 | f14_string.c | printf 文字列出力 | 文字列リテラル・可変長引数関数呼出 |
| 15 | f15_define.c | #define マクロ + for ループ | #define・for |
| 16 | f16_ternary.c | 三項演算子(値を持つ分岐) | ?:・入れ子・関数引数 |
| 17 | f17_incr.c | 前置インクリメント | 前置 ++(int とポインタ) |
標準判定用テストは
final/tests/に配置されている。python3 scaffold/test_runner.pyで一括実行できる。
fixed17 は総合判定であって、仕様の全機能を 1 対 1 で覆う集合ではない。
機能ごとの代表テストは各コマの sessions/NN_xxx/tests/ にあり、
その対応は language_spec.md の対応表にまとめてある。
固定テストセットの代表例(malloc + ポインタ + 関数の複合):
// f11: ポインタ演算 — malloc した領域を sizeof と添字・ポインタ走査で使う
#include "lib.h"
int sum(int *a, int n) {
int i;
int s;
s = 0;
for (i = 0; i < n; ++i) {
s = s + *(a + i);
}
return s;
}
int main() {
int *a;
a = malloc(sizeof(int) * 5);
a[0] = 10;
a[1] = 20;
a[2] = 30;
a[3] = 5;
a[4] = 7;
return sum(a, 5);
}期待する終了コード: 72
付録: 機能とコマの対応
どの機能をどのコマで導入し、どのテストで検証するかは
language_spec.md の対応表にまとめてある。
仕様の全機能を行に持つ表なので、この文書のコマ別の節と併せて使う
(この文書は「各コマの終わりに何が動くか」を実例で示し、対応表は
「仕様のどの項目がどこで埋まるか」を網羅する)。
コマ15(final/tests 全通)で実装は完成する。学習経路の完了はコマ16(発表会)だが、
発表会を待たずに選択制の発展課題(../advanced/README.md)へ進んでよい。
C 移植・セルフホスト(P1)の進め方は ../advanced/P1_selfhost/README.md にある。