diff --git a/apps/tests/minimal_usingcollector_collect.hako b/apps/tests/minimal_usingcollector_collect.hako new file mode 100644 index 00000000..0bdc44a4 --- /dev/null +++ b/apps/tests/minimal_usingcollector_collect.hako @@ -0,0 +1,19 @@ +// minimal_usingcollector_collect.hako +// 目的: UsingCollectorBox.collect の SSA/PHI バグを最小構成で再現するテスト入力。 +// - Stage‑1 CLI が読む `apps/tests/stage1_using_minimal.hako` と同形の 1 行 using を含む。 +// - Rust 側では NyashParser → MirCompiler → VM 実行時に +// UsingCollectorBox.collect/1 の substring まわりで Undefined Value が発生していないかを観測する。 + +using lang.compiler.parser.using.using_collector_box as UsingCollectorBox + +static box Main { + main() { + // Stage‑1 using minimal と同じ形の 1 行 using。 + // UsingCollectorBox.collect はこの文字列を line-scan して JSON を返す。 + local src = "using \"foo/bar.hako\" as Foo\n" + local json = UsingCollectorBox.collect(src) + print(json) + return 0 + } +} + diff --git a/apps/tests/stage1_cli_emit_program_min.hako b/apps/tests/stage1_cli_emit_program_min.hako new file mode 100644 index 00000000..c1ebef01 --- /dev/null +++ b/apps/tests/stage1_cli_emit_program_min.hako @@ -0,0 +1,24 @@ +// stage1_cli_emit_program_min.hako +// 目的: Stage1Cli + Stage1UsingResolverBox + BuildBox を直接呼び出す中間テスト。 +// - Rust ブリッジや Stage0 環境変数の組み立てを介さず、 +// Stage1Cli.emit_program_json(src) をそのまま VM 上で実行する。 +// - src には Stage‑1 minimal と同じ using 1 行を与える。 + +using lang.src.runner.stage1_cli as Stage1Cli + +static box Main { + main(args) { + // UsingResolver 側の apply フラグは明示的に 1 にしておく。 + env.set("HAKO_STAGEB_APPLY_USINGS", "1") + env.set("HAKO_STAGEB_MODULES_LIST", "Foo=foo/bar.hako") + + // 本線と同様に STAGE1_SOURCE_TEXT 経由でソースを渡し、FileBox 経路は通さない。 + local src = "using \"foo/bar.hako\" as Foo\n" + env.set("STAGE1_SOURCE_TEXT", src) + + // 第1引数は path だが、STAGE1_SOURCE_TEXT があるため _read_file は呼ばれない。 + local prog = Stage1Cli.emit_program_json("apps/tests/stage1_using_minimal.hako") + print("[stage1_cli_emit_program_min] prog=" + ("" + prog)) + return 0 + } +} diff --git a/apps/tests/stage1_resolver_usingcollector_min.hako b/apps/tests/stage1_resolver_usingcollector_min.hako new file mode 100644 index 00000000..ddfc932e --- /dev/null +++ b/apps/tests/stage1_resolver_usingcollector_min.hako @@ -0,0 +1,27 @@ +// stage1_resolver_usingcollector_min.hako +// 目的: Stage1UsingResolverBox + UsingCollectorBox のペアだけを合体させたときに +// SSA/PHI が崩れずに動くか確認する中間レベルのテスト。 +// +// - Stage‑1 CLI 本線とは独立に、resolve_for_source(src) → UsingCollectorBox.collect(src) +// の経路だけを小さな箱で再現する。 +// - src には Stage‑1 最小ケースと同じ using 1 行を与える。 + +using lang.compiler.entry.using_resolver_box as Stage1UsingResolverBox + +static box Main { + main(args) { + // UsingResolver 側の apply フラグを明示的に有効化(prefix 経路を通す) + env.set("HAKO_STAGEB_APPLY_USINGS", "1") + + // modules_list は最低限の 1 エントリだけを与える(実際にファイルが存在しなくても良い)。 + // _read_file が失敗しても prefix 連結をスキップするだけなので、UsingCollector の JSON 生成までは到達する。 + env.set("HAKO_STAGEB_MODULES_LIST", "Foo=foo/bar.hako") + + // Stage‑1 minimal と同形の 1 行 using + local src = "using \"foo/bar.hako\" as Foo\n" + local prefix = Stage1UsingResolverBox.resolve_for_source(src) + print("[stage1_resolver_usingcollector_min] prefix=" + ("" + prefix)) + return 0 + } +} + diff --git a/apps/tests/stageb_program_min_using_only.hako b/apps/tests/stageb_program_min_using_only.hako new file mode 100644 index 00000000..820e50b6 --- /dev/null +++ b/apps/tests/stageb_program_min_using_only.hako @@ -0,0 +1,18 @@ +// stageb_program_min_using_only.hako +// 目的: Stage‑B BuildBox + ParserBox だけで +// 「using 1 行」の Program(JSON v0) emit 中に型崩れや Compare エラーが出ないか確認する最小ケース。 +// +// - Stage1Cli や UsingResolver は通さず、BuildBox.emit_program_json_v0(src, null) を直叩きする。 +// - src は Stage‑1 minimal と同じ `using "foo/bar.hako" as Foo\n` 一行。 + +using lang.compiler.build.build_box as BuildBox + +static box Main { + main(args) { + local src = "using \"foo/bar.hako\" as Foo\n" + local prog = BuildBox.emit_program_json_v0(src, null) + print("[stageb_program_min_using_only] prog=" + ("" + prog)) + return 0 + } +} + diff --git a/docs/development/architecture/mir-logs-observability.md b/docs/development/architecture/mir-logs-observability.md index 1e63b044..d942be94 100644 --- a/docs/development/architecture/mir-logs-observability.md +++ b/docs/development/architecture/mir-logs-observability.md @@ -97,12 +97,37 @@ --- +### 5. MeCall Arity Debug(Phase 25.x追加) + +**場所**: `src/mir/builder/method_call_handlers.rs` + +| 行番号 | タグ | 説明 | 用途 | +|-------|------|-----|------| +| 70-73 | `[me-call] arity mismatch (instance)` | Instance method arity不一致警告 | Dev専用 | +| 98-101 | `[me-call] arity mismatch (static)` | Static method arity不一致警告 | Dev専用 | +| 150-154 | `[static-call] emit` | Static method呼び出しトレース | 観測用 | + +**有効化環境変数**: +- `NYASH_ME_CALL_ARITY_STRICT=1` - 厳密モード(不一致でエラー返却) +- `NYASH_STATIC_CALL_TRACE=1` - トレースモード(warning出力) + +**説明**: +- `me.method(...)` 呼び出しのarity検証 +- Instance method(receiver追加)vs Static method(receiver なし)の判別 +- ParserStmtBox.parse_using/4 の5引数バグ等の検出 + +**用途と削除時期**: +- Phase XX(static box instance化)完了後に削除予定 +- 過渡期のデバッグ支援ログ(static boxのsingleton化後は不要) + +--- + ## 用途別集計表 | 用途 | 件数 | 内容 | 用途と削除時期 | |-----|-------|-----|----------| -| **Dev専用** | 11 | Stage-1 CLI / StringHelpers デバッグ用 | 削除予定(Phase 25.x 完了) | -| **観測用** | 3 | FuncScanner ループ反復観測 | 保持推奨(MIR観測 API 昇格) | +| **Dev専用** | 13 | Stage-1 CLI / StringHelpers / MeCall Arity デバッグ用 | 削除予定(Phase 25.x 完了) | +| **観測用** | 4 | FuncScanner ループ反復 / Static call トレース | 保持推奨(MIR観測 API 昇格) | | **コメント** | 1 | テスト場所の注釈 | - | --- 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 index 448c988b..a798f266 100644 --- a/docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md +++ b/docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md @@ -9,8 +9,12 @@ - `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 期待を確認) + - Stage‑1 UsingResolver テストの観察点: + - `src/tests/mir_stage1_using_resolver_verify.rs` + - collect_entries 系ループ(JSON スキャン) + - Region+next_i 形の entries ループ + - modules_list 分割ループ(Region+next_start) + - resolve_for_source 相当で entries ループと modules_map 参照を同時に行うケース - 既存の JSON フロント経路でどのブロック/値が PHI 化されているかを dump しておくと導線が追いやすい。 ## JSON v0 フロント側の契約(Stage‑B → Stage‑1) @@ -113,7 +117,9 @@ source -> | Stage-B (block) | --> | Stage-1 UsingRes | --> | MirBuilder (.ha ### ループ洗い出しメモ(entry / pipeline_v2) - entry UsingResolver(lang/src/compiler/entry/using_resolver_box.hako) - Region+next_i 化済み: entries イテレーション / JSON スキャン / modules_list 分割 - - 追加テスト: modules_list 分割ループ(start/next_start)をそのまま MIR 化できることを `mir_stage1_using_resolver_module_map_regionized_verifies` で固定。 + - 追加テスト: + - modules_list 分割ループ(start/next_start)をそのまま MIR 化できることを `mir_stage1_using_resolver_module_map_regionized_verifies` で固定。 + - resolve_for_source 相当で entries ループと modules_map 参照(MapBox.has/get)を同時に行うケースを `mir_stage1_using_resolver_resolve_with_modules_map_verifies` で固定。 - 残り: なし(現状の 3 ループは all Region 形) - pipeline_v2 UsingResolver(lang/src/compiler/pipeline_v2/using_resolver_box.hako) - 役割: modules_json 上で alias を解決する stateful helper(テキスト収集は entry 側)。 diff --git a/docs/development/runtime/cli-hakorune-stage1.md b/docs/development/runtime/cli-hakorune-stage1.md index ee905147..bb65467e 100644 --- a/docs/development/runtime/cli-hakorune-stage1.md +++ b/docs/development/runtime/cli-hakorune-stage1.md @@ -17,6 +17,10 @@ Status: design-only + Stage0 stub 実装済み(Phase 25.1 時点では仕様 - Stage1 実行時は「CLI として」ではなく、これらのランタイムサービス層(C-ABI/extern)として利用することを前提とする。 - Phase 25.1 現在は、Rust Stage0 から `.hako` 側 stub CLI(`lang/src/runner/stage1_cli.hako`)を子プロセスとして起動する ブリッジ(`src/runner/stage1_bridge.rs`)のみ実装済みで、自己ホスト EXE(`target/selfhost/hakorune`)はまだ設計段階。 + - ブリッジは env-only 仕様で Stage1 stub を呼び出し、`STAGE1_EMIT_PROGRAM_JSON` / `STAGE1_EMIT_MIR_JSON` / `STAGE1_BACKEND` + / `STAGE1_SOURCE` などの環境変数をセットする。 + - Stage‑1 UsingResolver は `HAKO_STAGEB_APPLY_USINGS=0` を既定とし、CLI 経路では prefix 連結を行わない(using 解決の検証は + 専用テストで行い、CLI 本線は Program(JSON) 生成に集中させる)。 - Stage1(Hakorune selfhost CLI) - 実体: `target/selfhost/hakorune`(Phase 25.1 では Ny Executor プロトタイプ)。 - 役割: `.hako → Program(JSON) → MIR(JSON) → 実行/EXE` というパイプラインの制御。 @@ -229,6 +233,35 @@ hakorune emit mir-json [-o ] [--quiet] - `tools/selfhost/run_stage1_cli.sh --bin /tmp/hakorune-dev emit program-json apps/tests/minimal.hako` は exit code 0 だが stdout が空のまま。Stage‑B 側で box 定義を Program(JSON) に含められるようになるまで、Rust CLI / BuildBox (`tools/hakorune_emit_mir.sh`) 経由での JSON 取得を継続する。 - using 解決は Stage0(Rust Runner)と Stage1(Hakorune)の二系統に分離する方針。Stage1 側は `lang.compiler.entry.using_resolver_box` で `nyash.toml` の `[modules]` を参照し、`HAKO_STAGEB_MODULES_LIST`(shell 側で生成した `name=path` リスト)をキーに依存 Box を text merge する。Rust 側は既存の Runner using 実装を維持し、Stage1 経路はこの Box で独立した自己ホスト導線を持つ。 +### Stage‑1 CLI デバッグメモ(Stage1Cli + BuildBox + ParserBox) + +- 中間スモーク: `apps/tests/stage1_cli_emit_program_min.hako` + - 役割: `Stage1Cli.emit_program_json` → `BuildBox.emit_program_json_v0` → `ParserBox.parse_program2` までを、Rust ブリッジや Stage0 runner を経由せずに直接 VM 上で実行する最小ケース。 + - 実行例: + ```bash + NYASH_PARSER_STAGE3=1 HAKO_PARSER_STAGE3=1 \ + NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 \ + ./target/release/hakorune apps/tests/stage1_cli_emit_program_min.hako + ``` +- Rust テスト側のハーネス: `src/tests/mir_stage1_cli_emit_program_min.rs` + - `include_str!("../../lang/src/runner/stage1_cli.hako")` で Stage1Cli 本体をバンドルし、`static box Main` を末尾に付けて 1 ソースとしてパースする形に統一。 + - `mir_stage1_cli_emit_program_min_compiles_and_verifies`: + - Stage1Cli + UsingResolver + BuildBox を含んだモジュールが MIR 生成・verify まで通ることを確認(SSA/PHI 崩壊なし)。 + - `mir_stage1_cli_emit_program_min_exec_hits_type_error`: + - VM 実行まで進め、Stage‑1 CLI 経路がまだ決定的に失敗すること(型エラー or 未解決呼び出し)が Rust テスト内で再現できることを確認するための箱。 +- 現時点で観測されている実行時エラー(2025‑11‑21 時点): + - `apps/tests/stage1_cli_emit_program_min.hako` を直接 `hakorune` で実行すると、 + - 以前の「String + Void」「String + MapBox」ではなく、 + - `Type error: unsupported compare Ge on String("using \"foo/bar.hako\" as Foo\n") and Integer(19)` + - というエラーが Stage‑1 経路で発生している。 + - 経路としては `Stage1Cli.emit_program_json` → `BuildBox.emit_program_json_v0` → `ParserBox.parse_program2` まで進んだうえで、 + どこかで Compare(Ge) の両辺が String vs Int に崩れていることが確認できている。 +- デバッグ方針: + - Stage‑1 CLI 本線のバグ追跡は、必ず「小さな箱」から行う: + - `.hako` 側: `apps/tests/stage1_cli_emit_program_min.hako` で Stage1Cli+UsingResolver+BuildBox をまとめて叩く中間スモーク。 + - Rust 側: `src/tests/mir_stage1_cli_emit_program_min.rs` で同じソースを `NyashParser → MirCompiler → VM` まで持ち込むテスト。 + - 次フェーズでは、このハーネス上で Compare(Ge) 命令をスキャンし、「左オペランドがソース行 String、右が Int になっている Compare」を 1 関数単位まで特定し、その関数を別の minimal .hako + Rust テストに切り出して修正する。 + ## `check` コマンド(予約) ```text diff --git a/docs/reference/environment-variables.md b/docs/reference/environment-variables.md new file mode 100644 index 00000000..082630a7 --- /dev/null +++ b/docs/reference/environment-variables.md @@ -0,0 +1,153 @@ +# 環境変数リファレンス + +Nyashの動作を制御する環境変数の一覧。 + +--- + +## MIR Builder検証系 + +MIRビルダーの動作検証・デバッグ用環境変数。 + +| 環境変数 | デフォルト | 用途 | 追加Phase | +|---------|----------|-----|-----------| +| `NYASH_ME_CALL_ARITY_STRICT=1` | OFF | me.method呼び出しのarity不一致でエラー返却 | Phase 25.x | +| `NYASH_STATIC_CALL_TRACE=1` | OFF | Static method呼び出しのトレース出力 | Phase 25.x | +| `NYASH_VERIFY_ALLOW_NO_PHI=1` | OFF | PHI検証スキップ(支配・マージ) | - | +| `NYASH_VERIFY_ALLOW_LEGACY=1` | OFF | レガシー命令許可 | - | +| `NYASH_VERIFY_BARRIER_STRICT=1` | OFF | Barrier配置厳密チェック | - | +| `NYASH_VERIFY_EDGE_COPY_STRICT=1` | OFF | Edge copy検証 | - | +| `NYASH_VERIFY_RET_PURITY=1` | OFF | Return block純粋性検証 | - | +| `NYASH_BREAKFINDER_SSA_TRACE=1` | OFF | SSA詳細トレース(BreakFinderBox等) | - | + +### 使用例 + +```bash +# Dev環境で厳密検証(arity不一致でビルドエラー) +NYASH_ME_CALL_ARITY_STRICT=1 cargo test + +# トレースのみ(warning表示、エラーにはしない) +NYASH_STATIC_CALL_TRACE=1 ./target/release/hakorune program.hako + +# PHI検証を一時的にスキップ +NYASH_VERIFY_ALLOW_NO_PHI=1 ./target/release/hakorune program.hako + +# SSA詳細トレース(BreakFinderBox等のデバッグ) +NYASH_BREAKFINDER_SSA_TRACE=1 cargo test mir_stage1_using_resolver_verify 2>&1 | grep "breakfinder/ssa" +``` + +--- + +## Stage-1 CLI制御系 + +Stage-1 CLI(Nyash selfhosting compiler)の制御用環境変数。 + +| 環境変数 | デフォルト | 用途 | 追加Phase | +|---------|----------|-----|-----------| +| `NYASH_USE_STAGE1_CLI=1` | OFF | Stage-1 CLI有効化 | Phase 25.1 | +| `STAGE1_CLI_DEBUG=1` | OFF | Stage-1 CLI詳細ログ | Phase 25.1 | +| `STAGE1_EMIT_PROGRAM_JSON=1` | OFF | Program JSON出力モード | Phase 25.1 | +| `STAGE1_EMIT_MIR_JSON=1` | OFF | MIR JSON出力モード | Phase 25.1 | +| `STAGE1_BACKEND={vm\|llvm\|pyvm}` | `vm` | 実行バックエンド指定 | Phase 25.1 | +| `STAGE1_SOURCE=` | - | ソースファイルパス | Phase 25.1 | +| `STAGE1_SOURCE_TEXT=` | - | ソーステキスト(inline指定) | Phase 25.1 | +| `STAGE1_PROGRAM_JSON=` | - | Program JSONパス | Phase 25.1 | + +### 使用例 + +```bash +# Stage-1 CLI経由で.hakoファイルをコンパイル +NYASH_USE_STAGE1_CLI=1 STAGE1_SOURCE=program.hako \ + ./target/release/hakorune lang/src/runner/stage1_cli.hako + +# Program JSON生成 +STAGE1_EMIT_PROGRAM_JSON=1 STAGE1_SOURCE=program.hako \ + ./target/release/hakorune lang/src/runner/stage1_cli.hako + +# デバッグログ付き +STAGE1_CLI_DEBUG=1 NYASH_USE_STAGE1_CLI=1 \ + ./target/release/hakorune program.hako +``` + +--- + +## Parser制御系 + +パーサーの動作制御用環境変数。 + +| 環境変数 | デフォルト | 用途 | 追加Phase | +|---------|----------|-----|-----------| +| `NYASH_PARSER_STAGE3=1` | OFF | Stage-3 parser有効化 | Phase 25.3 | +| `HAKO_PARSER_STAGE3=1` | OFF | Stage-3 parser有効化(.hako側) | Phase 25.3 | +| `NYASH_ENABLE_USING=1` | OFF | using文サポート有効化 | Phase 25.1 | +| `HAKO_ENABLE_USING=1` | OFF | using文サポート有効化(.hako側) | Phase 25.1 | +| `HAKO_STAGEB_APPLY_USINGS=1` | OFF | Stage-B using適用 | Phase 25.1 | + +### 使用例 + +```bash +# using文を使ったプログラムをパース +NYASH_PARSER_STAGE3=1 NYASH_ENABLE_USING=1 \ + ./target/release/hakorune program_with_using.hako +``` + +--- + +## デバッグ・観測系 + +デバッグ・トレース用環境変数。 + +| 環境変数 | デフォルト | 用途 | 追加Phase | +|---------|----------|-----|-----------| +| `NYASH_CLI_VERBOSE=1` | OFF | 詳細診断情報出力 | - | +| `NYASH_VM_TRACE=1` | OFF | VM実行トレース出力 | - | +| `NYASH_VM_DUMP_MIR=1` | OFF | VM実行前MIR出力 | - | +| `NYASH_DUMP_JSON_IR=1` | OFF | JSON IR出力 | - | +| `NYASH_STAGE1_MIR_DUMP=1` | OFF | Stage-1 MIR出力 | Phase 25.x | +| `NYASH_STAGE1_SCAN_GE=1` | OFF | Compare Ge命令スキャン | Phase 25.x | +| `NYASH_TO_I64_DEBUG=1` | OFF | to_i64変換デバッグ | Phase 25.x | +| `NYASH_FUNCSCANNER_DEBUG=1` | OFF | FuncScanner詳細ログ | Phase 25.3 | + +### 使用例 + +```bash +# VM実行トレース +NYASH_VM_TRACE=1 ./target/release/hakorune program.hako 2>&1 | grep "vm-trace" + +# MIR確認(複数手法) +NYASH_VM_DUMP_MIR=1 ./target/release/hakorune program.hako +./target/release/hakorune --dump-mir program.hako +./target/release/hakorune --emit-mir-json debug.json program.hako + +# Stage-1 CLI + MIRダンプ +NYASH_STAGE1_MIR_DUMP=1 cargo test mir_stage1_cli_emit_program_min +``` + +--- + +## プラグイン・Box制御系 + +プラグインとBox factoryの制御用環境変数。 + +| 環境変数 | デフォルト | 用途 | 追加Phase | +|---------|----------|-----|-----------| +| `NYASH_DISABLE_PLUGINS=1` | OFF | プラグイン無効化 | - | +| `NYASH_BOX_FACTORY_POLICY={builtin_first\|plugin_first}` | `builtin_first` | Box factory優先順位 | Phase 15.5 | +| `NYASH_FILEBOX_MODE={auto\|plugin\|builtin}` | `auto` | FileBox実装選択 | Phase 15.5 | + +### 使用例 + +```bash +# プラグイン無効で実行(コアBox経路のみ) +NYASH_DISABLE_PLUGINS=1 ./target/release/hakorune program.hako + +# Plugin優先でBox生成 +NYASH_BOX_FACTORY_POLICY=plugin_first ./target/release/hakorune program.hako +``` + +--- + +## 参考 + +- **MIRログ観測**: [docs/development/architecture/mir-logs-observability.md](../development/architecture/mir-logs-observability.md) +- **Phase 25.4計画**: [docs/development/roadmap/phases/phase-25.4-naming-cli-cleanup/README.md](../development/roadmap/phases/phase-25.4-naming-cli-cleanup/README.md) +- **MIR検証システム**: [src/mir/verification/](../../src/mir/verification/) diff --git a/lang/src/runner/stage1_cli.hako b/lang/src/runner/stage1_cli.hako index 1fb65cee..c175af5a 100644 --- a/lang/src/runner/stage1_cli.hako +++ b/lang/src/runner/stage1_cli.hako @@ -147,15 +147,38 @@ static box Stage1Cli { // and create a fresh ArrayBox from environment variables instead local args_safe = new ArrayBox() - // Config box: Parse all env vars into structured config - local cfg = Stage1CliConfigBox.from_env() - local mode = cfg.get("mode") - local debug = cfg.get("debug") + // 現状: MIR 型レジストリの制約により MapBox ベースの ConfigBox は本線では使用しない。 + // env から直接読み取り、Stage1CliConfigBox は将来の構造化用として温存する。 + + // mode 判定(env-only) + local mode = "disabled" + if env.get("STAGE1_EMIT_PROGRAM_JSON") == "1" { + mode = "emit_program_json" + } else if env.get("STAGE1_EMIT_MIR_JSON") == "1" { + mode = "emit_mir_json" + } else if env.get("NYASH_USE_STAGE1_CLI") == "1" { + mode = "run" + } + + // backend と入力まわり + local backend = env.get("STAGE1_BACKEND") + if backend == null { backend = "vm" } + backend = "" + backend + + local source = env.get("STAGE1_SOURCE") + local source_text = env.get("STAGE1_SOURCE_TEXT") + local prog_path = env.get("STAGE1_PROGRAM_JSON") + local debug = env.get("STAGE1_CLI_DEBUG") // Log entry point for debugging if debug == "1" { __mir__.log("[stage1_main] args_safe at entry", args_safe) - print("[stage1-cli/debug] stage1_main ENTRY: mode=" + ("" + mode) + " backend=" + ("" + cfg.get("backend"))) + print("[stage1-cli/debug] stage1_main ENTRY: mode=" + ("" + mode) + " backend=" + backend) + __mir__.log("[stage1_main] env-config", + mode, + backend, + source, + prog_path) } // Early exit if CLI is disabled @@ -166,21 +189,6 @@ static box Stage1Cli { return 97 } - // Log config toggles before dispatch - if debug == "1" { - __mir__.log("[stage1_main] config", - cfg.get("mode"), - cfg.get("backend"), - cfg.get("source_path"), - cfg.get("program_json_path")) - } - - // Extract config fields - local source = cfg.get("source_path") - local source_text = cfg.get("source_text") - local prog_path = cfg.get("program_json_path") - local backend = cfg.get("backend") - // Dispatch by mode if mode == "emit_program_json" { if source == null || source == "" { diff --git a/src/grammar/generated.rs b/src/grammar/generated.rs index 25bb6e51..d94cb9c1 100644 --- a/src/grammar/generated.rs +++ b/src/grammar/generated.rs @@ -36,15 +36,37 @@ pub static OPERATORS_DIV_RULES: &[(&str, &str, &str, &str)] = &[ ]; pub fn lookup_keyword(word: &str) -> Option<&'static str> { for (k, t) in KEYWORDS { - if *k == word { - return Some(*t); - } + if *k == word { return Some(*t); } } None } pub static SYNTAX_ALLOWED_STATEMENTS: &[&str] = &[ - "box", "global", "function", "static", "if", "loop", "break", "return", "print", "nowait", - "include", "local", "outbox", "try", "throw", "using", "from", + "box", + "global", + "function", + "static", + "if", + "loop", + "break", + "return", + "print", + "nowait", + "include", + "local", + "outbox", + "try", + "throw", + "using", + "from", ]; -pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &["add", "sub", "mul", "div", "and", "or", "eq", "ne"]; +pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[ + "add", + "sub", + "mul", + "div", + "and", + "or", + "eq", + "ne", +]; \ No newline at end of file diff --git a/src/mir/builder/method_call_handlers.rs b/src/mir/builder/method_call_handlers.rs index 829a1083..5865edd9 100644 --- a/src/mir/builder/method_call_handlers.rs +++ b/src/mir/builder/method_call_handlers.rs @@ -7,7 +7,7 @@ use crate::ast::ASTNode; use crate::mir::builder::builder_calls::CallTarget; use crate::mir::builder::calls::function_lowering; use crate::mir::builder::{MirBuilder, ValueId}; -use crate::mir::{MirInstruction, TypeOpKind}; +use crate::mir::{MirInstruction, MirType, TypeOpKind}; /// Me-call 専用のポリシー箱。 /// @@ -35,26 +35,85 @@ impl MeCallPolicyBox { } let arity = arg_values.len(); let fname = function_lowering::generate_method_function_name(cls, method, arity); - let exists = if let Some(ref module) = builder.current_module { - module.functions.contains_key(&fname) - } else { - false - }; - if exists { - // Pass 'me' as first arg - let me_id = builder.build_me_expression()?; - let mut call_args = Vec::with_capacity(arity + 1); - call_args.push(me_id); - call_args.extend(arg_values.into_iter()); - let dst = builder.next_value_id(); - // Emit as unified global call to lowered function - builder.emit_unified_call( - Some(dst), - CallTarget::Global(fname.clone()), - call_args, - )?; - builder.annotate_call_result_from_func_name(dst, &fname); - return Ok(Some(dst)); + if let Some(ref module) = builder.current_module { + if let Some(func) = module.functions.get(&fname) { + // Decide whether this lowered function expects an implicit receiver. + // Instance methods: params[0] is Box(box_name) + // Static methods: params[0] is non-Box or params.is_empty() + let params = &func.signature.params; + let is_instance_method = !params.is_empty() + && matches!(params[0], MirType::Box(_)); + + // Expected argument count from signature (including receiver for instance) + let expected_params = params.len(); + let provided_static = arg_values.len(); + let provided_instance = arg_values.len() + 1; + + // Build call_args based on method kind + let call_args: Vec = if is_instance_method { + // Instance method: prepend 'me' receiver + if expected_params != provided_instance { + if std::env::var("NYASH_ME_CALL_ARITY_STRICT") + .ok() + .as_deref() + == Some("1") + { + return Err(format!( + "[me-call] arity mismatch (instance): {}: declared {} params, got {} args(+me)", + fname, expected_params, provided_instance + )); + } else if std::env::var("NYASH_STATIC_CALL_TRACE") + .ok() + .as_deref() + == Some("1") + { + eprintln!( + "[me-call] arity mismatch (instance): {}: declared {} params, got {} args(+me)", + fname, expected_params, provided_instance + ); + } + } + let me_id = builder.build_me_expression()?; + let mut v = Vec::with_capacity(provided_instance); + v.push(me_id); + v.extend(arg_values.into_iter()); + v + } else { + // Static method: no receiver + if expected_params != provided_static { + if std::env::var("NYASH_ME_CALL_ARITY_STRICT") + .ok() + .as_deref() + == Some("1") + { + return Err(format!( + "[me-call] arity mismatch (static): {}: declared {} params, got {} args", + fname, expected_params, provided_static + )); + } else if std::env::var("NYASH_STATIC_CALL_TRACE") + .ok() + .as_deref() + == Some("1") + { + eprintln!( + "[me-call] arity mismatch (static): {}: declared {} params, got {} args", + fname, expected_params, provided_static + ); + } + } + arg_values + }; + + let dst = builder.next_value_id(); + // Emit as unified global call to lowered function + builder.emit_unified_call( + Some(dst), + CallTarget::Global(fname.clone()), + call_args, + )?; + builder.annotate_call_result_from_func_name(dst, &fname); + return Ok(Some(dst)); + } } // Fallback: treat me.method(...) as a static method on the enclosing box. diff --git a/src/runner/stage1_bridge.rs b/src/runner/stage1_bridge.rs index c40993e9..02770cb9 100644 --- a/src/runner/stage1_bridge.rs +++ b/src/runner/stage1_bridge.rs @@ -221,8 +221,12 @@ impl NyashRunner { if std::env::var("NYASH_BOX_FACTORY_POLICY").is_err() { cmd.env("NYASH_BOX_FACTORY_POLICY", "builtin_first"); } + // Stage‑1 CLI 経路では既定で using 適用を無効化し、 + // prefix は空(HAKO_STAGEB_APPLY_USINGS=0)とする。 + // UsingResolver/UsingCollector の検証は専用テストで行い、 + // CLI 本線はシンプルな Program(JSON) 生成に集中させる。 if std::env::var("HAKO_STAGEB_APPLY_USINGS").is_err() { - cmd.env("HAKO_STAGEB_APPLY_USINGS", "1"); + cmd.env("HAKO_STAGEB_APPLY_USINGS", "0"); } if std::env::var("NYASH_ENABLE_USING").is_err() { cmd.env("NYASH_ENABLE_USING", "1"); diff --git a/src/tests/mir_stage1_cli_emit_program_min.rs b/src/tests/mir_stage1_cli_emit_program_min.rs new file mode 100644 index 00000000..d8831ae6 --- /dev/null +++ b/src/tests/mir_stage1_cli_emit_program_min.rs @@ -0,0 +1,142 @@ +use crate::ast::ASTNode; +use crate::backend::VM; +use crate::mir::printer::MirPrinter; +use crate::mir::{instruction::MirInstruction, types::CompareOp, MirCompiler, MirVerifier}; +use crate::parser::NyashParser; + +fn ensure_stage3_env() { + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("HAKO_PARSER_STAGE3", "1"); + std::env::set_var("NYASH_ENABLE_USING", "1"); + std::env::set_var("HAKO_ENABLE_USING", "1"); +} + +/// Bundle Stage1Cli 本体 + 最小 Main を 1 ソースにまとめたフィクスチャ。 +/// - using 解決を Stage‑0 ランナーや hako.toml に頼らず、このテスト内だけで完結させる。 +fn stage1_cli_fixture_src() -> String { + let stage1_cli_src = include_str!("../../lang/src/runner/stage1_cli.hako"); + let test_main_src = r#" +using lang.src.runner.stage1_cli as Stage1Cli + +static box Main { + main(args) { + env.set("HAKO_STAGEB_APPLY_USINGS", "1") + env.set("HAKO_STAGEB_MODULES_LIST", "Foo=foo/bar.hako") + env.set("STAGE1_SOURCE_TEXT", "using \"foo/bar.hako\" as Foo\n") + local prog = Stage1Cli.emit_program_json("apps/tests/stage1_using_minimal.hako") + print("[stage1_cli_emit_program_min] prog=" + ("" + prog)) + return 0 + } +} +"#; + format!("{stage1_cli_src}\n\n{test_main_src}") +} + +/// Stage1Cli.emit_program_json 経路の最小再現を Rust テスト側に持ち込むハーネス。 +/// - apps/tests/stage1_cli_emit_program_min.hako と同じ形で Stage1Cli を呼び出す。 +/// - ここでは「MIR/SSA が壊れずモジュールが verify できるか」までを確認し、 +/// 実際の VM 実行時の型崩れは別フェーズで VM テストとして扱う前提。 +#[test] +fn mir_stage1_cli_emit_program_min_compiles_and_verifies() { + ensure_stage3_env(); + let src = stage1_cli_fixture_src(); + + let ast: ASTNode = NyashParser::parse_from_string(&src).expect("parse ok"); + let mut mc = MirCompiler::with_options(false); + let cr = mc.compile(ast).expect("compile"); + + // Optional: dump MIR when debugging this path + if std::env::var("NYASH_STAGE1_MIR_DUMP").ok().as_deref() == Some("1") { + let printer = MirPrinter::verbose(); + let txt = printer.print_module(&cr.module); + eprintln!("=== MIR stage1_cli_emit_program_min ===\n{}", txt); + } + + let mut verifier = MirVerifier::new(); + if let Err(errors) = verifier.verify_module(&cr.module) { + for e in &errors { + eprintln!("[rust-mir-verify] {}", e); + } + panic!("MIR verification failed for stage1_cli_emit_program_min"); + } +} + +/// VM 実行まで進めて、現在発生している String > Integer の型エラーを Rust テスト内で再現する。 +#[test] +fn mir_stage1_cli_emit_program_min_exec_hits_type_error() { + ensure_stage3_env(); + let src = stage1_cli_fixture_src(); + + let ast: ASTNode = NyashParser::parse_from_string(&src).expect("parse ok"); + let mut mc = MirCompiler::with_options(false); + let cr = mc.compile(ast).expect("compile"); + + // Optional: scan for Compare::Ge instructions to locate suspicious comparisons + if std::env::var("NYASH_STAGE1_SCAN_GE") + .ok() + .as_deref() + == Some("1") + { + for (fname, func) in cr.module.functions.iter() { + for (bb_id, bb) in func.blocks.iter() { + for inst in bb.instructions.iter() { + if let MirInstruction::Compare { op, lhs, rhs, .. } = inst { + if *op == CompareOp::Ge { + eprintln!( + "[stage1-cli/scan] Compare Ge in {} @bb{:?} lhs=%{:?} rhs=%{:?}", + fname, bb_id, lhs, rhs + ); + } + } + } + if let Some(term) = &bb.terminator { + if let MirInstruction::Compare { op, lhs, rhs, .. } = term { + if *op == CompareOp::Ge { + eprintln!( + "[stage1-cli/scan] Compare Ge(term) in {} @bb{:?} lhs=%{:?} rhs=%{:?}", + fname, bb_id, lhs, rhs + ); + } + } + } + } + } + } + + let mut verifier = MirVerifier::new(); + if let Err(errors) = verifier.verify_module(&cr.module) { + for e in &errors { + eprintln!("[rust-mir-verify] {}", e); + } + panic!("MIR verification failed for stage1_cli_emit_program_min exec path"); + } + + let mut vm = VM::new(); + let exec = vm.execute_module(&cr.module); + match exec { + Ok(v) => { + panic!( + "expected VM exec to hit Stage‑1 CLI wiring error, but it succeeded with value: {}", + v.to_string_box().value + ); + } + Err(e) => { + let msg = format!("{}", e); + if std::env::var("NYASH_STAGE1_MIR_DUMP") + .ok() + .as_deref() + == Some("1") + { + eprintln!("[stage1-cli/debug] VM exec error: {}", msg); + } + let is_type_error = + msg.contains("unsupported") && (msg.contains("Gt") || msg.contains("compare")); + let is_missing_stage1 = msg.contains("Stage1Cli.emit_program_json/1"); + assert!( + is_type_error || is_missing_stage1, + "expected Stage‑1 CLI path to fail deterministically (type mismatch or missing Stage1Cli); got: {}", + msg + ); + } + } +} diff --git a/src/tests/mir_stage1_using_resolver_verify.rs b/src/tests/mir_stage1_using_resolver_verify.rs index 2ac43b28..05ed0302 100644 --- a/src/tests/mir_stage1_using_resolver_verify.rs +++ b/src/tests/mir_stage1_using_resolver_verify.rs @@ -431,3 +431,111 @@ static box ParserBoxHarness { panic!("MIR verification failed for ParserBoxHarness"); } } + +/// resolve_for_source 相当の処理で entries ループと modules_map 参照を同時に行うケースを固定する。 +/// Region+next_i 形のループと MapBox get/has が組み合わさっても PHI/SSA が崩れないことを確認する。 +#[test] +fn mir_stage1_using_resolver_resolve_with_modules_map_verifies() { + ensure_stage3_env(); + let src = r#" +using selfhost.shared.json.utils.json_frag as JsonFragBox + +static box Stage1UsingResolverResolveWithMap { + _collect_using_entries(src_unused) { + // Minimal JSON-like entries: two usable, one empty name for skip path + local json = "[{\"name\":\"A\"},{\"name\":\"B\"},{\"name\":\"\"}]" + local out = new ArrayBox() + local pos = 0 + local n = json.length() + loop(pos < n) { + local next_pos = n + local name_idx = JsonFragBox.index_of_from(json, "\"name\":\"", pos) + if name_idx < 0 { break } + local name = JsonFragBox.read_string_after(json, name_idx + 7) + local obj_end = JsonFragBox.index_of_from(json, "}", name_idx) + if obj_end < 0 { obj_end = n } + if name != "" { + local entry = new MapBox() + entry.set("name", name) + out.push(entry) + } + next_pos = obj_end + 1 + pos = next_pos + } + return out + } + + _build_module_map(raw) { + // Delimiter分割で key=val を MapBox に積む Region+next_start 形。 + local map = new MapBox() + if raw == null { return map } + local delim = "|||" + local start = 0 + local cont = 1 + loop(cont == 1) { + local next_start = raw.length() + local next = JsonFragBox.index_of_from(raw, delim, start) + local seg = "" + if next >= 0 { + seg = raw.substring(start, next) + next_start = next + delim.length() + } else { + seg = raw.substring(start, raw.length()) + cont = 0 + } + if seg.length() > 0 { + local eq_idx = JsonFragBox.index_of_from(seg, "=", 0) + if eq_idx >= 0 { + local key = seg.substring(0, eq_idx) + local val = seg.substring(eq_idx + 1, seg.length()) + if key != "" && val != "" { + map.set(key, val) + } + } + } + start = next_start + } + return map + } + + resolve_for_source(src_unused, modules_raw) { + local entries = me._collect_using_entries(src_unused) + if entries == null { return 0 } + local modules_map = me._build_module_map(modules_raw) + local prefix = "" + local i = 0 + local n = entries.length() + loop(i < n) { + local next_i = i + 1 + local entry = entries.get(i) + local name = "" + entry.get("name") + if name != "" { + prefix = prefix + name + if modules_map.has(name) { + prefix = prefix + modules_map.get(name) + } + } + i = next_i + } + return prefix.length() + } + + main() { + // modules_map: A→/a, B→/b, 末尾デリミタ付き + return me.resolve_for_source("unused", "A=/a|||B=/b|||") + } +} +"#; + + let ast: ASTNode = NyashParser::parse_from_string(src).expect("parse ok"); + let mut mc = MirCompiler::with_options(false); + let cr = mc.compile(ast).expect("compile"); + + let mut verifier = MirVerifier::new(); + if let Err(errors) = verifier.verify_module(&cr.module) { + for e in &errors { + eprintln!("[rust-mir-verify] {}", e); + } + panic!("MIR verification failed for Stage1UsingResolverResolveWithMap"); + } +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 7a940a41..594289d4 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -14,6 +14,7 @@ pub mod mir_loopform_conditional_reassign; pub mod mir_loopform_exit_phi; pub mod mir_loopform_complex; pub mod mir_static_box_naming; +pub mod mir_stage1_cli_emit_program_min; pub mod mir_stage1_using_resolver_verify; pub mod stage1_cli_entry_ssa_smoke; pub mod mir_stageb_like_args_length;