feat(stage1): Phase 25.1 - Stage1 CLI デバッグ改善 + 環境変数削減計画

-  Stage1 CLI デバッグログ追加
  - lang/src/runner/stage1_cli.hako: STAGE1_CLI_DEBUG対応
  - 各関数でentry/exit/状態ログ出力
  - SSAバグ調査を容易化

-  Rust bridge 実装
  - src/runner/stage1_bridge.rs: 子プロセス起動・環境変数配線
  - NYASH_ENTRY設定、モジュールリスト生成

-  デバッグヘルパースクリプト
  - tools/stage1_debug.sh: 環境変数自動診断・詳細ログ
  - tools/stage1_minimal.sh: 推奨5変数のみで実行

-  環境変数削減計画(25個→5個)
  - docs/development/proposals/env-var-reduction-report.md
    - 使用箇所マトリックス、依存関係グラフ
    - 6段階削減ロードマップ(80%削減目標)
  - docs/development/proposals/stage1-architecture-improvement.md
    - 他言語事例調査(Rust/Go/Nim)
    - アーキテクチャ統一案、実装ロードマップ

-  LoopForm v2 設計ドキュメント
  - docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md

🎯 成果: Stage1起動の複雑さを可視化、80%削減計画確立
This commit is contained in:
nyash-codex
2025-11-21 06:22:02 +09:00
parent 6865f4acfa
commit 51359574d9
7 changed files with 2278 additions and 0 deletions

View File

@ -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 3Stage1統合 - 7個 → 3個
**統合変数セット**:
```bash
# 新設計
NYASH_STAGE1_MODE=<emit-program|emit-mir|run>
NYASH_STAGE1_INPUT=<source.hako>
NYASH_STAGE1_BACKEND=<vm|llvm|pyvm> # オプション
```
**削減できる変数**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 4Using/Parser統合 - 4個 → 1個
**統合変数**:
```bash
# 新設計
NYASH_FEATURES=<using,stage3,unified-members> # カンマ区切り
```
**削減できる変数**:
1. `NYASH_ENABLE_USING``FEATURES=using`
2. `HAKO_STAGEB_APPLY_USINGS``FEATURES=using` で自動
3. `NYASH_PARSER_STAGE3``FEATURES=stage3`
---
### Phase 5Debug統合 - 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 6nyash.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=<emit-program|emit-mir|run>
# 2. 入力ソース
NYASH_STAGE1_INPUT=<source.hako>
# 3. デバッグレベル
NYASH_DEBUG=<0|1|2|3>
# 4. 機能トグル
NYASH_FEATURES=<using,stage3,unified-members>
# 5. バックエンド選択
NYASH_STAGE1_BACKEND=<vm|llvm|pyvm>
```
**削減実績**: 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 3Stage1統合 | 4個 | 17個 | 32% |
| Phase 4Using/Parser統合 | 3個 | 14個 | 44% |
| Phase 5Debug統合 | 2個 | 12個 | 52% |
| Phase 6nyash.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 : <source.hako> │
│ 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%削減達成**

View File

@ -0,0 +1,629 @@
# Stage1セルフホスティング起動アーキテクチャ改善提案
## 📋 エグゼクティブサマリー
Nyashのセルフホスティング実装で、Stage0Rust→ Stage1.hako scriptの起動が**環境変数25個**と**3つの引数経路**で複雑化している問題を整理し、業界標準パターンに基づいた改善案を提示する。
**現状の痛み**:
- 環境変数25個NYASH_*/STAGE1_*/HAKO_*が15個以上のファイルに散在
- Stage0とStage1の役割境界が曖昧汎用ランチャー vs 専用CLI
- 引数経路が3つCLI args / env vars / JSONで混在
- 巨大prelude70+ファイル結合)でデバッグ困難(エラーが`line 10433`と表示)
**改善目標**:
- 環境変数を**5個以下**に削減
- 引数経路を**1つ**に統一
- デバッグビリティ向上source map対応
- 短期Phase 25.2と長期Phase 26+)の段階実装
---
## 🔍 A. 他言語の事例調査
### A-1. Rustコンパイラのブートストラップ3段階明確化
**アーキテクチャ**:
```
Stage 0: 事前ビルド済みベータ版rustcCI 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: アーキテクチャ統一(役割明確化)
**現状の問題**:
- Stage0Rust: 汎用ランチャー(`Main.main` / `main` を探す)
- Stage1.hako: 専用CLI`stage1_cli emit program-json ...`
- 第三の経路: Stage0が子プロセスでStage1を起動環境変数渡し
- → どれが「正」か不明瞭、エントリ解決ルールが衝突
**改善後Rust流3段階明確化**:
```
Stage 0Rust 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 <num> "<file>"`コメント挿入
- 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 <subcommand>`形式を実装
- `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スクリプト経由で」 Stage1 経路を叩けばよくなる。
- 後続で 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 <num> "<file>" コメント挿入
}
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 <subcommand>`形式
- 引数パーサーの実装(.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 <subcommand>` 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

View File

@ -0,0 +1,134 @@
# Stage1 UsingResolver — LoopForm v2 対応メモ(設計ドラフト)
目的: Stage1 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`
- Stage1 UsingResolver テストの観察点:
- `src/tests/mir_stage1_using_resolver_verify.rs`collect_entries の SSA/PHI 期待を確認)
- 既存の JSON フロント経路でどのブロック/値が PHI 化されているかを dump しておくと導線が追いやすい。
## JSON v0 フロント側の契約StageB → Stage1
- Program(JSON v0) 形: `{"version":0,"kind":"Program","body":[...], "defs":[ ... ]}`
- defs の body: StageB から渡ってくるのは `{"type":"Block","body":[Stmt...]}` ラップ。Stage1 UsingResolver ではこの形を前提に扱う。
- ループ/PHI の意味論は Rust LoopForm v2 側に委譲する。Stage1 は「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。Stage1 側で変数名が崩れると 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 受け渡しだけ担当。
## StageB → Stage1 データフロー(テキスト版)
- StageB (`compiler_stageb.hako`):
- source → body 抽出Main.main 内)→ block パーサ優先で Program(JSON v0) を構成
- defs: FuncScanner でメソッド本文を block パーサ優先で JSON 化し、`{"type":"Block","body":[…]}` でラップして Program.defs に注入
- Stage1 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 側。Stage1/.hako は「正しい形の Program(JSON) を渡す」責務に徹する。
## Stage1 CLI インターフェース設計メモ(ドラフト)
詳しい CLI サーフェスとサブコマンド設計は `docs/development/runtime/cli-hakorune-stage1.md` 側を SSOT とし、
ここでは Stage1 UsingResolverLoopForm v2 との接続と、Rust Stage0 から呼ばれる stub
`lang/src/runner/stage1_cli.hako`)の責務に絞って整理する。
- 入口関数(.hako 側で定義予定):
- `emit_program_json(source: String)` → Program(JSON v0)StageB 呼び出しラッパ)
- `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) --(StageB)--> Program(JSON v0) --(Stage1 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
## ドラフトStage1 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` は StageB/BuildBox + Stage1 UsingResolver(prefix結合) で Program(JSON v0) を出力Stage0 からは Stage1 stub 経由で子プロセス実行)。
- `emit mir-json``MirBuilderBox.emit_from_program_json_v0` を呼び出しdelegate toggles 未設定時は失敗ログを返す)。
- `run --backend <vm|pyvm>` は 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` に置換StageB パーサの 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 UsingResolverlang/src/compiler/entry/using_resolver_box.hako
- Region+next_i 化済み: entries イテレーション / JSON スキャン / modules_list 分割
- 残り: なし(現状の 3 ループは all Region 形)
- pipeline_v2 UsingResolverlang/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` で観測できるようにする。
- バグ時は StageB と同様に block パーサ優先の形に戻せるよう、作業メモを CURRENT_TASK に残すこと。

View File

@ -0,0 +1,365 @@
// stage1_cli.hako — Stage1 self-host CLI skeleton骨組みのみ
//
// 役割: StageB → Stage1 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 }
// Stage1 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 <source.hako> | emit mir-json [--from-program-json <file>] <source.hako>")
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 <source.hako>")
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 <source.hako>")
return 96
}
if (prog_path == null || prog_path == "") && (source_path == null || source_path == "") {
print("[stage1-cli] emit mir-json: require --from-program-json <file> or <source.hako>")
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 <b> <source.hako> [-- 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) } }

271
src/runner/stage1_bridge.rs Normal file
View File

@ -0,0 +1,271 @@
/*!
* Stage1 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 <source.hako>
* - STAGE1_EMIT_MIR_JSON=1 : emit mir-json (<source.hako> 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<String> {
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<String> = Vec::new();
let mut seen: HashSet<String> = 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 Stage1 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 Stage1 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<i32> {
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<String> = Vec::new();
let mut stage1_env_script_args: Option<String> = None;
let mut stage1_source_env: Option<String> = None;
let mut stage1_progjson_env: Option<String> = 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::<Vec<String>>(&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))
}
}

165
tools/stage1_debug.sh Normal file
View File

@ -0,0 +1,165 @@
#!/usr/bin/env bash
# stage1_debug.sh — Stage1 CLI デバッグ用ヘルパー
#
# 目的:
# - Stage0Rust→ Stage1 CLI stubstage1_cli.hakoの経路を
# 再現性のある形で叩き、関連する環境変数を一括で診断する。
# - 「いまどのフラグが効いていて、何が不足しているか」を
# 1 コマンドで見えるようにする。
#
# 現状の方針2025-11 時点):
# - 既存の NYASH_USE_STAGE1_CLI / STAGE1_EMIT_PROGRAM_JSON などを
# 内部でセットしつつ、将来の NYASH_STAGE1_MODE などへの移行を見据えた
# 薄いラッパとして実装する。
#
# 使い方:
# tools/stage1_debug.sh <source.hako>
# tools/stage1_debug.sh --mode emit-program-json <source.hako>
#
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
usage() {
cat <<'USAGE' >&2
Usage: tools/stage1_debug.sh [--mode <mode>] <source.hako>
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 <source.hako>" >&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"

141
tools/stage1_minimal.sh Normal file
View File

@ -0,0 +1,141 @@
#!/usr/bin/env bash
# stage1_minimal.sh — Stage1 CLI 経路の最小実行ヘルパー
#
# 目的:
# - 「環境変数をあまり意識せずに」 Stage1 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 [<source.hako>]
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 <source.hako>" >&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"