# BASICコンパイラコア — 字句解析・コード生成・CMPDATA

> compiler.c が担う 1 パスコンパイル処理、statements.c によるステートメント解析、operators.c の演算子優先順位、cmpdata.c のコンパイル時メタデータ管理（変数名・ラベル・クラス情報の格納と検索）、ARMv6-M サムネイルオブジェクトコード生成の仕組みを解説します。

- Repository: machikania/phyllosoma
- GitHub: https://github.com/machikania/phyllosoma
- Human wiki: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c
- Complete Markdown: https://grok-wiki.com/public/wiki/machikania-phyllosoma-fab169c4381c/llms-full.txt

## Source Files

- `compiler.c`
- `compiler.h`
- `statements.c`
- `operators.c`
- `cmpdata.c`
- `functions.c`

---

<details>
<summary>関連するソースファイル</summary>
このWikiページの生成に使用したファイル:
- [compiler.c](compiler.c)
- [compiler.h](compiler.h)
- [cmpdata.c](cmpdata.c)
- [statements.c](statements.c)
- [operators.c](operators.c)
- [functions.c](functions.c)
- [variable.c](variable.c)
- [value.c](value.c)
- [integer.c](integer.c)
</details>

# BASICコンパイラコア — 字句解析・コード生成・CMPDATA

machikania/phyllosoma の BASIC コンパイラは、ソーステキストを **1パスで直接 ARMv6-M Thumb バイナリ**（`unsigned short` の配列として RAM 上に生成）へ変換するシステムです。字句解析・ステートメント分岐・演算子評価・コード出力がすべて C 関数の呼び出しチェーンとして実装されており、中間 AST は存在しません。コンパイル時に必要なメタデータ（変数名・ラベル・前方参照パッチ先・クラス情報）は **CMPDATA** と呼ばれる独自のコンパクトなレコード群で管理されます。

このページでは `compiler.c`・`statements.c`・`operators.c`・`cmpdata.c` が担う各責務、それらの連携方式、生成される Thumb 機械語の構造を解説します。

---

## 全体アーキテクチャ

```text
ソーステキスト (unsigned char*)
        |
        v
  code2upper()          ── 小文字→大文字、タブ→スペース、文字列リテラル内は保護
        |
        v
  compile_line()        ── 行番号の登録・取得、複数ステートメント(:区切り)ループ
        |
        v
  compile_statement()   ── instruction_is() でキーワードを判別し各ハンドラへ
       / \
      /   \
 let_statement()   goto_statement() … (他多数)
      |
      v
 get_integer() / get_float() / get_string()
      |
      v
 get_value_sub()  ←→  get_operator() / calculation()   演算子優先度付き再帰
      |
      v
 object[] へ 16bit Thumb 命令を直接書き出す
      |
      v
  CMPDATA          ── 変数名・ラベル・前方参照BLアドレスを一時保存
```

Sources: [compiler.c:251-310](), [statements.c:1484-1585]()

---

## compiler.c — コンパイルパイプライン

### 初期化と終了処理

`init_compiler()` はコンパイル開始時に一度だけ呼ばれ、オブジェクト出力ポインタ `object`、CMPDATA 領域、変数テーブルを初期化します。

```c
// compiler.c
void init_compiler(void){
    object=&kmbasic_object[0];
    g_objmax=&kmbasic_object[(sizeof kmbasic_object)/2];
    cmpdata_init();
    variable_init();
    g_class_id=0;
}
```

ファイル単位の初期化は `begin_file_compiler()` が担い、`IF`/`FOR` の深さカウンタや行番号をリセットします。コンパイル完了後は `end_file_compiler()` が未解決の前方参照（`CMPDATA_GOTO_NUM_BL`・`CMPDATA_GOTO_LABEL_BL` 等）の残留を検査しエラーを返します。

Sources: [compiler.c:37-52](), [compiler.c:55-112]()

### compile_line() — 行コンパイルの核

```c
int compile_line(unsigned char* code){
    g_linenum++;
    before=source=code2upper(code);          // 正規化
    e=get_positive_decimal_value();           // 行番号取得を試みる
    if (0<=e) { register_line_number(e); handle_line_number(e); }
    // ステートメントループ
    while(1){
        e=compile_statement();
        if (e) break;
        if (source[0]==0x00) break;
        if (source[0]!=':') { e=ERROR_SYNTAX; break; }
        source++;                             // ':' で次ステートメントへ
    }
    ...
}
```

`code2upper()` は入力行を作業バッファ `g_compile_buffer` にコピーしながら英小文字を大文字へ変換します。文字列リテラル（`"..."` 内）は変換対象外です。行末 (`\r`/`\n`/`\0`) で変換を打ち切り、戻り値として消費バイト数を返します。

Sources: [compiler.c:176-230](), [compiler.c:251-310]()

### Thumb 命令出力ユーティリティ

| 関数 | 生成するコード | 用途 |
|---|---|---|
| `set_value_in_register(r, val)` | `movs rx, #val` / `ldr rx, [pc, #4]` + インライン定数 | 任意整数をレジスタへロード |
| `call_lib_code(lib_number)` | `movs r3, #lib` + `blx r8` | ランタイムライブラリ呼び出し |
| `update_bl(bl, dest)` | BL 命令 2 ハーフワードをパッチ | 前方参照の解決 |

`set_value_in_register` は値の範囲に応じて最小バイト数の命令列を選択します。`0–255` なら 1 命令（`movs`）、それ以外は PC 相対 `ldr` + インライン 32bit 定数（アライメントを考慮して NOP を挿入）を使います。

Sources: [variable.c:27-57](), [compiler.c:130-143]()

---

## statements.c — ステートメント解析

### compile_statement() — 大統一ディスパッチャ

`compile_statement()` は `instruction_is()` によるキーワード照合を順次試みます。

```c
int compile_statement(void){
    // まず LET を試みる（"LET" の明示省略も許容）
    if (instruction_is("LET")) return let_statement();
    e=let_statement();
    if (!e) return 0;
    rewind_object(bobj); source=bsrc;
    // 以下、アルファベット順に全ステートメントを検査
    if (instruction_is("BREAK"))    return break_statement();
    if (instruction_is("DATA"))     return data_statement();
    if (instruction_is("FOR"))      return for_statement();
    if (instruction_is("GOTO"))     return goto_statement();
    if (instruction_is("IF"))       return if_statement();
    if (instruction_is("LABEL"))    return label_statement();
    ...
    // サブモジュールへ委譲
    e=io_statements();
    if (e!=ERROR_STATEMENT_NOT_DETECTED) return e;
    e=display_statements(); ...
    e=wifi_statements(); ...
    e=aux_statements(); ...
}
```

`instruction_is()` はソースポインタを前進させながらキーワードと比較し、次の文字が区切り文字（空白・`:`・`,`・`\0`）であることを確認します。マッチしない場合はポインタを戻します。

Sources: [statements.c:1484-1585](), [compiler.c:151-170]()

### GOTO / GOSUB と前方参照処理

ラベルへのジャンプは、コンパイル時点で宛先が未確定な場合に「プレースホルダ BL」を出力し、CMPDATA に BL のアドレスを記録します。ラベル定義が後から現れたとき `update_bl()` でパッチします。

```c
// 宛先未確定の場合
(object++)[0]=0xf000; // bl 仮エンコード
(object++)[0]=0xf800;
g_scratch_int[0]=(int)object-4;
cmpdata_insert(CMPDATA_GOTO_LABEL_BL, id, g_scratch_int, 1);

// ラベル定義時
while(data=cmpdata_findfirst_with_id(CMPDATA_GOTO_LABEL_BL, id)){
    bl=(short*)data[1];
    update_bl(bl, object);      // BL を正しいオフセットで上書き
    cmpdata_delete(data);
}
```

行番号による GOTO も同様で `CMPDATA_GOTO_NUM_BL` を使います。

Sources: [statements.c:467-530]()

### DATA / CDATA ステートメント

`DATA` はオブジェクトコード中に文字列・整数リテラルをインライン埋め込みます。BL 命令でデータブロック全体を読み飛ばすエピローグコードが先頭に置かれ、`MOV R5,R5`（opcode `0x462d`）がデータブロック開始のマーカーとなります。`CDATA` は 1 バイト単位のデータで `MOV R6,R6`（`0x4636`）がマーカーです。

Sources: [statements.c:201-260]()

---

## operators.c — 演算子優先順位と Thumb コード生成

### 優先順位テーブル

```c
const unsigned char g_priority[]={
    0, // OP_VOID
    1, // OP_OR
    2, // OP_AND
    3, // OP_XOR
    4,4, // OP_EQ, OP_NEQ
    5,5,5,5, // OP_LT, OP_LTE, OP_MT, OP_MTE
    6,6, // OP_SHL, OP_SHR
    7,7, // OP_ADD, OP_SUB
    8,8,8 // OP_MUL, OP_DIV, OP_REM
};
```

数値が大きいほど優先度が高く（乗算・除算が最高）、`compiler.h` の `priority(x)` マクロでテーブルを参照します。

Sources: [operators.c:20-30]()

### 整数演算のコード生成

ほとんどの演算子は 1〜7 命令の直接 Thumb エンコードで処理されます。除算・剰余のみランタイムを呼び出します。

| 演算子 | 生成される Thumb | 備考 |
|---|---|---|
| `OR` | `orrs r0, r1` (`0x4308`) | 1命令 |
| `AND` | `ands r0, r1` (`0x4008`) | 1命令 |
| `XOR` | `eors r0, r1` (`0x4048`) | 1命令 |
| `ADD` | `adds r0, r1, r0` (`0x1808`) | 1命令 |
| `SUB` | `subs r0, r1, r0` (`0x1a08`) | 1命令 |
| `MUL` | `muls r0, r1` (`0x4348`) | 1命令 |
| `DIV` / `REM` | `call_lib_code(LIB_CALC)` | ランタイム委譲 |
| `EQ` | `subs`→`negs`→`adcs` | 3命令ブランチレス |
| `<` | 7命令の符号付き比較列 | |
| `SHL` | `lsls r1, r0` + `movs r0, r1` | 2命令 |

Sources: [operators.c:100-200]()

### 浮動小数点演算

浮動小数点では `get_float_operator()` が `XOR`・`%`・`<<`・`>>` を拒否し、残りを `LIB_CALC_FLOAT` ランタイムへ委譲します。

```c
int float_calculation(int op){
    switch(op){
        case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV:
        case OP_EQ:  case OP_NEQ: case OP_LT:  case OP_LTE:
        case OP_MT:  case OP_MTE: case OP_OR:  case OP_AND:
            set_value_in_register(2, op);
            return call_lib_code(LIB_CALC_FLOAT);
        ...
    }
}
```

Sources: [operators.c:216-245]()

---

## cmpdata.c — コンパイル時メタデータ管理

### メモリレイアウト

CMPDATA はオブジェクトコード出力バッファの**上端**（`g_objmax`）から下方向へ伸びます。オブジェクトコードは下端（`kmbasic_object[0]`）から上方向へ伸びるため、両者が衝突したとき `ERROR_OBJ_TOO_LARGE` が返ります。

```text
kmbasic_object[]
 低アドレス                          高アドレス
 +------------------+-----------+--------------------+
 | オブジェクトコード→ |  (空き)   | ←CMPDATA レコード群 |
 +------------------+-----------+--------------------+
 object             g_objmax
```

Sources: [cmpdata.c:55-65]()

### レコードフォーマット

```text
MSB                              LSB
+--------+--------+----------------+
|  type  |  len   |    data16      |  ← record[0] (32bit)
+--------+--------+----------------+
|           record[1]              |  ← ペイロード先頭
+----------------------------------+
|           record[2]              |
+----------------------------------+
        … (len-1 個)
```

- **type** (8bit): `CMPDATA_VARNAME`・`CMPDATA_LINENUM`・`CMPDATA_LABEL` などの種別定数
- **len** (8bit): ヘッダ含む 32bit ワード数（最小 1）
- **data16** (16bit): 汎用 16bit フィールド（変数番号・ラベル ID 等）

Sources: [cmpdata.c:15-30]()

### 主要な CMPDATA 型一覧

| 定数 | 用途 | ペイロード |
|---|---|---|
| `CMPDATA_VARNAME` | 変数名→変数番号のマッピング | hash + 変数名文字列 |
| `CMPDATA_LINENUM` | 行番号→オブジェクトアドレス | `(int)object` |
| `CMPDATA_LABEL` | ラベル ID→オブジェクトアドレス | `(int)object` |
| `CMPDATA_LABELNAME` | ラベル文字列→ラベル ID | hash + ラベル名文字列 |
| `CMPDATA_GOTO_NUM_BL` | 未解決行番号ジャンプ | 仮 BL のアドレス |
| `CMPDATA_GOTO_LABEL_BL` | 未解決ラベルジャンプ | 仮 BL のアドレス |
| `CMPDATA_IF_BL` | 未解決 IF 分岐 | 仮 BL のアドレス |
| `CMPDATA_ENDIF_BL` | 未解決 ENDIF | 仮 BL のアドレス |
| `CMPDATA_BREAK_BL` | 未解決 BREAK | 仮 BL のアドレス |
| `CMPDATA_CLASSNAME` | クラス名 | クラス名文字列 |
| `CMPDATA_CLASS` | クラス定義情報 | クラスメタデータ |
| `CMPDATA_METHOD` | メソッド情報 | — |
| `CMPDATA_FIELDNAME` | フィールド名 | フィールド名文字列 |
| `CMPDATA_STRSTACK` | 文字列スタック | 文字列データ |
| `CMPDATA_STATIC` | static 変数情報 | — |

Sources: [compiler.h:198-225]()

### 検索 API

文字列型レコードの検索は **ハッシュ** で高速化されています。

```c
// cmpdata.c
int cmpdata_nhash(const unsigned char* str, int num){
    int hash=0;
    for(i=0;i<num;i++){
        hash = hash<<6 ^ hash>>26;
        hash ^= str[i];
    }
    return hash;
}

int* cmpdata_nsearch_string(unsigned int type, unsigned char* str, int num){
    int hash=cmpdata_nhash(str,num);
    while(data=cmpdata_find(type)){
        if (hash!=data[1]) continue;    // ハッシュ不一致は即スキップ
        // バイト列比較 ...
        if (一致) return data;
    }
    return 0;
}
```

非文字列型の検索は `cmpdata_find()` / `cmpdata_findfirst()` / `cmpdata_findfirst_with_id()` が `type` フィールドと `data16`（ID）の組み合わせで線形スキャンします。

Sources: [cmpdata.c:210-255]()

### 挿入・削除

`cmpdata_insert()` は `g_cmpdata` ポインタを下方向へ `num+1` ワード移動させ、`g_objmax` を更新します。`CMPDATA_STRSTACK` 型だけは既存レコードの先頭に割り込む特別なスタックセマンティクスを持ちます。

`cmpdata_delete()` は削除対象レコードの上にある全レコードを `delnum` ワード分シフトダウンして穴を埋め、`g_cmpdata` を戻します。`rewind_object()` から呼ばれる `cmpdata_delete_invalid()` は、巻き戻されたオブジェクトポインタより先を指す前方参照レコードを一括削除します。

Sources: [cmpdata.c:87-190]()

---

## ARMv6-M Thumb オブジェクトコード生成

### レジスタ規約

| レジスタ | 役割 |
|---|---|
| R0 | 計算結果・第1引数 |
| R1 | 第2引数・一時 |
| R2 | 第3引数（ライブラリオプション） |
| R3 | ライブラリ番号（`call_lib_code` 時） |
| R5 | `&kmbasic_variables[0]`（変数配列ベース） |
| R6 | 引数スタックフレームポインタ |
| R7 | `&kmbasic_data[0]`（ランタイムデータ） |
| R8 | ライブラリ呼び出しエントリ（`blx r8`） |

Sources: [compiler.c:13-30]()

### ライブラリ呼び出し規約

```c
// call_lib_code() が生成するコード（2ハーフワード）
0x2300 | lib_number   // movs r3, #lib_number
0x47c0                 // blx  r8
```

`lib_number` が 0–127 は「クイックライブラリ」（計算・関数）、128–255 は「ステートメントライブラリ」（ガベージコレクション・Break チェック付き）です。

Sources: [compiler.c:144-150](), [compiler.h:64-80]()

### 変数アクセスパターン

```c
// r0_to_variable(): R0の値を変数番号 vn に格納
// vn が 0-31 (A-Z 単文字) の場合
(object++)[0] = 0x6028 | (vn<<6); // str  r0, [r5, #vn*4]
(object++)[0] = 0x2300;            // movs r3, #0
(object++)[0] = 0x68ba;            // ldr  r2, [r7, #8]   ← var_size 配列
(object++)[0] = 0x8013 | (vn<<6); // strh r3, [r2, #vn*2]
```

単文字変数（A–Z）はオフセット計算が即値でできるため高速です。長名変数（CMPDATA_VARNAME で管理）は `set_value_in_register(1, vn*4)` で動的に計算します。

Sources: [variable.c:80-100]()

### BL 前方参照パッチ (`update_bl`)

```c
void update_bl(short* bl, short* destination){
    int i = (int)destination - ((int)bl + 4);
    i >>= 1;
    bl[1] = 0xf800 | (i & 0x7ff);
    i >>= 11;
    bl[0] = 0xf000 | (i & 0x7ff);
}
```

Thumb BL 命令は 2 つの 16bit ハーフワードで構成されます。`update_bl()` はオフセットをビットフィールドに分割し、すでにオブジェクトコード中に出力済みの仮 BL 命令（`0xf000`/`0xf800`）を正しいアドレスで上書きします。

Sources: [compiler.c:127-133]()

---

## 変数名の登録と解決フロー

```mermaid
sequenceDiagram
    participant S as source ポインタ
    participant V as variable.c<br/>get_var_number()
    participant C as cmpdata.c

    S->>V: 識別子を読む
    V->>C: cmpdata_nsearch_string_first(CMPDATA_VARNAME, name, len)
    C-->>V: 見つかった: data[0]&0xffff = 変数番号
    V-->>S: 変数番号を返す
    Note over V,C: 未登録の長名変数は<br/>CMPDATA_VARNAME に<br/>cmpdata_insert_string() で追加
```

単文字変数（A–Z）は 0–25 番に直接マップされ CMPDATA を介しません。2 文字以上の変数名は `CMPDATA_VARNAME` へ文字列とハッシュを格納し、`get_new_varnum()` で 26 番以降の番号が割り当てられます。

Sources: [variable.c:68-80]()

---

## まとめ

phyllosoma の BASICコンパイラは中間表現を持たない徹底したシングルパス設計です。`compile_line()` が行を正規化してトークン列を `source` グローバルポインタとして公開し、`compile_statement()` が `instruction_is()` によるキーワード照合で各ハンドラへ分岐、各ハンドラが `object` ポインタへ Thumb 命令を直接書き出します。前方参照はすべて CMPDATA に仮記録して `update_bl()` でパッチするため、データ構造の複雑さがランタイムメモリを消費せず、コンパイル完了後は `cmpdata_delete_all()` で解放されます。演算子優先順位は `g_priority[]` テーブルと再帰的な `get_value_sub()` で実現し、整数演算はほぼ全て直接 Thumb 命令列、浮動小数演算はランタイム委譲という明確な役割分担が取られています。

Sources: [compiler.c:251-310](), [cmpdata.c:55-65](), [operators.c:20-30]()
