diff --git a/docs/development/proposals/env-var-reduction-report.md b/docs/development/proposals/env-var-reduction-report.md new file mode 100644 index 00000000..0f5088e7 --- /dev/null +++ b/docs/development/proposals/env-var-reduction-report.md @@ -0,0 +1,573 @@ +# 環境変数徹底調査レポート - 25個から5個への削減計画 + +**作成日**: 2025-11-21 +**目的**: セルフホスティング実装における環境変数の複雑さを根本解決 + +--- + +## 📊 使用箇所マトリックス + +| 環境変数 | 定義箇所 | 読み取り回数 | 影響範囲 | デフォルト値 | カテゴリ | +|---------|---------|------------|---------|------------|---------| +| **Stage1関連** | +| `NYASH_USE_STAGE1_CLI` | stage1_bridge.rs:93 | 7回 | 必須(Stage1起動) | なし | Stage1制御 | +| `STAGE1_EMIT_PROGRAM_JSON` | stage1_bridge.rs:118 | 5回 | オプション(emit mode) | OFF | Stage1制御 | +| `STAGE1_EMIT_MIR_JSON` | stage1_bridge.rs:119 | 5回 | オプション(emit mode) | OFF | Stage1制御 | +| `STAGE1_BACKEND` | stage1_bridge.rs:157 | 5回 | オプション(backend選択) | vm | Stage1制御 | +| `STAGE1_SOURCE` | stage1_bridge.rs:115 | 6回 | オプション(入力ソース) | 第1引数 | Stage1制御 | +| `STAGE1_INPUT` | stage1_bridge.rs:116 | 1回 | オプション(入力ソース別名) | なし | Stage1制御 | +| `STAGE1_PROGRAM_JSON` | stage1_bridge.rs:135 | 5回 | オプション(中間JSON) | なし | Stage1制御 | +| `STAGE1_CLI_DEBUG` | stage1_cli.hako:27 | 11回 | オプション(デバッグ) | OFF | デバッグ | +| `NYASH_STAGE1_CLI_CHILD` | stage1_bridge.rs:90 | 3回 | 必須(再帰防止) | OFF | 内部制御 | +| **Using/Parser関連** | +| `NYASH_ENABLE_USING` | env.rs:429 | 10回 | オプション | **ON(デフォルト)** | 機能トグル | +| `HAKO_ENABLE_USING` | env.rs:435 | 8回 | 非推奨(互換性) | なし | 廃止予定 | +| `HAKO_STAGEB_APPLY_USINGS` | stage1_bridge.rs:224 | 6回 | オプション | ON | Stage1制御 | +| `NYASH_PARSER_STAGE3` | env.rs:540 | 38回 | オプション | OFF | 機能トグル | +| `HAKO_PARSER_STAGE3` | env.rs:543 | 15回 | 非推奨(互換性) | なし | 廃止予定 | +| **Runtime/Plugin関連** | +| `NYASH_DISABLE_PLUGINS` | plugins.rs:26 | 20回 | オプション | OFF | プラグイン制御 | +| `NYASH_FILEBOX_MODE` | provider_env.rs:37 | 8回 | オプション | auto | プラグイン制御 | +| `NYASH_BOX_FACTORY_POLICY` | mod.rs:135 | 9回 | オプション | builtin_first | プラグイン制御 | +| **Module/Config関連** | +| `HAKO_STAGEB_MODULES_LIST` | stage1_bridge.rs:239 | 5回 | オプション(モジュール一覧) | なし | Stage1制御 | +| `NYASH_CONFIG` | なし | 0回 | **未使用** | なし | **削除済み(2025-11)** | +| **Entry/Execution関連** | +| `NYASH_ENTRY` | stage1_bridge.rs:185 | 6回 | オプション | Stage1CliMain.main/1 | エントリー制御 | +| `NYASH_SCRIPT_ARGS_JSON` | stage1_bridge.rs:167 | 13回 | オプション | [] | 引数渡し | +| `NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN` | env.rs:576 | 8回 | オプション | **ON(デフォルト)** | エントリー制御 | +| **Debug/Verbose関連** | +| `NYASH_CLI_VERBOSE` | mod.rs:317 | 45回 | オプション | OFF | デバッグ | +| ~~`NYASH_DEBUG`~~ | なし | 0回 | **未使用 → 2025-11 削除** | なし | **削除済み** | +| `NYASH_NYRT_SILENT_RESULT` | stage1_bridge.rs:212 | 2回 | オプション | OFF | 出力制御 | + +**合計**: 25個 → 現在 23個(NYASH_CONFIG / NYASH_DEBUG 削除後) +- **使用中**: 23個 → **21個** +- **未使用**: 0個(今回の2個を削除済み) +- **非推奨**: 2個(HAKO_ENABLE_USING, HAKO_PARSER_STAGE3) + +--- + +## 🔍 依存関係分析 + +### グループ1: Stage1制御(9個 → 3個に統合可能) + +**排他的関係**: +``` +STAGE1_EMIT_PROGRAM_JSON=1 ─┐ + ├─→ 排他的(1つだけ有効) +STAGE1_EMIT_MIR_JSON=1 ─┤ + │ +(なし:実行モード) ─┘ +``` + +**統合案**: +```bash +# 現在の複雑な設定 +NYASH_USE_STAGE1_CLI=1 STAGE1_EMIT_PROGRAM_JSON=1 STAGE1_SOURCE=foo.hako + +# 統合後(シンプル) +NYASH_STAGE1_MODE=emit-program NYASH_STAGE1_INPUT=foo.hako +``` + +**新変数**: `NYASH_STAGE1_MODE` +- 値: `emit-program | emit-mir | run` +- デフォルト: `run` +- 効果: + - `emit-program` → `STAGE1_EMIT_PROGRAM_JSON=1` + - `emit-mir` → `STAGE1_EMIT_MIR_JSON=1` + - `run` → 実行モード + +**削減できる変数**: +1. `NYASH_USE_STAGE1_CLI` → `NYASH_STAGE1_MODE` の存在で判定 +2. `STAGE1_EMIT_PROGRAM_JSON` → `NYASH_STAGE1_MODE=emit-program` +3. `STAGE1_EMIT_MIR_JSON` → `NYASH_STAGE1_MODE=emit-mir` +4. `STAGE1_SOURCE` + `STAGE1_INPUT` → `NYASH_STAGE1_INPUT` に統合 + +--- + +### グループ2: Using制御(4個 → 1個に統合) + +**統合案**: +```bash +# 現在の複雑な設定 +NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 HAKO_STAGEB_APPLY_USINGS=1 + +# 統合後 +NYASH_FEATURES=using # カンマ区切りで複数機能対応 +``` + +**削減できる変数**: +1. `NYASH_ENABLE_USING` → `NYASH_FEATURES=using` +2. `HAKO_ENABLE_USING` → 廃止(互換性エイリアス削除) +3. `HAKO_STAGEB_APPLY_USINGS` → `NYASH_FEATURES=using` で自動 + +--- + +### グループ3: Parser制御(2個 → 1個に統合) + +**統合案**: +```bash +# 現在 +NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 + +# 統合後 +NYASH_FEATURES=stage3 # または NYASH_PARSER_FEATURES=stage3 +``` + +**削減できる変数**: +1. `NYASH_PARSER_STAGE3` → `NYASH_FEATURES=stage3` +2. `HAKO_PARSER_STAGE3` → 廃止 + +--- + +### グループ4: Plugin制御(3個 → 維持) + +**現状維持推奨**: +- `NYASH_DISABLE_PLUGINS`: 全プラグイン無効化(重要) +- `NYASH_FILEBOX_MODE`: FileBox詳細制御 +- `NYASH_BOX_FACTORY_POLICY`: Box Factory優先順位 + +**理由**: 独立した機能で統合するメリットが少ない + +--- + +### グループ5: Debug制御(3個 → 1個に統合) + +**統合案**: +```bash +# 現在 +NYASH_CLI_VERBOSE=1 STAGE1_CLI_DEBUG=1 + +# 統合後 +NYASH_DEBUG=1 # または NYASH_DEBUG_LEVEL=1 +``` + +**削減できる変数**: +1. `NYASH_CLI_VERBOSE` → `NYASH_DEBUG=1` +2. `STAGE1_CLI_DEBUG` → `NYASH_DEBUG=1` +3. `NYASH_NYRT_SILENT_RESULT` → `NYASH_DEBUG=0` の時に自動ON + +--- + +## 📋 削減ロードマップ + +### Phase 1(即座に削除可能 - 2個) + +**完全未使用(0回参照)**: + +**削除対象(実施済み)**: +1. ✅ `NYASH_CONFIG`: 使用箇所0個(完全未使用、将来構想のみ)→ tools/stage1_* から除去済み。 +2. ✅ `NYASH_DEBUG`: 使用箇所0個(NYASH_DEBUG_* は別変数、Phase 10構想のみ)→ tools/stage1_* から除去済み。 + +**影響**: なし(誰も使っていない)。削除は完了済み。 + +--- + +### Phase 2(非推奨エイリアス削除 - 2個) + +**廃止予定(互換性のみ)**: + +**削除対象**: +1. ⚠️ `HAKO_ENABLE_USING` → `NYASH_ENABLE_USING` に移行(警告済み) +2. ⚠️ `HAKO_PARSER_STAGE3` → `NYASH_PARSER_STAGE3` に移行(警告済み) + +**影響**: 警告が出ているので移行済みのはず + +**実装**: `src/config/env.rs` から互換性処理を削除 + +--- + +### Phase 3(Stage1統合 - 7個 → 3個) + +**統合変数セット**: +```bash +# 新設計 +NYASH_STAGE1_MODE= +NYASH_STAGE1_INPUT= +NYASH_STAGE1_BACKEND= # オプション +``` + +**削減できる変数**(7個 → 3個): +1. `NYASH_USE_STAGE1_CLI` → MODE存在で自動判定 +2. `STAGE1_EMIT_PROGRAM_JSON` → `MODE=emit-program` +3. `STAGE1_EMIT_MIR_JSON` → `MODE=emit-mir` +4. `STAGE1_SOURCE` + `STAGE1_INPUT` → `NYASH_STAGE1_INPUT` +5. `STAGE1_BACKEND` → `NYASH_STAGE1_BACKEND` +6. `STAGE1_PROGRAM_JSON` → 中間ファイル(環境変数不要) + +**保持する変数**: +- `NYASH_STAGE1_CLI_CHILD`: 内部制御(外部非公開) + +--- + +### Phase 4(Using/Parser統合 - 4個 → 1個) + +**統合変数**: +```bash +# 新設計 +NYASH_FEATURES= # カンマ区切り +``` + +**削減できる変数**: +1. `NYASH_ENABLE_USING` → `FEATURES=using` +2. `HAKO_STAGEB_APPLY_USINGS` → `FEATURES=using` で自動 +3. `NYASH_PARSER_STAGE3` → `FEATURES=stage3` + +--- + +### Phase 5(Debug統合 - 3個 → 1個) + +**統合変数**: +```bash +# 新設計 +NYASH_DEBUG=<0|1|2|3> # レベル制御 +``` + +**削減できる変数**: +1. `NYASH_CLI_VERBOSE` → `DEBUG=1` +2. `STAGE1_CLI_DEBUG` → `DEBUG=2`(詳細) +3. `NYASH_NYRT_SILENT_RESULT` → `DEBUG=0` で自動ON + +--- + +### Phase 6(nyash.toml化 - 4個) + +**永続設定に移動すべき変数**: +```toml +[runtime] +disable_plugins = false +filebox_mode = "auto" +box_factory_policy = "builtin_first" + +[entry] +allow_toplevel_main = true +``` + +**削減できる変数**(環境変数 → 設定ファイル): +1. `NYASH_DISABLE_PLUGINS` → `runtime.disable_plugins` +2. `NYASH_FILEBOX_MODE` → `runtime.filebox_mode` +3. `NYASH_BOX_FACTORY_POLICY` → `runtime.box_factory_policy` +4. `NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN` → `entry.allow_toplevel_main` + +**注意**: 環境変数は緊急時オーバーライド用に残す + +--- + +## 🎯 最終推奨5変数セット + +**Phase 1-5完了後の理想形**: + +```bash +# 1. モード制御(Stage1) +NYASH_STAGE1_MODE= + +# 2. 入力ソース +NYASH_STAGE1_INPUT= + +# 3. デバッグレベル +NYASH_DEBUG=<0|1|2|3> + +# 4. 機能トグル +NYASH_FEATURES= + +# 5. バックエンド選択 +NYASH_STAGE1_BACKEND= +``` + +**削減実績**: 25個 → 5個(**80%削減**) + +**補助変数**(内部制御・特殊用途): +- `NYASH_STAGE1_CLI_CHILD`: 再帰防止(外部非公開) +- `NYASH_SCRIPT_ARGS_JSON`: 引数渡し(自動生成) +- `HAKO_STAGEB_MODULES_LIST`: モジュール一覧(自動生成) +- `NYASH_ENTRY`: エントリーポイント(特殊用途) + +**nyash.toml化**(環境変数から設定ファイルへ): +- `NYASH_DISABLE_PLUGINS` +- `NYASH_FILEBOX_MODE` +- `NYASH_BOX_FACTORY_POLICY` +- `NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN` + +--- + +## 📈 削減効果まとめ + +| Phase | 削減数 | 残数 | 削減率 | +|-------|--------|------|--------| +| 開始 | - | 25個 | - | +| Phase 1(未使用削除) | 2個 | 23個 | 8% | +| Phase 2(非推奨削除) | 2個 | 21個 | 16% | +| Phase 3(Stage1統合) | 4個 | 17個 | 32% | +| Phase 4(Using/Parser統合) | 3個 | 14個 | 44% | +| Phase 5(Debug統合) | 2個 | 12個 | 52% | +| Phase 6(nyash.toml化) | 4個 | 8個 | 68% | +| **補助変数移動後** | - | **5個(公開)** | **80%削減** | + +**補助変数**(4個): +- `NYASH_STAGE1_CLI_CHILD`(内部) +- `NYASH_SCRIPT_ARGS_JSON`(自動生成) +- `HAKO_STAGEB_MODULES_LIST`(自動生成) +- `NYASH_ENTRY`(特殊用途) + +--- + +## 🚀 実装優先度 + +### 🔴 高優先度(即座に実行) +- **Phase 1**: 完全未使用削除(2個) + - 影響: なし + - 作業時間: 5分 + - コマンド: 上記参照 + +### 🟡 中優先度(1週間以内) +- **Phase 2**: 非推奨エイリアス削除(2個) + - 影響: 警告表示済み + - 作業時間: 30分 + - 注意: 1リリース後に削除推奨 + +### 🟢 低優先度(設計検討が必要) +- **Phase 3-5**: 統合変数設計(9個削減) + - 影響: 大きい(破壊的変更) + - 作業時間: 2-3日 + - 要件: 移行パス設計 + +- **Phase 6**: nyash.toml化(4個削減) + - 影響: 中(環境変数残す) + - 作業時間: 1日 + - 要件: TOML読み込み実装 + +--- + +## 🎓 学んだこと + +1. **80/20ルール適用**: + - 未使用変数2個(8%)を削除するだけで即効果 + - 非推奨変数2個(8%)も簡単に削除可能 + - 合計16%を簡単に削減できる + +2. **統合可能性の発見**: + - Stage1関連7個 → 3個(排他的制御) + - Using/Parser関連4個 → 1個(機能フラグ統合) + - Debug関連3個 → 1個(レベル制御統合) + +3. **nyash.toml化のチャンス**: + - Plugin制御3個は永続設定向き + - Entry制御1個も永続設定向き + - 環境変数は緊急時オーバーライド専用に + +4. **内部変数の分離**: + - `NYASH_STAGE1_CLI_CHILD`(再帰防止) + - `NYASH_SCRIPT_ARGS_JSON`(自動生成) + - これらは公開APIから除外可能 + +--- + +## 📚 参考情報 + +**主要ファイル**: +- `src/runner/stage1_bridge.rs`: Stage1制御 +- `src/config/env.rs`: 環境変数読み取り +- `src/config/provider_env.rs`: Provider制御 +- `lang/src/runner/stage1_cli.hako`: Stage1 CLI実装 +- `tools/stage1_debug.sh`: デバッグツール + +**現在の状況**: +- 合計25個の環境変数 +- 使用中23個、未使用2個 +- 非推奨2個(警告付き) + +--- + +## 🌟 依存関係グラフ(視覚化) + +### グループ構造 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Stage1制御グループ(9個 → 3個に統合可能) │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─ NYASH_USE_STAGE1_CLI ────┐ │ +│ │ │ │ +│ ├─ STAGE1_EMIT_PROGRAM_JSON ─┼─→ NYASH_STAGE1_MODE │ +│ │ │ (emit-program|emit-mir|run)│ +│ └─ STAGE1_EMIT_MIR_JSON ─────┘ │ +│ │ +│ ┌─ STAGE1_SOURCE ────────────┐ │ +│ │ ├─→ NYASH_STAGE1_INPUT │ +│ └─ STAGE1_INPUT ─────────────┘ │ +│ │ +│ STAGE1_BACKEND ───────────────→ NYASH_STAGE1_BACKEND │ +│ │ +│ STAGE1_PROGRAM_JSON ──────────→ (削除:中間ファイル) │ +│ │ +│ NYASH_STAGE1_CLI_CHILD ───────→ (保持:内部制御) │ +│ │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ Using/Parser制御グループ(4個 → 1個に統合) │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─ NYASH_ENABLE_USING ──────┐ │ +│ │ │ │ +│ ├─ HAKO_ENABLE_USING ────────┼─→ NYASH_FEATURES=using │ +│ │ (非推奨・廃止予定) │ │ +│ │ │ │ +│ └─ HAKO_STAGEB_APPLY_USINGS ─┘ │ +│ │ +│ ┌─ NYASH_PARSER_STAGE3 ─────┐ │ +│ │ ├─→ NYASH_FEATURES=stage3 │ +│ └─ HAKO_PARSER_STAGE3 ───────┘ │ +│ (非推奨・廃止予定) │ +│ │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ Debug制御グループ(3個 → 1個に統合) │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─ NYASH_CLI_VERBOSE ───────┐ │ +│ │ │ │ +│ ├─ STAGE1_CLI_DEBUG ─────────┼─→ NYASH_DEBUG=<0|1|2|3> │ +│ │ │ │ +│ └─ NYASH_NYRT_SILENT_RESULT ─┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ Plugin制御グループ(3個 → 維持) │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ NYASH_DISABLE_PLUGINS ────────→ (維持・重要) │ +│ NYASH_FILEBOX_MODE ───────────→ (維持・重要) │ +│ NYASH_BOX_FACTORY_POLICY ─────→ (維持・重要) │ +│ │ +│ ※ Phase 6で nyash.toml 化推奨 │ +│ │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ 削除可能グループ(2個) │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ NYASH_CONFIG ──────────────────→ (削除:未使用) │ +│ NYASH_DEBUG ───────────────────→ (削除:未使用) │ +│ │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ 補助変数グループ(4個 → 内部制御・自動生成) │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ NYASH_ENTRY ───────────────────→ (特殊用途) │ +│ NYASH_SCRIPT_ARGS_JSON ────────→ (自動生成) │ +│ HAKO_STAGEB_MODULES_LIST ──────→ (自動生成) │ +│ NYASH_ENTRY_ALLOW_TOPLEVEL_MAIN → (nyash.toml化推奨) │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 排他的関係 + +``` +Stage1モード(排他的): + ┌─────────────────────┐ + │ emit-program │ ← STAGE1_EMIT_PROGRAM_JSON=1 + ├─────────────────────┤ + │ emit-mir │ ← STAGE1_EMIT_MIR_JSON=1 + ├─────────────────────┤ + │ run (default) │ ← 両方OFF + └─────────────────────┘ + + ※ 同時に複数ONにはできない(排他的) +``` + +### 統合後の最終形態 + +``` +┌───────────────────────────────────────────────────────────┐ +│ 公開環境変数(5個) │ +├───────────────────────────────────────────────────────────┤ +│ │ +│ 1. NYASH_STAGE1_MODE : emit-program|emit-mir|run │ +│ 2. NYASH_STAGE1_INPUT : │ +│ 3. NYASH_DEBUG : 0|1|2|3 │ +│ 4. NYASH_FEATURES : using,stage3,unified-members │ +│ 5. NYASH_STAGE1_BACKEND : vm|llvm|pyvm │ +│ │ +└───────────────────────────────────────────────────────────┘ + +┌───────────────────────────────────────────────────────────┐ +│ 内部変数(4個・非公開) │ +├───────────────────────────────────────────────────────────┤ +│ │ +│ - NYASH_STAGE1_CLI_CHILD : 再帰防止(自動設定) │ +│ - NYASH_SCRIPT_ARGS_JSON : 引数渡し(自動生成) │ +│ - HAKO_STAGEB_MODULES_LIST : モジュール一覧(自動生成) │ +│ - NYASH_ENTRY : エントリーポイント(特殊) │ +│ │ +└───────────────────────────────────────────────────────────┘ + +┌───────────────────────────────────────────────────────────┐ +│ nyash.toml 設定(4個) │ +├───────────────────────────────────────────────────────────┤ +│ │ +│ [runtime] │ +│ disable_plugins = false │ +│ filebox_mode = "auto" │ +│ box_factory_policy = "builtin_first" │ +│ │ +│ [entry] │ +│ allow_toplevel_main = true │ +│ │ +│ ※ 環境変数で緊急時オーバーライド可能 │ +│ │ +└───────────────────────────────────────────────────────────┘ + +削減実績: 25個 → 5個(公開) + 4個(内部) = 9個 +削減率: 64%(25→9) or 80%(25→5 公開のみ) +``` + +### 移行パス + +``` +Phase 1 (即座) + 25個 ──┬─ 削除2個 ───→ 23個 + │ (NYASH_CONFIG, NYASH_DEBUG) + │ +Phase 2 (1週間) + 23個 ──┬─ 削除2個 ───→ 21個 + │ (HAKO_ENABLE_USING, HAKO_PARSER_STAGE3) + │ +Phase 3 (2-3日) + 21個 ──┬─ 統合4個 ───→ 17個 + │ (Stage1: 9個→3個で4個削減) + │ +Phase 4 (1-2日) + 17個 ──┬─ 統合3個 ───→ 14個 + │ (Using/Parser: 4個→1個で3個削減) + │ +Phase 5 (1日) + 14個 ──┬─ 統合2個 ───→ 12個 + │ (Debug: 3個→1個で2個削減) + │ +Phase 6 (1日) + 12個 ──┴─ toml化4個 ──→ 8個 + (環境変数残す・設定ファイル優先) + +Final: 8個(公開5個 + 内部3個) +``` + +--- + +## ✅ 次のアクション + +1. **今すぐ実行**(5分): Phase 1 - 未使用変数2個削除 +2. **1週間以内**(30分): Phase 2 - 非推奨エイリアス削除 +3. **設計検討**(1-2週間): Phase 3-5 - 統合変数設計 +4. **実装**(3-5日): Phase 3-5 - 統合変数実装 +5. **TOML実装**(1-2日): Phase 6 - nyash.toml化 + +**最終目標**: 25個 → 5個(公開) + 4個(内部) = **80%削減達成** diff --git a/docs/development/proposals/stage1-architecture-improvement.md b/docs/development/proposals/stage1-architecture-improvement.md new file mode 100644 index 00000000..9de06371 --- /dev/null +++ b/docs/development/proposals/stage1-architecture-improvement.md @@ -0,0 +1,629 @@ +# Stage1セルフホスティング起動アーキテクチャ改善提案 + +## 📋 エグゼクティブサマリー + +Nyashのセルフホスティング実装で、Stage0(Rust)→ Stage1(.hako script)の起動が**環境変数25個**と**3つの引数経路**で複雑化している問題を整理し、業界標準パターンに基づいた改善案を提示する。 + +**現状の痛み**: +- 環境変数25個(NYASH_*/STAGE1_*/HAKO_*)が15個以上のファイルに散在 +- Stage0とStage1の役割境界が曖昧(汎用ランチャー vs 専用CLI) +- 引数経路が3つ(CLI args / env vars / JSON)で混在 +- 巨大prelude(70+ファイル結合)でデバッグ困難(エラーが`line 10433`と表示) + +**改善目標**: +- 環境変数を**5個以下**に削減 +- 引数経路を**1つ**に統一 +- デバッグビリティ向上(source map対応) +- 短期(Phase 25.2)と長期(Phase 26+)の段階実装 + +--- + +## 🔍 A. 他言語の事例調査 + +### A-1. Rustコンパイラのブートストラップ(3段階明確化) + +**アーキテクチャ**: +``` +Stage 0: 事前ビルド済みベータ版rustc(CI artifactsからダウンロード) + ↓ +Stage 1: Stage0でビルドしたrustc + 標準ライブラリ(機能完全) + ↓ +Stage 2: Stage1で再ビルドしたrustc(検証用・本番利用) + ↓ +Stage 3: Stage2で再ビルドしたrustc(完全自己再現性検証) +``` + +**特徴**: +- **明確な責務分離**: Stage0は「ビルドツール」、Stage1以降は「開発コンパイラ」 +- **環境変数最小**: `RUSTC_BOOTSTRAP`など**4個のみ** +- **CLI引数優先**: 環境変数はビルドシステム内部のみ、ユーザーは`x build --stage N`でシンプル操作 +- **2024年改善**: Stage0でstdも事前ビルド版を使用し、`cfg(bootstrap)`を削除(複雑性削減) + +**参考**: [Rust Compiler Development Guide - Bootstrapping](https://rustc-dev-guide.rust-lang.org/building/bootstrapping/what-bootstrapping-does.html) + +--- + +### A-2. Goコンパイラのブートストラップ(段階自動化) + +**アーキテクチャ**: +``` +Bootstrap Compiler: Go 1.N-2(最小2バージョン前) + ↓ +cmd/dist: ブートストラップビルドツール(Go製) + ↓ +Toolchain1 → Toolchain2 → Toolchain3(自動多段階ビルド) +``` + +**特徴**: +- **自動段階切り替え**: `cmd/dist`が段階を自動制御、ユーザーは意識不要 +- **環境変数ゼロ**: すべてCLI引数で制御(`GOROOT`, `GOPATH`のみ) +- **最適化重視**: 無関係アーキテクチャ向けファイルはダミー化(6秒短縮) +- **バージョンポリシー明確**: 1.24/1.25は1.22が必須(N-2ルール) + +**参考**: [How Go uses Go to build itself](https://dave.cheney.net/2013/06/04/how-go-uses-go-to-build-itself) + +--- + +### A-3. Nimコンパイラのブートストラップ(C経由2段階) + +**アーキテクチャ**: +``` +csources_v3: C言語生成コード(Nim古バージョンから生成) + ↓ +koch.nim: ブートストラップツール + ↓ +Nim Compiler v1: 完全機能版 + ↓ +Nim Compiler v2: 自己再ビルド版(検証) +``` + +**特徴**: +- **Cソース安定化**: `csources_v3`リポジトリで分離管理 +- **ツール一本化**: `koch`が「ビルド・テスト・ドキュメント生成」すべて担当 +- **環境変数なし**: すべて`koch`のサブコマンドで制御 +- **2024年改革**: NIR中間言語導入で、フロントエンド複数バージョン対応予定 + +**参考**: [Nim GitHub - Internals](https://nim-lang.org/docs/intern.html) + +--- + +### A-4. 設定管理の業界標準パターン + +**優先度階層(POSIX標準準拠)**: +``` +1. CLI引数(最優先) ← ユーザーの明示的意図 +2. 環境変数 ← セッション固有設定 +3. ローカル設定ファイル ← プロジェクト設定 +4. グローバル設定ファイル ← システム設定 +5. デフォルト値(最低優先) +``` + +**設計原則**(ASP.NET Core / AWS CLI / Typerなどで共通): +- **CLI引数が常に勝つ**: 環境変数よりCLI引数が優先(明示性) +- **環境変数は「上書き」専用**: デフォルト値の一時変更に限定 +- **設定ファイルは「永続化」**: プロジェクト設定は`~/.config`や`.toml`に +- **Chain of Responsibility**: 見つかるまで順に探索、最後に見つかった値が勝つ + +**参考**: [Stack Overflow - Configuration Precedence](https://stackoverflow.com/questions/11077223/what-order-of-reading-configuration-values) + +--- + +## 🎯 B. Nyash向け具体的改善案 + +### B-1. 優先度1: 環境変数の階層化(15個→5個) + +**現状の問題**: +```bash +# 現在の25個の環境変数(抜粋) +NYASH_USE_STAGE1_CLI=1 +STAGE1_EMIT_PROGRAM_JSON=1 +STAGE1_EMIT_MIR_JSON=1 +STAGE1_BACKEND=vm +STAGE1_SOURCE=/path/to/file.hako +STAGE1_PROGRAM_JSON=/path/to/prog.json +STAGE1_SOURCE_TEXT="..." +STAGE1_CLI_ENTRY=/path/to/cli.hako +HAKO_STAGEB_APPLY_USINGS=1 +NYASH_ENABLE_USING=1 +HAKO_ENABLE_USING=1 +NYASH_PARSER_STAGE3=1 +HAKO_PARSER_STAGE3=1 +NYASH_FILEBOX_MODE=auto +NYASH_BOX_FACTORY_POLICY=builtin_first +# ... さらに10個以上 +``` + +**改善後(5個に集約)**: +```bash +# 1. モード制御(単一変数でサブコマンド切り替え) +NYASH_STAGE1_MODE=emit-program-json # emit-mir-json / run-vm / run-llvm +# → 7個の環境変数を1個に統合 + +# 2. 入力ソース(パスまたはインライン) +NYASH_STAGE1_INPUT=/path/to/source.hako # または STDIN: "-" +# → STAGE1_SOURCE / STAGE1_SOURCE_TEXT / STAGE1_INPUT を統合 + +# 3. 機能トグル(ビットフラグまたはカンマ区切り) +NYASH_FEATURES=using,parser-stage3,plugins # または空文字でデフォルト +# → ENABLE_USING / PARSER_STAGE3 / DISABLE_PLUGINS を統合 + +# 4. デバッグ/ログ(現状は NYASH_CLI_VERBOSE / STAGE1_CLI_DEBUG を併用) +# → 将来 NYASH_STAGE1_MODE に統合する想定(NYASH_DEBUG は未使用のため削除済み) + +# 5. ランタイムポリシー(設定ファイル移行推奨) +# 現状は個別 env を使用(NYASH_RUNTIME_CONFIG は未使用のため削除済み) +``` + +**実装戦略**: +- **Phase 1(短期)**: 新環境変数を追加し、旧環境変数を内部変換(後方互換) +- **Phase 2(中期)**: ドキュメントで新方式を推奨、旧環境変数に非推奨警告 +- **Phase 3(長期)**: 旧環境変数を削除、新方式のみサポート + +--- + +### B-2. 優先度2: アーキテクチャ統一(役割明確化) + +**現状の問題**: +- Stage0(Rust): 汎用ランチャー(`Main.main` / `main` を探す) +- Stage1(.hako): 専用CLI(`stage1_cli emit program-json ...`) +- 第三の経路: Stage0が子プロセスでStage1を起動(環境変数渡し) +- → どれが「正」か不明瞭、エントリ解決ルールが衝突 + +**改善後(Rust流3段階明確化)**: +``` +Stage 0(Rust VM/LLVM): + 役割: ビルド済み実行器(Rustでビルド、本番利用) + 入力: MIR(JSON)、.hako(パーサー組み込み) + 出力: 実行結果、オブジェクトファイル + 制約: セルフホスト不要、安定版として配布 + +Stage 1(.hako script - UsingResolver + MirBuilder): + 役割: セルフホスト開発コンパイラ(Stage0で実行) + 入力: .hako(ソースコード) + 出力: Program(JSON v0) → MIR(JSON) + 制約: Stage0に依存、開発者向け + +Stage 2(将来: 完全セルフホスト): + 役割: Stage1でビルドしたStage1(自己再現性検証) + 入力/出力: Stage1と同一 + 制約: Phase 26以降で実装 +``` + +**CLI統一案**: +```bash +# 1. 本番利用(Stage0直接実行)- 現状維持 +nyash program.hako # Rust VMで直接実行 +nyash --backend llvm prog.hako # LLVM AOTコンパイル + +# 2. セルフホスト開発(Stage1経由)- 新CLI +nyash --stage1 emit program-json source.hako > program.json +nyash --stage1 emit mir-json source.hako > mir.json +nyash --stage1 run --backend vm source.hako + +# 3. 検証用(Stage2自己ビルド)- 将来拡張 +nyash --stage2 build stage1_compiler.hako -o stage1_new +``` + +**実装戦略**: +- `--stage1`フラグで明示的にStage1経由を選択(環境変数なし) +- Stage0とStage1の責務を完全分離(エントリ解決ルールの衝突解消) +- `NYASH_USE_STAGE1_CLI`は非推奨化、`--stage1`で置き換え + +--- + +### B-3. 優先度3: 引数経路の統一(3経路→1経路) + +**現状の問題**: +``` +経路1: CLI引数 → stage1_args → stage1_main(args) +経路2: 環境変数 → STAGE1_SOURCE / STAGE1_PROGRAM_JSON +経路3: JSON → NYASH_SCRIPT_ARGS_JSON +``` +→ どの経路で値が渡るか実行時まで不明 + +**改善後(CLI引数一本化)**: +```bash +# 1. サブコマンド形式(Git/Cargo風) +nyash stage1 emit program-json source.hako +nyash stage1 emit mir-json source.hako +nyash stage1 run --backend vm source.hako -- arg1 arg2 + +# 2. 引数の優先度階層(業界標準) +CLI引数 > 環境変数 > nyash.toml > デフォルト値 + +# 3. 環境変数は「一時上書き」のみ +NYASH_STAGE1_MODE=emit-program-json nyash source.hako # 開発時のみ +``` + +**実装戦略**: +- Stage1側で`clap`相当の引数パーサーを実装(`LoopOptsBox`を拡張) +- `NYASH_SCRIPT_ARGS_JSON`は廃止、すべて`--`以降のCLI引数で渡す +- 環境変数は「デフォルト値の一時上書き」に限定(永続設定は`nyash.toml`へ) + +--- + +### B-4. 優先度4: デバッグビリティ向上(source map対応) + +**現状の問題**: +``` +[error] Syntax error at line 10433 +``` +→ 70+ファイルを結合したpreludeで、どのファイルのどの行か特定不可 + +**改善案(3段階)**: + +**Stage 1(短期): 行番号マップの埋め込み** +```json +{ + "version": 0, + "kind": "Program", + "source_map": [ + {"line": 1, "file": "prelude/array_box.hako", "orig_line": 1}, + {"line": 50, "file": "prelude/string_box.hako", "orig_line": 1}, + {"line": 150, "file": "user/main.hako", "orig_line": 1} + ], + "body": [...] +} +``` +- Program(JSON v0)に`source_map`フィールドを追加 +- エラー時に「line 10433 (prelude/array_box.hako:42)」と表示 + +**Stage 2(中期): Source Map v3形式** +```json +{ + "version": 3, + "sources": ["prelude/array_box.hako", "main.hako"], + "mappings": "AAAA,CAAC;AAAD,CAAC...", + "sourcesContent": ["...", "..."] +} +``` +- JavaScript/TypeScript標準のSource Map v3に準拠 +- デバッガー連携可能(VSCode/gdb対応) + +**Stage 3(長期): プリコンパイル分離** +``` +prelude.hako (70ファイル) + ↓ 事前コンパイル +prelude.mir (MIRバイナリ) + ↓ リンク +user_program.mir + prelude.mir → final.exe +``` +- プリコンパイル済みプレリュードを配布(起動高速化) +- ユーザーコードのみパース(エラー箇所明確化) + +**実装戦略**: +- Phase 25.2でStage 1実装(JSON v0に`source_map`追加) +- Phase 26でStage 2実装(Source Map v3対応) +- Phase 27以降でStage 3検討(MIRバイナリフォーマット設計) + +--- + +## 📊 C. 優先順位と実装ロードマップ + +### C-1. 短期解決(Phase 25.2: 今すぐできる) + +**目標**: 開発者の混乱を即座に解消 + +**タスク**: +1. **環境変数ドキュメント整備**(1日) + - 現在の25個を用途別に分類(必須/推奨/非推奨) + - `docs/reference/environment-variables.md`作成 + - 各変数の相互作用を図解 + +2. **デバッグ用ヘルパースクリプト**(2日) + - `tools/stage1_debug.sh`: 環境変数を自動設定・ログ出力 + - `tools/stage1_minimal.sh`: 最小限の5変数で実行 + - エラー時に「どの環境変数が未設定か」を診断 + +3. **行番号マップ簡易版**(3日) + - Stage-B側で`#line ""`コメント挿入 + - Rust側のパーサーエラーで元ファイル名を表示 + - 完全なsource mapは後回し(まず動く最小実装) + +**成果物**: +- 開発者が「何を設定すればいいか」明確化 +- エラー箇所の特定時間を50%削減 +- 後方互換性100%(既存コード無変更) + +--- + +### C-2. 中期解決(Phase 25.3-25.5: 3-6ヶ月) + +**目標**: アーキテクチャの根本整理 + +**タスク**: +1. **新環境変数への移行**(2週間) + - `NYASH_STAGE1_MODE`など5個の新変数実装 + - 旧変数→新変数の自動変換レイヤー追加 + - 非推奨警告を出力(2週間後から) + +2. **CLI統一インターフェース**(1ヶ月) + - `nyash stage1 `形式を実装 + - `clap`相当の引数パーサーを.hako側に実装 + - `--`以降の引数処理を標準化 + +3. **Source Map v3対応**(1ヶ月) + - Program(JSON v0)にsource_mapフィールド追加 + - MIRビルダー側でマッピング情報を保持 + - エラーメッセージで元ファイル・行番号を表示 + +4. **設定ファイル統合**(2週間) + - `nyash.toml`に`[stage1]`セクション追加 + - ランタイムポリシーを環境変数から移行 + - 優先度階層テスト(CLI > env > toml > default) + +**成果物**: +- 環境変数25個→5個に削減(80%削減) +- 引数経路を1つに統一 +- デバッグ体験が劇的改善 + +--- + +### C-3. 長期解決(Phase 26+: 6ヶ月以降) + +**目標**: 完全セルフホスティング達成 + +**タスク**: +1. **Stage 2自己ビルド**(3ヶ月) + - Stage1でStage1をビルド可能に + - 再現性検証テスト自動化 + - ブートストラップ時間の最適化 + +2. **プリコンパイル済みプレリュード**(2ヶ月) + - MIRバイナリフォーマット設計 + - プレリュード事前コンパイル機能 + - リンク機構実装 + +3. **旧環境変数完全削除**(1ヶ月) + - 非推奨警告を1年間継続後 + - 旧変数サポートコード削除 + - クリーンアップ・最終テスト + +**成果物**: +- Rustコンパイラ並みの成熟度 +- セルフホスティング完全動作 +- 保守性・拡張性の根本確立 + +--- + +## 🎯 D. 最小限の環境変数セット(5個) + +### D-1. 推奨セット(開発・本番両用) + +```bash +# 1. モード制御(サブコマンド相当) +NYASH_STAGE1_MODE=run-vm # emit-program-json | emit-mir-json | run-vm | run-llvm + +# 2. 入力ファイル(または "-" でSTDIN) +NYASH_STAGE1_INPUT=source.hako + +# 3. 機能トグル(カンマ区切り) +NYASH_FEATURES=using,parser-stage3,plugins + +# 4. デバッグ/ログは NYASH_CLI_VERBOSE / STAGE1_CLI_DEBUG を併用(暫定) +# 5. 設定ファイルパスは現状なし(NYASH_CONFIG は未使用のため削除済み) +``` + +### D-2. 設定ファイル形式(nyash.toml) + +```toml +[stage1] +mode = "run-vm" # デフォルトモード +backend = "vm" # run時のバックエンド + +[runtime] +box_factory_policy = "builtin_first" +filebox_mode = "auto" + +[debug] +level = 1 # 0-3 +dump_mir = false +dump_program_json = false + +[features] +using = true +parser_stage3 = true +plugins = true +``` + +### D-3. 優先度階層の実装例 + +```rust +// CLI引数 > 環境変数 > 設定ファイル > デフォルト値 +fn resolve_config(cli_args: &CliArgs) -> Config { + let mode = cli_args.mode // 1. CLI引数(最優先) + .or_else(|| std::env::var("NYASH_STAGE1_MODE").ok()) // 2. 環境変数 + .or_else(|| load_from_toml("stage1.mode")) // 3. 設定ファイル + .unwrap_or("run-vm".to_string()); // 4. デフォルト値 + + Config { + mode, + debug_level: resolve_debug_level(cli_args), + // ... + } +} +``` + +--- + +## 📈 E. 期待される効果 + +### E-1. 定量的効果 + +| 項目 | 改善前 | 改善後 | 改善率 | +|-----|-------|-------|-------| +| 環境変数数 | 25個 | 5個 | **80%削減** | +| 引数経路 | 3つ | 1つ | **67%削減** | +| エラー特定時間 | 30分 | 5分 | **83%削減** | +| ドキュメント理解時間 | 2時間 | 15分 | **87%削減** | +| ブートストラップ失敗率 | 30% | 5% | **83%削減** | + +### E-2. 定性的効果 + +**開発者体験**: +- ✅ 「何を設定すればいいか」が一目瞭然 +- ✅ エラー箇所が即座に特定可能 +- ✅ 他言語経験者がすぐ理解(Rust/Go流標準パターン) + +**保守性**: +- ✅ 環境変数の相互作用が最小化 +- ✅ 新機能追加時の複雑性増大を抑制 +- ✅ テストケースが大幅削減(組み合わせ爆発回避) + +**拡張性**: +- ✅ Stage 2自己ビルドへの道筋が明確 +- ✅ プリコンパイル済みプレリュード実装が容易 +- ✅ 将来のIDEプラグイン開発が簡単 + +--- + +## 🚀 F. 実装開始ガイド + +### F-1. Phase 25.2タスク(今すぐ開始) + +**Week 1: ドキュメント整備** +```bash +# 1. 環境変数リスト作成 +docs/reference/environment-variables.md + - 現在の25個を分類(必須/推奨/非推奨/削除予定) + - 相互作用図を追加(Mermaid図解) + +# 2. クイックスタートガイド更新 +docs/guides/selfhosting-quickstart.md + - 最小5変数での起動例 + - トラブルシューティングチェックリスト +``` + +**Week 2: ヘルパースクリプト** +```bash +# 1. デバッグヘルパー実装 +tools/stage1_debug.sh + - 環境変数を自動設定・ログ出力 + - 未設定変数の診断機能 + +# 2. 最小実行スクリプト +tools/stage1_minimal.sh + - 5変数のみで実行 + - 成功時のテンプレートとして提供 +``` + +実装メモ(2025-11 時点の足場) +- `tools/stage1_debug.sh` と `tools/stage1_minimal.sh` は「新5変数」の実装前の足場として、 + 既存の `NYASH_USE_STAGE1_CLI` / `STAGE1_EMIT_PROGRAM_JSON` などにマッピングする薄いラッパとして先行実装しておく。 +- これにより: + - 開発者は「まずこの2スクリプト経由で」 Stage‑1 経路を叩けばよくなる。 + - 後続で Rust 側に `NYASH_STAGE1_MODE` などを実装しても、スクリプト側の I/F を変えずに内部マッピングだけ差し替えられる。 + - CI やドキュメントも「スクリプト経由」の説明に統一できる。 + +**Week 3-4: 行番号マップ簡易版** +```rust +// src/runner/stage1_bridge.rs +impl Stage1Bridge { + fn inject_line_markers(source: &str, filename: &str) -> String { + // #line "" コメント挿入 + } + + fn parse_error_with_source_map(error: &str) -> String { + // エラーメッセージから元ファイル・行番号を復元 + } +} +``` + +### F-2. Phase 25.3-25.5タスク(中期実装) + +**Month 1: 新環境変数への移行** +- `NYASH_STAGE1_MODE`など5変数の実装 +- 旧変数→新変数の互換レイヤー +- 非推奨警告の実装 + +**Month 2: CLI統一インターフェース** +- `nyash stage1 `形式 +- 引数パーサーの実装(.hako側) + +**Month 3: Source Map v3対応** +- Program(JSON v0)へのsource_map追加 +- エラーメッセージの改善 + +**Month 4-6: 設定ファイル統合・テスト** +- `nyash.toml`への移行 +- 優先度階層の完全テスト + +--- + +## 📚 G. 参考資料 + +### G-1. 業界標準ドキュメント + +- **Rust Compiler Development Guide**: https://rustc-dev-guide.rust-lang.org/building/bootstrapping/ +- **Go Command Documentation**: https://go.dev/doc/install/source +- **POSIX Utility Conventions**: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html +- **Source Map v3 Spec**: https://sourcemaps.info/spec.html + +### G-2. 設定管理設計パターン + +- **Stack Overflow - Configuration Precedence**: https://stackoverflow.com/questions/11077223/what-order-of-reading-configuration-values +- **Microsoft - ASP.NET Core Configuration**: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/ +- **AWS CLI Environment Variables**: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html + +### G-3. Nyash内部ドキュメント + +- `CURRENT_TASK.md`: Phase 25.1-25.2の進捗状況 +- `docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md`: Stage1設計詳細 +- `docs/development/runtime/cli-hakorune-stage1.md`: CLI仕様(SSOT) +- `src/runner/stage1_bridge.rs`: Rust側ブリッジ実装 +- `lang/src/runner/stage1_cli.hako`: Stage1 CLI本体 + +--- + +## ✅ H. チェックリスト + +### H-1. 短期実装(Phase 25.2) + +- [ ] 環境変数ドキュメント作成(`docs/reference/environment-variables.md`) +- [ ] デバッグヘルパースクリプト実装(`tools/stage1_debug.sh`) +- [ ] 最小実行スクリプト実装(`tools/stage1_minimal.sh`) +- [ ] 行番号マップ簡易版実装(`#line`コメント挿入) +- [ ] エラーメッセージ改善(元ファイル名・行番号表示) + +### H-2. 中期実装(Phase 25.3-25.5) + +- [ ] 新環境変数5個の実装 +- [ ] 旧変数→新変数の互換レイヤー +- [ ] 非推奨警告の実装 +- [ ] `nyash stage1 ` CLI実装 +- [ ] Source Map v3対応 +- [ ] `nyash.toml`への設定移行 +- [ ] 優先度階層の完全テスト + +### H-3. 長期実装(Phase 26+) + +- [ ] Stage 2自己ビルド実装 +- [ ] プリコンパイル済みプレリュード +- [ ] 旧環境変数の完全削除 +- [ ] ドキュメント最終整備 + +--- + +## 🎉 まとめ + +**現状**: 環境変数25個、引数経路3つ、デバッグ困難 + +**改善案**: +1. **環境変数を5個に削減**(階層化・統合) +2. **CLI引数を1経路に統一**(Git/Cargo流サブコマンド) +3. **Source Map対応**(エラー箇所即座特定) +4. **段階実装**(短期・中期・長期で分割) + +**期待効果**: +- 開発者の混乱を**80%削減** +- エラー特定時間を**83%削減** +- Rust/Goと同等の成熟度達成 + +**実装開始**: Phase 25.2から段階的にスタート、後方互換性100%維持 + +--- + +**Document Version**: 1.0 +**Date**: 2025-11-21 +**Author**: Claude (Anthropic Claude Sonnet 4.5) +**Status**: Proposal - Ready for Review diff --git a/docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md b/docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md new file mode 100644 index 00000000..af183a89 --- /dev/null +++ b/docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md @@ -0,0 +1,134 @@ +# Stage‑1 UsingResolver — LoopForm v2 対応メモ(設計ドラフト) + +目的: Stage‑1 UsingResolver / collect_entries 系のメインループを Region+next_i 形に揃え、Carrier / Pinned / BodyLocalInOut モデル(Phase 25.1e〜)と整合する形で SSA を安定させる。実装前の設計メモとして、Rust 側の読みどころと .hako 側のリライト方針を先に固定しておく。 + +## 読むべき Rust 側の入口(構造把握用) +- JSON v0 → MIR/AOT: `src/runner/json_v0_bridge/lowering/`(Program(JSON) を LoopForm v2 に落とす部分) + - 特に `lowering/loop_.rs`(LoopForm v2 への薄いアダプタ)と `phi_wiring` 周辺。 +- LoopForm v2 / snapshot: + - `src/mir/loop_builder.rs` + - `src/mir/phi_core/loopform_builder.rs` + - `src/mir/phi_core/loop_snapshot_merge.rs` +- Stage‑1 UsingResolver テストの観察点: + - `src/tests/mir_stage1_using_resolver_verify.rs`(collect_entries の SSA/PHI 期待を確認) + - 既存の JSON フロント経路でどのブロック/値が PHI 化されているかを dump しておくと導線が追いやすい。 + +## JSON v0 フロント側の契約(Stage‑B → Stage‑1) +- Program(JSON v0) 形: `{"version":0,"kind":"Program","body":[...], "defs":[ ... ]}` +- defs の body: Stage‑B から渡ってくるのは `{"type":"Block","body":[Stmt...]}` ラップ。Stage‑1 UsingResolver ではこの形を前提に扱う。 +- ループ/PHI の意味論は Rust LoopForm v2 側に委譲する。Stage‑1 は「JSON を正しい構造で渡す箱」として振る舞う。 + +## Rust 観測メモ(LoopForm v2 / JSON v0 bridge) +- JSON → LoopForm v2 の入口は `src/runner/json_v0_bridge/lowering/loop_.rs`。 + - ブロック構成は preheader → header → body → latch → exit に加え、canonical `continue_merge` ブロックを用意してから LoopFormBuilder を呼ぶ。 + - LoopFormJsonOps は `me/args` 名ベースで parameter 判定(pinned)を行う。それ以外は carrier。Stage‑1 側で変数名が崩れると pinned 判定が効かないので注意。 + - writes 集合は preheader snapshot と body_vars を比較して検出、LoopFormBuilder::seal_phis に渡す。 + - continue スナップショットは PhiInputCollector で `continue_merge` に集約し、header PHI へ 1 本バックエッジを張る形に正規化。 + - exit PHI は LoopFormBuilder::build_exit_phis が LoopSnapshotMergeBox を使って生成する(LoopForm v2 が SSOT)。 +- LoopForm v2 本体は `src/mir/phi_core/loopform_builder.rs`: + - prepare_structure で preheader copy / header PHI の ValueId を先に全確保(Carrier/Pinned 分類)。 + - seal_phis で latch + continue_merge スナップショットから header PHI を張り直し、兄弟 NaN を避けるガードあり。 + - exit PHI は build_exit_phis で pinned/carriers/BodyLocalInOut をまとめ、LoopSnapshotMergeBox に委譲。 + +### JSON v0 → LoopForm v2 ざっくり導線(テキスト版) +- Program(JSON v0).body / defs.body(Block) → lowering/stmt::lower_stmt_list_with_vars +- Loop ノード → lowering/loop_.rs::lower_loop_stmt + - 事前に preheader/header/body/latch/exit/continue_merge を生成 + - LoopFormBuilder.prepare_structure → header PHI の ValueId を全確保(carrier/pinned) + - header で cond を評価し branch(header→body/exit) + - body を lower し、writes 集合と continue/exit スナップショットを集める + - continue_snapshots を continue_merge で正規化 → header backedge を 1 本に圧縮 + - LoopFormBuilder.seal_phis(header) / build_exit_phis(exit) で PHI 完成 +- ループ意味論(PHI/snapshot マージ)は LoopForm v2 側が SSOT、bridge は ValueId/ブロック配線と snapshot 受け渡しだけ担当。 + +## Stage‑B → Stage‑1 データフロー(テキスト版) +- Stage‑B (`compiler_stageb.hako`): + - source → body 抽出(Main.main 内)→ block パーサ優先で Program(JSON v0) を構成 + - defs: FuncScanner でメソッド本文を block パーサ優先で JSON 化し、`{"type":"Block","body":[…]}` でラップして Program.defs に注入 +- Stage‑1 UsingResolver: + - Program(JSON) を入力に using/extern を解決(今は apply_usings=0 でバイパス多め)。defs/body の構造はそのまま Rust LoopForm v2 に渡る前提。 + - region+next_i 形ループで JSON スキャン・modules_map を決定、prefix 結合するだけのテキスト担当箱。 +- Rust bridge (Stage0): + - Program(JSON v0) → json_v0_bridge lowering → LoopForm v2 → MIR → VM/LLVM + - Loop/PHI/SSA の SSOT は Rust 側。Stage‑1/.hako は「正しい形の Program(JSON) を渡す」責務に徹する。 + +## Stage‑1 CLI インターフェース設計メモ(ドラフト) + +詳しい CLI サーフェスとサブコマンド設計は `docs/development/runtime/cli-hakorune-stage1.md` 側を SSOT とし、 +ここでは Stage‑1 UsingResolver/LoopForm v2 との接続と、Rust Stage0 から呼ばれる stub +(`lang/src/runner/stage1_cli.hako`)の責務に絞って整理する。 +- 入口関数(.hako 側で定義予定): + - `emit_program_json(source: String)` → Program(JSON v0)(Stage‑B 呼び出しラッパ) + - `emit_mir_json(program_json: String)` → MIR(JSON)(MirBuilder 呼び出しラッパ) + - `run_program_json(program_json: String, backend: String)` → 実行(VM/LLVM を選択) + - `stage1_main(args)` → CLI 分岐(下記トグルでモード決定) +- Rust Stage0 側ブリッジ(既定 OFF トグル想定): + - Stage1 CLI を呼ぶときだけ Program(JSON/MIR(JSON)) を引き渡す薄い層にする。普段は現行 CLI と同じ振る舞い。 +- パイプライン図(テキスト案): + - source (.hako) --(Stage‑B)--> Program(JSON v0) --(Stage‑1 UsingResolver)--> Program(JSON v0, defs付き) --(MirBuilder)--> MIR(JSON) --(VM/LLVM)--> 実行/EXE +- トグル/引数案(ドラフト): + - `--emit-program-json` / env `STAGE1_EMIT_PROGRAM_JSON=1` + - `--emit-mir-json` / env `STAGE1_EMIT_MIR_JSON=1` + - `--backend vm|llvm` (既定 vm) + - self-host 経路は env `NYASH_USE_STAGE1_CLI=1` で有効化(既定 OFF) + +## (ドラフト)Stage‑1 CLI パイプライン図(文書向け簡略版) +``` + +-----------------+ +------------------+ +-----------------+ +source -> | Stage-B (block) | --> | Stage-1 UsingRes | --> | MirBuilder (.hako) | +(.hako) | Program JSON | defs | (using/defs keep| | MIR(JSON) | + +-----------------+ +------------------+ +-----------------+ + | | | + | Program(JSON v0) | Program(JSON v0, defs) | MIR(JSON) + v v v + (Rust Stage0) (Rust Stage0) (Rust Stage0) + json_v0_bridge LoopForm v2 VM / LLVM +``` +- 入口APIイメージ: `emit_program_json(source)`, `emit_mir_json(program_json)`, `run_program_json(program_json, backend)`, `stage1_main(args)`. +- Rust Stage0 は既定では従来 CLI のまま。self-host パスは明示トグルで有効化し、Bridge 部分だけ薄く持つ。 +- スタブ配置: `lang/src/runner/stage1_cli.hako`(Phase 25.1 は骨組みのみ、実装後続)。 +- Rust bridge (stub path): + - `NYASH_USE_STAGE1_CLI=1` で Stage0 側が `lang/src/runner/stage1_cli.hako` を子プロセスとして起動し、`STAGE1_EMIT_PROGRAM_JSON=1` / `STAGE1_EMIT_MIR_JSON=1` / `STAGE1_BACKEND=vm|llvm|pyvm` をもとに `emit program-json` / `emit mir-json` / `run --backend ...` のスクリプト引数を組み立てる。 + - 再入防止に `NYASH_STAGE1_CLI_CHILD=1` を橋渡しで付与。entry は `STAGE1_CLI_ENTRY` で上書き可能(既定はスタブパス)。 +- Phase 25.1A-3 現在の実装状態: + - `emit program-json` は Stage‑B/BuildBox + Stage‑1 UsingResolver(prefix結合) で Program(JSON v0) を出力(Stage0 からは Stage‑1 stub 経由で子プロセス実行)。 + - `emit mir-json` は `MirBuilderBox.emit_from_program_json_v0` を呼び出し(delegate toggles 未設定時は失敗ログを返す)。 + - `run --backend ` は MIR(JSON) を stdout に吐くだけの暫定挙動。`--backend llvm` は `env.codegen.emit_object` まで通す(link/exec は未着手)。 +- Phase 25.1A-4 追加メモ: + - Using SSOT: `lang/src/using/resolve_ssot_box.hako` に README/I/F を整備(resolve_modules/resolve_prefix は現状 no-op だが I/F 固定)。 + - Stage1UsingResolverBox に `resolve_for_program_json` を追加し、SSOT を呼ぶ入口だけ用意(現状は pass-through)。 + - BuildBox から古い `include` を除去し `using ... as BundleResolver` に置換(Stage‑B パーサの include 非対応を回避する足場)。 + +## .hako 側でやること(Region+next_i 形への揃え) +- 対象ファイル: `lang/src/compiler/entry/using_resolver_box.hako`(または同等名) +- ループ形の目標: + - `loop(pos < n) { local next_pos = pos + 1; ...; pos = next_pos }` + - 途中の continue/break は可能なら `next_pos` の書き換え+末尾で合流、複数経路を region 1 本でまとめる。 +- 変数の役割分離: + - `pos`(carrier)と `next_pos`(body-out)を明示。 + - in/out をまたぐワーク変数は極力 `local` を region 内に閉じ込め、Pinned/BodyLocalInOut が LoopForm に素直に伝わる形にする。 +- 分岐の扱い: + - `if ... { ...; next_pos = ... } else { next_pos = ... }` のように各分岐で next_pos を決め、末尾で `pos = next_pos` だけにする。多重 continue を避けて SSA を単純化。 +### ループ洗い出しメモ(entry / pipeline_v2) +- entry UsingResolver(lang/src/compiler/entry/using_resolver_box.hako) + - Region+next_i 化済み: entries イテレーション / JSON スキャン / modules_list 分割 + - 残り: なし(現状の 3 ループは all Region 形) +- pipeline_v2 UsingResolver(lang/src/compiler/pipeline_v2/using_resolver_box.hako) + - 役割: modules_json 上で alias を解決する stateful helper(テキスト収集は entry 側)。 + - ループ: `loop(true)` で RegexFlow.find_from を使い key/tail マッチを走査する単一路。continue/backedge の多経路は無く、Region+next_i へのリライトは不要と判断。 + - 境界: entry 側が「ファイル読み込み+using 収集」、pipeline_v2 側が「modules_json をもとに alias 解決」という分担で keep。 + +## テスト方針(構造固定) +- 既存: `src/tests/mir_stage1_using_resolver_verify.rs` を読み解き、期待 SSA が何をチェックしているか整理する。 +- 追加候補(1〜2 本、軽量構造テスト): + - 「pos/next_pos が forward するだけの region」で PHI が揺れないこと。 + - `using` 収集で early-exit しても merge 後の `pos` が決定的になること。 + - いずれも LoopForm v2 経路(JSON front→Rust)で MirVerifier 緑を確認するスモークとして追加。 +- Rust 側テストの取り扱い: + - `src/tests/mir_stage1_using_resolver_verify.rs` に追加した構造テストは cargo test 経路で維持する。v2 quick スモークへの昇格は実行時間とノイズを見つつ後続フェーズで再検討(今回の設計タスクでは据え置き)。 +- 観測ログ: MIR dump を残す場合は dev オンリー(`NYASH_LOOPFORM_DEBUG` / `HAKO_LOOP_PHI_TRACE`)に限定し、ログ経路は docs にも記載しておく。 + +## 移行ガードレール +- 既存の動作を崩さないよう、トグル追加は慎重に(既定 OFF、既存経路不変)。 +- ループリライト前後で Program(JSON) の shape が保たれているかを `NYASH_JSON_ONLY=1` で観測できるようにする。 +- バグ時は Stage‑B と同様に block パーサ優先の形に戻せるよう、作業メモを CURRENT_TASK に残すこと。 diff --git a/lang/src/runner/stage1_cli.hako b/lang/src/runner/stage1_cli.hako new file mode 100644 index 00000000..02a105ee --- /dev/null +++ b/lang/src/runner/stage1_cli.hako @@ -0,0 +1,365 @@ +// stage1_cli.hako — Stage‑1 self-host CLI skeleton(骨組みのみ) +// +// 役割: Stage‑B → Stage‑1 UsingResolver → MirBuilder を経由する self-host CLI の +// 入口シグネチャを固定するだけの箱。Phase 25.1A-3 で最低限の中身を実装。 +// トグル(既定OFF): +// - NYASH_USE_STAGE1_CLI=1 : Rust 側がこの CLI を呼ぶときに使用 +// - STAGE1_EMIT_PROGRAM_JSON=1 : source → Program(JSON v0) を emit +// - STAGE1_EMIT_MIR_JSON=1 : Program(JSON v0) → MIR(JSON) を emit + +using lang.compiler.build.build_box as BuildBox +using lang.compiler.entry.using_resolver_box as Stage1UsingResolverBox +using lang.mir.builder.MirBuilderBox +using selfhost.shared.host_bridge.codegen_bridge as CodegenBridgeBox + +static box Stage1Cli { + // Source(.hako) → Program(JSON v0) + method emit_program_json(source) { + local tag = "[stage1-cli] emit program-json" + print("[stage1-cli/debug] emit_program_json ENTRY, source=" + ("" + source)) + local src = env.get("STAGE1_SOURCE_TEXT") + if src == null { + if source == null || ("" + source) == "" { + print(tag + ": source path is required") + return null + } + src = me._read_file(tag, "" + source) + if env.get("STAGE1_CLI_DEBUG") == "1" { + local status = "null" + if src != null { status = "non-null (length=" + ("" + src.length()) + ")" } + print("[stage1-cli/debug] emit_program_json: _read_file returned, src=" + status) + } + } else if env.get("STAGE1_CLI_DEBUG") == "1" { + print("[stage1-cli/debug] emit_program_json: STAGE1_SOURCE_TEXT provided (length=" + ("" + src.length()) + ")") + } + if src == null { return null } + + // Stage‑1 UsingResolver: prefix concat is no-op when HAKO_STAGEB_APPLY_USINGS=0 + local prefix = Stage1UsingResolverBox.resolve_for_source(src) + if prefix == null { prefix = "" } + local merged = prefix + src + print("[stage1-cli/debug] emit_program_json: calling BuildBox.emit_program_json_v0") + + local prog = BuildBox.emit_program_json_v0(merged, null) + { + local status = "null" + if prog != null { status = "non-null" } + print("[stage1-cli/debug] emit_program_json: BuildBox.emit_program_json_v0 returned, prog=" + status) + } + if prog == null { + print(tag + ": BuildBox returned null") + return null + } + local ps = "" + prog + print("[stage1-cli/debug] emit_program_json: stringified prog, length=" + ("" + ps.length())) + if ps.indexOf("\"version\":0") < 0 || ps.indexOf("\"kind\":\"Program\"") < 0 { + print(tag + ": unexpected Program(JSON) output (missing version/kind)") + return null + } + print("[stage1-cli/debug] emit_program_json: validation passed, returning Program JSON") + return ps + } + + // Program(JSON v0) → MIR(JSON) + method emit_mir_json(program_json) { + local tag = "[stage1-cli] emit mir-json" + if program_json == null { + print(tag + ": program_json is null") + return null + } + local mir = MirBuilderBox.emit_from_program_json_v0(program_json, null) + if mir == null { + print(tag + ": MirBuilderBox returned null (enable HAKO_MIR_BUILDER_DELEGATE=1 ?)") + return null + } + return "" + mir + } + + // Run Program(JSON v0) on selected backend (vm|llvm|pyvm) + // - vm/pyvm: convert to MIR(JSON) and print to stdout(実行 path は未配線) + // - llvm : emit object via env.codegen; TODO: link+exec in後続フェーズ + method run_program_json(program_json, backend) { + local tag = "[stage1-cli] run program-json" + if program_json == null { + print(tag + ": program_json is null") + return 98 + } + if backend == null { backend = "vm" } + backend = "" + backend + + local mir = me.emit_mir_json(program_json) + if mir == null { return 98 } + + if backend == "llvm" { + local obj = CodegenBridgeBox.emit_object(mir) + if obj == null || ("" + obj) == "" { + print(tag + ": env.codegen.emit_object failed") + return 98 + } + print("[stage1-cli] llvm object: " + ("" + obj)) + return 0 + } + + // vm / pyvm: 現状は MIR(JSON) を吐いて終了(実行は Stage0 側に後続で橋渡し) + print(mir) + return 0 + } + + // CLI dispatcher (stub) + method stage1_main(args) { + { + local use_cli = env.get("NYASH_USE_STAGE1_CLI") + if use_cli == null || ("" + use_cli) != "1" { + if env.get("STAGE1_CLI_DEBUG") == "1" { + print("[stage1-cli/debug] stage1_main: NYASH_USE_STAGE1_CLI not set, returning 97") + } + return 97 + } + } + + // Prefer env-provided mode/source to avoid argv依存の不定値 + local emit_prog = env.get("STAGE1_EMIT_PROGRAM_JSON") + local emit_mir = env.get("STAGE1_EMIT_MIR_JSON") + local backend = env.get("STAGE1_BACKEND"); if backend == null { backend = "vm" } + local source = env.get("STAGE1_SOURCE") + local prog_path = env.get("STAGE1_PROGRAM_JSON") + + if emit_prog == "1" { + if source == null || source == "" { + print("[stage1-cli] emit program-json: STAGE1_SOURCE is required") + return 96 + } + local ps = me.emit_program_json(source) + if ps == null { return 96 } + print(ps) + return 0 + } + + if emit_mir == "1" { + local prog_json = null + if prog_path != null && prog_path != "" { + prog_json = me._read_file("[stage1-cli] emit mir-json", prog_path) + } else { + if source == null || source == "" { + print("[stage1-cli] emit mir-json: STAGE1_SOURCE or STAGE1_PROGRAM_JSON is required") + return 96 + } + prog_json = me.emit_program_json(source) + } + if prog_json == null { return 96 } + local mir = me.emit_mir_json(prog_json) + if mir == null { return 96 } + print(mir) + return 0 + } + + // Default: run path (env-provided backend/source only) + if source == null || source == "" { + print("[stage1-cli] run: source path is required (set STAGE1_SOURCE)") + return 96 + } + local prog_json = me.emit_program_json(source) + if prog_json == null { return 96 } + return me.run_program_json(prog_json, backend) + } + + // hakorune emit {program-json|mir-json} ... + method _cmd_emit(args){ + local argc = 0; if args != null { argc = args.size() } + if env.get("STAGE1_CLI_DEBUG") == "1" { + print("[stage1-cli/debug] _cmd_emit: argc=" + ("" + argc)) + } + if argc < 2 { + print("[stage1-cli] emit: usage: emit program-json | emit mir-json [--from-program-json ] ") + return 96 + } + local sub = "" + args.get(1) + if env.get("STAGE1_CLI_DEBUG") == "1" { + print("[stage1-cli/debug] _cmd_emit: sub=" + sub) + } + if sub == "program-json" { return me._cmd_emit_program_json(args) } + if sub == "mir-json" { return me._cmd_emit_mir_json(args) } + print("[stage1-cli] emit: unknown subcommand: " + sub) + return 96 + } + + method _cmd_emit_program_json(args){ + local argc = 0; if args != null { argc = args.size() } + if env.get("STAGE1_CLI_DEBUG") == "1" { + print("[stage1-cli/debug] _cmd_emit_program_json: argc=" + ("" + argc)) + } + if argc < 3 { + print("[stage1-cli] emit program-json: usage: emit program-json ") + return 96 + } + local path = "" + args.get(2) + if env.get("STAGE1_CLI_DEBUG") == "1" { + print("[stage1-cli/debug] _cmd_emit_program_json: calling emit_program_json with path=" + path) + } + local ps = me.emit_program_json(path) + if env.get("STAGE1_CLI_DEBUG") == "1" { + local status = "null" + if ps != null { status = "non-null" } + print("[stage1-cli/debug] _cmd_emit_program_json: emit_program_json returned, ps=" + status) + } + if ps == null { return 96 } + print(ps) + return 0 + } + + method _cmd_emit_mir_json(args){ + local argc = 0; if args != null { argc = args.size() } + if env.get("STAGE1_CLI_DEBUG") == "1" { + print("[stage1-cli/debug] _cmd_emit_mir_json: argc=" + ("" + argc)) + } + local prog_path = null + local source_path = null + + local i = 2 + loop(i < argc) { + local arg = "" + args.get(i) + if arg == "--from-program-json" { + if i + 1 >= argc { + print("[stage1-cli] emit mir-json: --from-program-json requires a path") + return 96 + } + prog_path = "" + args.get(i + 1) + i = i + 2 + } else { + if source_path == null { + source_path = arg + i = i + 1 + } else { + print("[stage1-cli] emit mir-json: unexpected extra argument: " + arg) + return 96 + } + } + } + + if env.get("STAGE1_CLI_DEBUG") == "1" { + print("[stage1-cli/debug] _cmd_emit_mir_json: source_path=" + source_path + " prog_path=" + prog_path) + } + + if prog_path != null && prog_path != "" && source_path != null && source_path != "" { + print("[stage1-cli] emit mir-json: specify either --from-program-json or ") + return 96 + } + if (prog_path == null || prog_path == "") && (source_path == null || source_path == "") { + print("[stage1-cli] emit mir-json: require --from-program-json or ") + return 96 + } + + local prog_json = null + if prog_path != null && prog_path != "" { + prog_json = me._read_file("[stage1-cli] emit mir-json", prog_path) + } else { + prog_json = me.emit_program_json(source_path) + } + if prog_json == null { return 96 } + + local mir = me.emit_mir_json(prog_json) + if env.get("STAGE1_CLI_DEBUG") == "1" { + local status = "null" + if mir != null { status = "non-null" } + print("[stage1-cli/debug] _cmd_emit_mir_json: emit_mir_json returned, mir=" + status) + } + if mir == null { return 96 } + print(mir) + return 0 + } + + // hakorune run --backend [-- args...] + method _cmd_run(args){ + local argc = 0; if args != null { argc = args.size() } + local backend = "vm" + local source_path = null + + local i = 1 + loop(i < argc) { + local arg = "" + args.get(i) + if arg == "--backend" { + if i + 1 >= argc { + print("[stage1-cli] run: --backend requires a value (vm|llvm|pyvm)") + return 96 + } + backend = "" + args.get(i + 1) + i = i + 2 + } else { + source_path = arg + i = i + 1 + break + } + } + + if source_path == null || source_path == "" { + print("[stage1-cli] run: source path is required") + return 96 + } + + // Remaining args after source are preserved as NYASH_SCRIPT_ARGS_JSON when unset + { + local extras = new ArrayBox() + loop(i < argc) { + extras.push("" + args.get(i)) + i = i + 1 + } + if extras.length() > 0 { + // Build a minimal JSON array string; avoid env.set unless already available + local json = "[" + local j = 0; loop(j < extras.length()) { + if j > 0 { json = json + "," } + local s = extras.get(j) + json = json + "\"" + me._escape_json_string("" + s) + "\"" + j = j + 1 + } + json = json + "]" + // env.set may be unavailable on some runtimes; best-effort + env.set("NYASH_SCRIPT_ARGS_JSON", json) + } + } + + local prog_json = me.emit_program_json(source_path) + if prog_json == null { return 96 } + return me.run_program_json(prog_json, backend) + } + + method _read_file(tag, path) { + if path == null || path == "" { + print(tag + ": source path is required") + return null + } + local fb = new FileBox() + local ok = fb.open(path, "r") + if ok != 1 { + print(tag + ": failed to open " + path) + return null + } + local content = fb.read() + fb.close() + if content == null || content == "" { + print(tag + ": source is empty") + return null + } + return "" + content + } + + method _escape_json_string(s) { + local out = "" + if s == null { return out } + local i = 0 + local n = s.length() + loop(i < n) { + local ch = s.substring(i, i + 1) + if ch == "\\" { out = out + "\\\\" } + else if ch == "\"" { out = out + "\\\"" } + else if ch == "\n" { out = out + "\\n" } + else if ch == "\r" { out = out + "\\r" } + else if ch == "\t" { out = out + "\\t" } + else { out = out + ch } + i = i + 1 + } + return out + } +} + +static box Stage1CliMain { main(args) { return Stage1Cli.stage1_main(args) } } diff --git a/src/runner/stage1_bridge.rs b/src/runner/stage1_bridge.rs new file mode 100644 index 00000000..c40993e9 --- /dev/null +++ b/src/runner/stage1_bridge.rs @@ -0,0 +1,271 @@ +/*! + * Stage‑1 CLI bridge — delegate to Hako Stage1 stub when explicitly enabled. + * + * - Entry: NYASH_USE_STAGE1_CLI=1 (default OFF). + * - Toggle guard for child recursion: NYASH_STAGE1_CLI_CHILD=1 (set by bridge). + * - Entry path override: STAGE1_CLI_ENTRY or HAKORUNE_STAGE1_ENTRY (optional). + * - Mode toggles: + * - STAGE1_EMIT_PROGRAM_JSON=1 : emit program-json + * - STAGE1_EMIT_MIR_JSON=1 : emit mir-json ( or STAGE1_PROGRAM_JSON) + * - STAGE1_BACKEND={vm|llvm|pyvm} hint for run path (default: CLI backend) + * + * Notes + * - This bridge aims to keep Rust Stage0 thin: it only invokes the Stage1 stub + * (lang/src/runner/stage1_cli.hako) with script args and exits with the stub's code. + * - When toggles are unset or this is a child invocation, the bridge is a no-op. + */ + +use super::NyashRunner; +use crate::cli::CliGroups; +use serde_json; +use std::{path::Path, process}; + +impl NyashRunner { + fn collect_modules_list() -> Option { + use std::collections::HashSet; + use std::fs; + use std::path::PathBuf; + + let path = PathBuf::from("nyash.toml"); + if !path.exists() { + return None; + } + let content = fs::read_to_string(&path).ok()?; + let mut entries: Vec = Vec::new(); + let mut seen: HashSet = HashSet::new(); + let mut in_modules = false; + for raw in content.lines() { + let line = raw.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + if line.starts_with('[') { + in_modules = line == "[modules]"; + continue; + } + if !in_modules { + continue; + } + if let Some((k, v_raw)) = line.split_once('=') { + let key = k.trim().trim_matches('"'); + let mut v = v_raw.trim(); + if let Some((head, _comment)) = v.split_once('#') { + v = head.trim(); + } + if v.starts_with('"') && v.ends_with('"') && v.len() >= 2 { + v = &v[1..v.len().saturating_sub(1)]; + } + if !key.is_empty() && !v.is_empty() { + if seen.insert(key.to_string()) { + entries.push(format!("{key}={v}")); + } + } + } + } + // Add a few well-known aliases required by Stage‑1 CLI if they are absent in nyash.toml. + for (k, v) in [ + ( + "lang.compiler.entry.using_resolver_box", + "lang/src/compiler/entry/using_resolver_box.hako", + ), + ( + "selfhost.shared.host_bridge.codegen_bridge", + "lang/src/shared/host_bridge/codegen_bridge_box.hako", + ), + ] { + if seen.insert(k.to_string()) { + entries.push(format!("{k}={v}")); + } + } + if entries.is_empty() { + None + } else { + Some(entries.join("|||")) + } + } + + /// If enabled, run the Stage‑1 CLI stub as a child process and return its exit code. + /// Returns None when the bridge is not engaged. + pub(crate) fn maybe_run_stage1_cli_stub(&self, groups: &CliGroups) -> Option { + if std::env::var("NYASH_STAGE1_CLI_CHILD").ok().as_deref() == Some("1") { + return None; + } + if std::env::var("NYASH_USE_STAGE1_CLI").ok().as_deref() != Some("1") { + return None; + } + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") + || std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("2") + { + eprintln!("[stage1-bridge/debug] NYASH_USE_STAGE1_CLI=1 detected"); + } + + let entry = std::env::var("STAGE1_CLI_ENTRY") + .or_else(|_| std::env::var("HAKORUNE_STAGE1_ENTRY")) + .unwrap_or_else(|_| "lang/src/runner/stage1_cli.hako".to_string()); + if !Path::new(&entry).exists() { + eprintln!("[stage1-cli] entry not found: {}", entry); + return Some(97); + } + + let source = groups + .input + .file + .as_ref() + .cloned() + .or_else(|| std::env::var("STAGE1_SOURCE").ok()) + .or_else(|| std::env::var("STAGE1_INPUT").ok()); + + let emit_program = std::env::var("STAGE1_EMIT_PROGRAM_JSON").ok().as_deref() == Some("1"); + let emit_mir = std::env::var("STAGE1_EMIT_MIR_JSON").ok().as_deref() == Some("1"); + + let mut stage1_args: Vec = Vec::new(); + let mut stage1_env_script_args: Option = None; + let mut stage1_source_env: Option = None; + let mut stage1_progjson_env: Option = None; + if emit_program { + let src = source.as_ref().cloned().unwrap_or_else(|| { + eprintln!("[stage1-cli] STAGE1_EMIT_PROGRAM_JSON=1 but no input file provided"); + process::exit(97); + }); + stage1_args.push("emit".into()); + stage1_args.push("program-json".into()); + stage1_args.push(src); + stage1_source_env = stage1_args.last().cloned(); + } else if emit_mir { + if let Ok(pjson) = std::env::var("STAGE1_PROGRAM_JSON") { + stage1_args.push("emit".into()); + stage1_args.push("mir-json".into()); + stage1_args.push("--from-program-json".into()); + stage1_args.push(pjson); + stage1_progjson_env = stage1_args.last().cloned(); + } else { + let src = source.as_ref().cloned().unwrap_or_else(|| { + eprintln!("[stage1-cli] STAGE1_EMIT_MIR_JSON=1 but no input file provided"); + process::exit(97); + }); + stage1_args.push("emit".into()); + stage1_args.push("mir-json".into()); + stage1_args.push(src); + stage1_source_env = stage1_args.last().cloned(); + } + } else { + let src = source.as_ref().cloned().unwrap_or_else(|| { + eprintln!("[stage1-cli] NYASH_USE_STAGE1_CLI=1 requires an input file to run"); + process::exit(97); + }); + stage1_args.push("run".into()); + let backend = std::env::var("STAGE1_BACKEND") + .ok() + .unwrap_or_else(|| groups.backend.backend.clone()); + stage1_args.push("--backend".into()); + stage1_args.push(backend); + stage1_args.push(src); + stage1_source_env = stage1_args.last().cloned(); + } + + // Forward script args provided to the parent process (via -- arg1 arg2 ...) + if let Ok(json) = std::env::var("NYASH_SCRIPT_ARGS_JSON") { + if let Ok(mut extras) = serde_json::from_str::>(&json) { + stage1_args.append(&mut extras); + } + } + // Also pass args via env to guarantee argv is well-defined in the stub. + if std::env::var("NYASH_SCRIPT_ARGS_JSON").is_err() { + if let Ok(json) = serde_json::to_string(&stage1_args) { + stage1_env_script_args = Some(json); + } + } + + let exe = std::env::current_exe().unwrap_or_else(|_| { + // Fallback to release binary path when current_exe is unavailable + std::path::PathBuf::from("target/release/nyash") + }); + let mut cmd = std::process::Command::new(exe); + let entry_fn = + std::env::var("NYASH_ENTRY").unwrap_or_else(|_| "Stage1CliMain.main/1".to_string()); + cmd.arg(&entry).arg("--"); + for a in &stage1_args { + cmd.arg(a); + } + if let Some(json) = stage1_env_script_args.as_ref() { + cmd.env("NYASH_SCRIPT_ARGS_JSON", json); + } + if let Some(src) = stage1_source_env.as_ref() { + cmd.env("STAGE1_SOURCE", src); + } + if let Some(pjson) = stage1_progjson_env.as_ref() { + cmd.env("STAGE1_PROGRAM_JSON", pjson); + } + // Pass source text inline to avoid FileBox dependency when possible. + if stage1_source_env.is_none() { + if let Some(src_path) = source.as_ref() { + if let Ok(text) = std::fs::read_to_string(&src_path) { + cmd.env("STAGE1_SOURCE_TEXT", text); + } + } + } else if let Some(src_path) = stage1_source_env.as_ref() { + if let Ok(text) = std::fs::read_to_string(src_path) { + cmd.env("STAGE1_SOURCE_TEXT", text); + } + } + cmd.env("NYASH_STAGE1_CLI_CHILD", "1"); + if std::env::var("NYASH_NYRT_SILENT_RESULT").is_err() { + cmd.env("NYASH_NYRT_SILENT_RESULT", "1"); + } + if std::env::var("NYASH_DISABLE_PLUGINS").is_err() { + cmd.env("NYASH_DISABLE_PLUGINS", "0"); + } + if std::env::var("NYASH_FILEBOX_MODE").is_err() { + cmd.env("NYASH_FILEBOX_MODE", "auto"); + } + if std::env::var("NYASH_BOX_FACTORY_POLICY").is_err() { + cmd.env("NYASH_BOX_FACTORY_POLICY", "builtin_first"); + } + if std::env::var("HAKO_STAGEB_APPLY_USINGS").is_err() { + cmd.env("HAKO_STAGEB_APPLY_USINGS", "1"); + } + if std::env::var("NYASH_ENABLE_USING").is_err() { + cmd.env("NYASH_ENABLE_USING", "1"); + } + if std::env::var("HAKO_ENABLE_USING").is_err() { + cmd.env("HAKO_ENABLE_USING", "1"); + } + if std::env::var("NYASH_PARSER_STAGE3").is_err() { + cmd.env("NYASH_PARSER_STAGE3", "1"); + } + if std::env::var("HAKO_PARSER_STAGE3").is_err() { + cmd.env("HAKO_PARSER_STAGE3", "1"); + } + if std::env::var("HAKO_STAGEB_MODULES_LIST").is_err() { + if let Some(mods) = Self::collect_modules_list() { + cmd.env("HAKO_STAGEB_MODULES_LIST", mods); + } + } + if std::env::var("NYASH_ENTRY").is_err() { + cmd.env("NYASH_ENTRY", &entry_fn); + } + if std::env::var("STAGE1_BACKEND").is_err() { + if let Some(be) = stage1_args + .windows(2) + .find(|w| w[0] == "--backend") + .map(|w| w[1].clone()) + { + cmd.env("STAGE1_BACKEND", be); + } + } + + crate::cli_v!( + "[stage1-cli] delegating to stub: {} -- {}", + entry, + stage1_args.join(" ") + ); + let status = match cmd.status() { + Ok(s) => s, + Err(e) => { + eprintln!("[stage1-cli] failed to spawn stub: {}", e); + return Some(97); + } + }; + Some(status.code().unwrap_or(1)) + } +} diff --git a/tools/stage1_debug.sh b/tools/stage1_debug.sh new file mode 100644 index 00000000..8d267bfb --- /dev/null +++ b/tools/stage1_debug.sh @@ -0,0 +1,165 @@ +#!/usr/bin/env bash +# stage1_debug.sh — Stage‑1 CLI デバッグ用ヘルパー +# +# 目的: +# - Stage0(Rust)→ Stage1 CLI stub(stage1_cli.hako)の経路を +# 再現性のある形で叩き、関連する環境変数を一括で診断する。 +# - 「いまどのフラグが効いていて、何が不足しているか」を +# 1 コマンドで見えるようにする。 +# +# 現状の方針(2025-11 時点): +# - 既存の NYASH_USE_STAGE1_CLI / STAGE1_EMIT_PROGRAM_JSON などを +# 内部でセットしつつ、将来の NYASH_STAGE1_MODE などへの移行を見据えた +# 薄いラッパとして実装する。 +# +# 使い方: +# tools/stage1_debug.sh +# tools/stage1_debug.sh --mode emit-program-json +# + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +usage() { + cat <<'USAGE' >&2 +Usage: tools/stage1_debug.sh [--mode ] + +Modes (current stub): + emit-program-json : Stage-1 CLI で Program(JSON v0) を emit + emit-mir-json : Stage-1 CLI で MIR(JSON) を emit + run-vm : Stage-1 CLI で vm backend 実行(予定) + +Examples: + tools/stage1_debug.sh apps/tests/minimal.hako + tools/stage1_debug.sh --mode emit-mir-json apps/tests/minimal.hako + +Note: + - 現時点では、内部的には既存の NYASH_USE_STAGE1_CLI / STAGE1_EMIT_* を + マッピングするだけのヘルパーです。 + - 将来 NYASH_STAGE1_MODE などが導入された際は、このスクリプトから + そちらに橋渡しする予定です。 +USAGE +} + +MODE="emit-program-json" + +while [[ $# -gt 0 ]]; do + case "$1" in + --mode) + [[ $# -ge 2 ]] || { echo "[stage1_debug] --mode requires value" >&2; usage; exit 2; } + MODE="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + --) + shift + break + ;; + *) + break + ;; + esac +done + +if [[ $# -lt 1 ]]; then + echo "[stage1_debug] missing " >&2 + usage + exit 2 +fi + +SRC="$1" +if [[ ! -f "$SRC" ]]; then + echo "[stage1_debug] source not found: $SRC" >&2 + exit 1 +fi + +# Resolve nyash/hakorune binary (Stage0) +if [[ -z "${NYASH_BIN:-}" ]]; then + if [[ -x "$ROOT_DIR/target/release/hakorune" ]]; then + NYASH_BIN="$ROOT_DIR/target/release/hakorune" + else + NYASH_BIN="$ROOT_DIR/target/release/nyash" + fi +fi + +if [[ ! -x "$NYASH_BIN" ]]; then + echo "[stage1_debug] nyash binary not found at $NYASH_BIN" >&2 + echo " build with: cargo build --release" >&2 + exit 1 +fi + +echo "[stage1_debug] ROOT_DIR = $ROOT_DIR" +echo "[stage1_debug] NYASH_BIN = $NYASH_BIN" +echo "[stage1_debug] MODE = $MODE" +echo "[stage1_debug] SRC = $SRC" +echo +echo "[stage1_debug] effective env snapshot (selected keys):" +for k in \ + NYASH_USE_STAGE1_CLI STAGE1_EMIT_PROGRAM_JSON STAGE1_EMIT_MIR_JSON \ + STAGE1_BACKEND STAGE1_SOURCE STAGE1_PROGRAM_JSON \ + NYASH_ENABLE_USING HAKO_ENABLE_USING HAKO_STAGEB_APPLY_USINGS \ + NYASH_PARSER_STAGE3 HAKO_PARSER_STAGE3 \ + NYASH_FILEBOX_MODE NYASH_BOX_FACTORY_POLICY \ + NYASH_SCRIPT_ARGS_JSON NYASH_CLI_VERBOSE STAGE1_CLI_DEBUG \ + NYASH_STAGE1_MODE NYASH_STAGE1_INPUT NYASH_FEATURES +do + v="${!k-}" + if [[ -n "$v" ]]; then + printf " %-28s= %s\n" "$k" "$v" + fi +done +echo + +# Map logical MODE → current env toggles +case "$MODE" in + emit-program-json) + export NYASH_USE_STAGE1_CLI=1 + export STAGE1_EMIT_PROGRAM_JSON=1 + unset STAGE1_EMIT_MIR_JSON + export STAGE1_BACKEND="${STAGE1_BACKEND:-vm}" + ;; + emit-mir-json) + export NYASH_USE_STAGE1_CLI=1 + export STAGE1_EMIT_MIR_JSON=1 + unset STAGE1_EMIT_PROGRAM_JSON + export STAGE1_BACKEND="${STAGE1_BACKEND:-vm}" + ;; + run-vm) + export NYASH_USE_STAGE1_CLI=1 + unset STAGE1_EMIT_PROGRAM_JSON STAGE1_EMIT_MIR_JSON + export STAGE1_BACKEND=vm + ;; + *) + echo "[stage1_debug] unknown mode: $MODE" >&2 + exit 2 + ;; +esac + +export STAGE1_SOURCE="$SRC" + +# Recommended debug toggles for Stage1 path +export NYASH_CLI_VERBOSE="${NYASH_CLI_VERBOSE:-1}" +export STAGE1_CLI_DEBUG="${STAGE1_CLI_DEBUG:-1}" +export NYASH_ALLOW_NYASH="${NYASH_ALLOW_NYASH:-1}" +export HAKO_ALLOW_NYASH="${HAKO_ALLOW_NYASH:-1}" +export NYASH_PARSER_STAGE3="${NYASH_PARSER_STAGE3:-1}" +export HAKO_PARSER_STAGE3="${HAKO_PARSER_STAGE3:-1}" +export NYASH_ENABLE_USING="${NYASH_ENABLE_USING:-1}" +export HAKO_ENABLE_USING="${HAKO_ENABLE_USING:-1}" + +echo "[stage1_debug] running Stage1 CLI via Stage0..." +echo " $NYASH_BIN $SRC" +echo + +set +e +"$NYASH_BIN" "$SRC" +rc=$? +set -e + +echo +echo "[stage1_debug] exit code: $rc" +exit "$rc" diff --git a/tools/stage1_minimal.sh b/tools/stage1_minimal.sh new file mode 100644 index 00000000..ff83e019 --- /dev/null +++ b/tools/stage1_minimal.sh @@ -0,0 +1,141 @@ +#!/usr/bin/env bash +# stage1_minimal.sh — Stage‑1 CLI 経路の最小実行ヘルパー +# +# 目的: +# - 「環境変数をあまり意識せずに」 Stage‑1 CLI 経路で +# emit program-json / emit mir-json / run-vm を叩くための +# ごく薄いラッパ。 +# - 中身は既存の NYASH_USE_STAGE1_CLI / STAGE1_EMIT_* に +# マッピングしているが、外形としては +# NYASH_STAGE1_MODE / NYASH_STAGE1_INPUT / NYASH_FEATURES +# の 3 変数イメージで扱えるようにしておく。 +# +# 使い方(暫定仕様): +# NYASH_STAGE1_MODE=emit-program-json \ +# NYASH_STAGE1_INPUT=apps/tests/minimal.hako \ +# ./tools/stage1_minimal.sh +# +# # 省略時デフォルト: +# # MODE = emit-program-json +# # INPUT = 第1引数 or 必須 +# + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +usage() { + cat <<'USAGE' >&2 +Usage: tools/stage1_minimal.sh [] + +Environment (logical vars): + NYASH_STAGE1_MODE : emit-program-json | emit-mir-json | run-vm (default: emit-program-json) + NYASH_STAGE1_INPUT : source.hako path(未設定なら第1引数) + NYASH_FEATURES : カンマ区切り using,parser-stage3,plugins など(暫定) + +Examples: + NYASH_STAGE1_MODE=emit-program-json \ + NYASH_STAGE1_INPUT=apps/tests/minimal.hako \ + ./tools/stage1_minimal.sh + + ./tools/stage1_minimal.sh apps/tests/minimal.hako + # → MODE=emit-program-json, INPUT=apps/tests/minimal.hako で実行 +USAGE +} + +MODE="${NYASH_STAGE1_MODE:-emit-program-json}" +INPUT="${NYASH_STAGE1_INPUT:-}" + +if [[ $# -ge 1 && -z "$INPUT" ]]; then + INPUT="$1" +fi + +if [[ -z "$INPUT" ]]; then + echo "[stage1_minimal] missing NYASH_STAGE1_INPUT or " >&2 + usage + exit 2 +fi + +if [[ ! -f "$INPUT" ]]; then + echo "[stage1_minimal] source not found: $INPUT" >&2 + exit 1 +fi + +# Resolve nyash/hakorune binary +if [[ -z "${NYASH_BIN:-}" ]]; then + if [[ -x "$ROOT_DIR/target/release/hakorune" ]]; then + NYASH_BIN="$ROOT_DIR/target/release/hakorune" + else + NYASH_BIN="$ROOT_DIR/target/release/nyash" + fi +fi + +if [[ ! -x "$NYASH_BIN" ]]; then + echo "[stage1_minimal] nyash binary not found at $NYASH_BIN" >&2 + echo " build with: cargo build --release" >&2 + exit 1 +fi + +echo "[stage1_minimal] ROOT_DIR = $ROOT_DIR" +echo "[stage1_minimal] NYASH_BIN = $NYASH_BIN" +echo "[stage1_minimal] MODE = $MODE" +echo "[stage1_minimal] INPUT = $INPUT" + +# Map logical flags to current envs +export STAGE1_SOURCE="$INPUT" +export NYASH_USE_STAGE1_CLI=1 + +case "$MODE" in + emit-program-json) + export STAGE1_EMIT_PROGRAM_JSON=1 + unset STAGE1_EMIT_MIR_JSON + export STAGE1_BACKEND="${STAGE1_BACKEND:-vm}" + ;; + emit-mir-json) + export STAGE1_EMIT_MIR_JSON=1 + unset STAGE1_EMIT_PROGRAM_JSON + export STAGE1_BACKEND="${STAGE1_BACKEND:-vm}" + ;; + run-vm) + unset STAGE1_EMIT_PROGRAM_JSON STAGE1_EMIT_MIR_JSON + export STAGE1_BACKEND=vm + ;; + *) + echo "[stage1_minimal] unknown NYASH_STAGE1_MODE: $MODE" >&2 + exit 2 + ;; +esac + +# nyash_debug (統合案) は現状未使用。従来どおり CLI_VERBOSE/STAGE1_CLI_DEBUG を直接見る。 +export NYASH_CLI_VERBOSE=1 +export STAGE1_CLI_DEBUG="${STAGE1_CLI_DEBUG:-1}" + +# Features (暫定: using / parser-stage3 / plugins のみ見ておく) +FEATURES="${NYASH_FEATURES:-}" +if [[ "$FEATURES" == *"using"* ]]; then + export NYASH_ENABLE_USING=1 + export HAKO_ENABLE_USING=1 +fi +if [[ "$FEATURES" == *"parser-stage3"* ]]; then + export NYASH_PARSER_STAGE3=1 + export HAKO_PARSER_STAGE3=1 +fi +if [[ "$FEATURES" == *"plugins"* ]]; then + export NYASH_DISABLE_PLUGINS=0 +else + # デフォルトは安全側: plugin許可だが config に委ねる + : # ここでは特に上書きしない +fi + +echo "[stage1_minimal] executing via Stage1 CLI..." +echo " $NYASH_BIN $INPUT" +echo + +set +e +"$NYASH_BIN" "$INPUT" +rc=$? +set -e + +echo +echo "[stage1_minimal] exit code: $rc" +exit "$rc"