chore: Phase 25.1 完了 - LoopForm v2/Stage1 CLI/環境変数削減 + Phase 26-D からの変更

Phase 25.1 完了成果:
-  LoopForm v2 テスト・ドキュメント・コメント完備
  - 4ケース(A/B/C/D)完全テストカバレッジ
  - 最小再現ケース作成(SSAバグ調査用)
  - SSOT文書作成(loopform_ssot.md)
  - 全ソースに [LoopForm] コメントタグ追加

-  Stage-1 CLI デバッグ環境構築
  - stage1_cli.hako 実装
  - stage1_bridge.rs ブリッジ実装
  - デバッグツール作成(stage1_debug.sh/stage1_minimal.sh)
  - アーキテクチャ改善提案文書

-  環境変数削減計画策定
  - 25変数の完全調査・分類
  - 6段階削減ロードマップ(25→5、80%削減)
  - 即時削除可能変数特定(NYASH_CONFIG/NYASH_DEBUG)

Phase 26-D からの累積変更:
- PHI実装改善(ExitPhiBuilder/HeaderPhiBuilder等)
- MIRビルダーリファクタリング
- 型伝播・最適化パス改善
- その他約300ファイルの累積変更

🎯 技術的成果:
- SSAバグ根本原因特定(条件分岐内loop変数変更)
- Region+next_iパターン適用完了(UsingCollectorBox等)
- LoopFormパターン文書化・テスト化完了
- セルフホスティング基盤強化

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: ChatGPT <noreply@openai.com>
Co-Authored-By: Task Assistant <task@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-21 06:25:17 +09:00
parent baf028a94f
commit f9d100ce01
366 changed files with 14322 additions and 5236 deletions

View File

@ -146,38 +146,38 @@ PR テンプレ(追加項目)
- [ ] 「スモークを通すためだけのハードコード」を入れていません(根治で解決)
- [ ] 受け入れ基準に記載の検証を実施しましたdev OFF/ログ確認)
### 5.2 Rust Freeze PolicySelfHost First
### 5.2 Rust Minimal PolicySelfHost First, but not Frozen
目的: 脱Rustで開発効率を最大化する。Rust層は“最小シームバグ修正”のみに留め、分析/ルール/可視化は .hako 側で実装する
目的: 脱Rustを志向しつつも、Stage1 / SelfHost ラインの整備やツールの使いやすさ向上のために、**Rust層で必要な構造的変更やブリッジ強化は積極的に許可する**。分析/ルール/可視化は引き続き .hako 側が主戦場
- 原則
- Rustは「SSOTランナー導線resolve→parse→merge」と「VM/Interpreterの安定化(バグ修正、挙動不変)」のみ
- 新規機能・ルール・可視化・チェックは .hako で実装(自己ホスト)。
- 変更は既定OFFのトグルで可逆・小差分・戻し手順ありAGENTS.md 5.1に準拠)。
- Rustは「SSOTランナー導線resolve→parse→merge」と「VM/Interpreterの安定化」を軸にしつつ、Stage1 CLI / selfhost ブリッジ / エラーメッセージ改善など**開発導線の改善**も扱ってよい
- 新規の言語ルール・静的解析ロジック・ビジネスロジックは、基本的に .hako で実装(自己ホスト)。
- 変更はできるだけ小さく・可逆に保ちつつ、必要に応じてトグルや dev 用フラグでガードAGENTS.md 5.1 に準拠)。
- 許可(最小のRust変更
- ランナー導線の保守using textmerge for .hako の維持)。
- バグ修正既定挙動不変、必要ならトグル既定OFF)。
- Analyzerシームの提供抽出のみ
- 例: `--hako-analyze <paths>` → AST/Analysis JSON を stdout/--out に出力
- 判定ロジックは持たない(抽出専用)
- 薄いCLI糖衣`--hako-check`(内部で `--hako-analyze`→.hakoアナライザをVM経由で実行
- 許可Rustでやってよいことの例
- ランナー導線の保守・改善StageB/Stage1/Stage0 の配線、using textmerge for .hako の維持・整理)。
- Stage1 / StageB / selfhost 向けのブリッジ強化(子プロセス起動、環境変数の集約、エラー表示の改善)。
- バグ修正既定挙動を壊さない範囲。必要なら既定OFFトグル
- Analyzerシームの提供AST/Analysis JSON の抽出専用。判定ロジックは持たない)
- 薄いCLI糖衣`--hako-check` など、.hako 側のアナライザを呼び出すための CLI エントリ
- 禁止/抑制
- ルール実装Lint/命名/依存/特定Box名分岐をRustに持ち込むこと
- 広域リファクタ・既定挙動変更(凍結)。
- 禁止/抑制(依然として .hako 側でやる領域)
- Lint / 命名規則 / 依存関係チェック / 特定 Box 名への分岐など、**ルール実装の本体**
- LoopSSA / 数値カーネル / 高レベルの最適化ロジックを Rust に新規実装することSelfHost で十分に表現できる範囲)。
- 広域リファクタや既定挙動を大きく変える変更(必要なら Phase/Proposal に切り出してから)。
- .hako 側の責務SelfHost
- Lint/解析/可視化/関係図DOTを .hako で実装tools/hako_check/*)。
- Lint / 解析 / 可視化 / 関係図DOTを .hako で実装tools/hako_check/*)。
- 解析入力は Rust の Analysis JSONまたは AST JSON
- 返り値は件数ベース0=OK、>0=警告/エラー件数)。
- 受け入れ基準Rust変更時
- quickスモーク/Adapter reps 緑維持(既定挙動不変)。
- 変更はトグルでガード既定OFF・小差分・戻し手順付き
- .hako からの利用例/READMEを更新
- quick スモーク / 代表テストが緑維持(既定挙動を意図せず変えていない)。
- 変更は目的が明確で、小さく・可逆(トグルや限定スコープでガード)であること
- .hako からの利用例 / README / proposal を更新し、将来の開発者が迷わないようにしておく
補足:自己ホスト達成済みのため、Rust層は“何かあった時のための最低限の手入れ”に限定。日常の機能拡張は .hako 側で行い、構造/テスト/ドキュメントを伴って進める。
補足:.hako 側の解析・Stage1 ラインが十分に動き始めたので、Rust層は「完全凍結」ではなく、**SelfHost を支えるための最小+必要な整備を行う層**という扱いに緩和する。日常の機能拡張や言語仕様変更はこれまで通り .hako 側で行い、Rust 変更は「導線の改善」「ブリッジ」「可観測性の向上」にフォーカスする。
### 6. Fail-Fast with Structure
@ -379,6 +379,7 @@ Notes
- Phase17LoopForm SelfHosting & Polish: `docs/development/roadmap/phases/phase-17-loopform-selfhost/`
- MacroBoxユーザー拡張: `docs/guides/macro-box.md`
- MacroBox in Nyash設計草案: `docs/guides/macro-box-nyash.md`
- MIR デバッグ総覧dump/hints/__mir__: `docs/guides/testing-guide.md``MIR デバッグの入口まとめ` セクション)
# Repository Guidelines

View File

@ -17,11 +17,37 @@
- .hako 側:
- StageB コンパイラ本体 / LoopSSA v2 / BreakFinderBox / PhiInjectorBox はまだ部分実装。
- JSON v0 / selfhost ルートは Rust 側の LoopForm v2 規約に追いつかせる必要がある。
- StageB / FuncScanner ライン:
- Phase 25.3 をクローズし、`stageb_fib_program_defs_canary_vm.sh` が緑(`defs``TestBox.fib/Main.main`、fib.body.body[*] に `Loop`)。
- StageB は block パーサ優先 + defs を Block 包みで構造化。次手: Stage1 UsingResolver ループの Region+next_i 揃え / Stage1 CLI program-json selfhost 準備。
---
## 1. 最近完了した重要タスク
### 1-0. Phase 25.3 — FuncScanner / StageB defs 安定化(完了)
**目的**
- StageB / FuncScanner ラインで defs が欠落したり Loop が脱落する問題を塞ぎ、selfhost 側の canary を緑に戻す。
**やったこと**
- StageBDriverBox.main:
- main 本文を `{…}` で包んだ block パーサ優先で Program(JSON) に組み立て、defs には `{"type":"Block","body":[…]}` 形式で埋め込むよう整理。
- Program パーサ fallback は `HAKO_STAGEB_PROGRAM_PARSE_FALLBACK=1` の opt-in に封じ込めskip_ws 崩れを回避)。
- StageBFuncScannerBox._scan_methods:
- block パーサ優先に統一し、Program パーサは `HAKO_STAGEB_FUNC_SCAN_PROG_FALLBACK=1` でのみ有効化。
- defs パラメータに必ず `me` を足す従来挙動は維持TestBox/Main いずれも同型で出力)。
- Rust 層の追加変更なしLoopForm v2 / LoopSnapshotMergeBox をそのまま利用)。
**結果**
- `tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh` が安定して PASS。
- `Program.kind == "Program"`
- `defs``TestBox.fib` / `Main.main` を保持していること。
- `TestBox.fib.body.body[*]``Loop` ノードが含まれること。
を満たした状態で `rc=0` になることを確認。
- FuncScanner / StageB 経由の fib defs ラインは LoopForm v2 + LoopSnapshotMergeBox 上で構造的に安定したとみなし、Phase 25.3 はクローズ。
- 次フェーズの入口が整理できたので、Stage1 UsingResolver ループRegion+next_i 形)と Stage1 CLI program-json/selfhost 導線に着手可能。
### 1-1. Phase 25.1m — Static Method / LoopForm v2 continue + PHI Fix完了
**目的**
@ -138,6 +164,26 @@
---
### 1-4. Phase 25.1A3 — Stage1 CLI bridgestub 実装)
- Rust 側: `src/runner/stage1_bridge.rs``run_refactored` 入口に組み込み、`NYASH_USE_STAGE1_CLI=1` かつ再入ガードなしのときに `lang/src/runner/stage1_cli.hako` を子プロセスで起動する。`STAGE1_EMIT_PROGRAM_JSON` / `STAGE1_EMIT_MIR_JSON` / `STAGE1_BACKEND` / `STAGE1_PROGRAM_JSON` で mode 選択。entry override は `STAGE1_CLI_ENTRY` / `HAKORUNE_STAGE1_ENTRY`
- .hako 側: `Stage1Cli` に最低限の本体を実装。
- `emit_program_json`: Stage1 UsingResolver で prefix を結合し、BuildBox.emit_program_json_v0 で Program(JSON v0) を返す。
- `emit_mir_json`: `MirBuilderBox.emit_from_program_json_v0` をそのまま呼ぶdelegate 未設定なら null
- `run_program_json`: backend==llvm の場合は `env.codegen.emit_object` まで通す。vm/pyvm は当面 MIR(JSON) を stdout に出すのみ(実行は Stage0 橋渡し未配線)。
- CLI: `emit program-json|mir-json` / `run --backend ... <src>` を受理。`NYASH_SCRIPT_ARGS_JSON` を JSON で best-effort 伝播。
- Docs: `docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md` に stub 状態を追記run は暫定挙動)。
- Known gaps: vm/pyvm 実行はまだ Stage0 への橋渡し未着手。llvm も emit object 止まりlink/exec は後続)。
### 1-5. Phase 25.1A4 — Using SSOT 薄設計BuildBox include 除去
- SSOT: `lang/src/using/resolve_ssot_box.hako` に README 相当のコメントと I/Fresolve_modules/resolve_prefixを追加。現状は no-op だが「using をここで扱う」窓口を固定。
- Stage1UsingResolver: `resolve_for_program_json` を追加し、SSOT を呼ぶ導線だけ確保(現状は透過返し)。
- BuildBox: 先頭の `include` を削除し、`using lang.compiler.entry.bundle_resolver as BundleResolver` に置換StageB パーサが include を解せない問題の足場づくり)。
- 実行確認: `NYASH_ALLOW_NYASH=1 HAKO_ALLOW_NYASH=1 NYASH_USE_STAGE1_CLI=1 STAGE1_EMIT_PROGRAM_JSON=1 ... cargo run --bin hakorune -- basic_test.hako` が RC=0 で完走(ただし stdout は `RC: 0` のみ、program-json 出力は未配線)。`cargo run ... emit program-json` は Rust CLI 側の表面が未対応のためエラー(想定挙動)。
---
## 2. まだ残っている問題・課題2025-11-18 時点)
### 2-1. StageB 本体の型エラー(`String > Integer(13)`
@ -230,27 +276,51 @@
## 3. 次にやること(候補タスク)
ここから先は「どのフェーズを進めるか」をそのときの優先度で選ぶ感じだよ。
ここから先は「どのフェーズを進めるか」をそのときの優先度で選ぶ感じだよ。
Rust 側は LoopForm v2 / StageB fib / Stage1 UsingResolver 構造テストまで一通り整ったので、当面は Stage1 CLI / selfhost ラインの小さな箱から進める。
1. **StageB / BreakFinder / FuncScanner ラインの SSA 根治Phase 25.1m 続き)**
- JSON v0 bridge の Loop lowering で、`backedge_to_cond``Jump` だけでなく `Branch(cond→header/exit)` も認識するように修正(`loop_.rs`)。
→ BreakFinderBox._find_loops/2 のような break を含むループでも header PHI が正しく張られるようにした。
- BreakFinderBox は解析用の static box として扱い、`me._find_loops` / `me._jumps_to``BreakFinderBox._find_loops` / `_jumps_to` に正規化。
`me` 未定義による Undefined ValueId を避ける。
- StageB → Stage1 パーサ経路は、`ParserBox.parse_program2` のトップレベルループではなく、`parse_block2``Main.main` の本文をブロックとしてパースし、
Program(JSON v0) を自前で組み立てる構造に変更StageB 固まり問題を回避)。
### A. Stage1 CLI / Stage0 ブリッジPhase 25.1 — いまここ)
2. **StageB 再入ガード `env.set/2` の整理**
- 再入ガードを Box 内 state で持つ方向か、Rust 側に dev 専用 extern を追加するかを決める。
- どちらにしても「本番経路prod ランナー)には影響しない」ようフラグでガードする。
- A1: Stage1 CLI stub を Stage0 Rust ランナーから呼び出すブリッジDONE
- 実装: `src/runner/stage1_bridge.rs` `src/runner/mod.rs``maybe_run_stage1_cli_stub` を追加。
- `NYASH_USE_STAGE1_CLI=1`(既定 OFFかつ `NYASH_STAGE1_CLI_CHILD!=1` のときにだけ有効。
- entry `.hako``STAGE1_CLI_ENTRY` / `HAKORUNE_STAGE1_ENTRY` で上書き可能(既定: `lang/src/runner/stage1_cli.hako`)。
- 入力ファイル / モード:
- `STAGE1_EMIT_PROGRAM_JSON=1`: `hakorune emit program-json <source.hako>` を子プロセスで実行。
- `STAGE1_EMIT_MIR_JSON=1`: `STAGE1_PROGRAM_JSON` があれば `--from-program-json`、なければ `<source.hako>` から `emit mir-json`
- 上記どちらも無い場合: `hakorune run --backend <backend> <source.hako>`backend は `STAGE1_BACKEND` か CLI の backend 設定)。
- `NYASH_SCRIPT_ARGS_JSON` を拾って `--` 以降の script 引数も Stage1 側に転送。
- 再入防止として子プロセスには `NYASH_STAGE1_CLI_CHILD=1` を付与(子側からは Rust ブリッジを素通り)。
- A2: Stage1 CLI skeleton の責務を doc に固定DONE
- `lang/src/runner/stage1_cli.hako``Stage1Cli.emit_program_json/emit_mir_json/run_program_json/stage1_main` のシグネチャとトグルトポロジーを固定。
- `docs/development/roadmap/phases/phase-25.1/stage1-usingresolver-loopform.md` に Rust 側ブリッジの振る舞いとトグル名(`NYASH_USE_STAGE1_CLI` / `STAGE1_EMIT_*` / `STAGE1_BACKEND` / `NYASH_STAGE1_CLI_CHILD`)を追記。
- A3: 次ステップ(未着手)
- Stage1 CLI skeleton に StageB/BuildBox/MirBuilder 呼び出しを順に実装し、「Program(JSON)/MIR(JSON) を selfhost 経由で emit できる」状態まで持っていく。
- `tools/selfhost/run_stage1_cli.sh` から呼び出す selfhost パスと、Rust CLl からのブリッジパスの両方で JSON I/O 契約が同じになるように揃える。
3. **.hako LoopSSA v2 実装LoopForm v2 への追従)**
- Rust LoopForm v2 の規約Carrier/Pinned / preheader/header/latch/exit`.hako` の LoopSSA に輸入。
- StageB Test2 / selfhost CLI の JSON→MIR 経路で MirVerifier 緑を目標にする。
### B. Stage1 UsingResolver / LoopForm v2 ラインPhase 25.1e 系フォロー)
4. **Selfhost / CLI 周辺のテスト整理**
- 代表的な selfhost / StageB / Stage1 CLI ケースに対して、
「Phase 25.1m でどこまで緑になったか」「どこから先が 25.1k 以降の仕事か」を tests / tools 側で見える化する。
- B1: entry UsingResolver の Region+next_i 化とコメント整備(おおむね DONE
- `lang/src/compiler/entry/using_resolver_box.hako` の 3 ループentries / JSON スキャン / modules_listは Region+next_i 形に揃え済み。
- 役割コメント: `resolve_for_source`StageB body_src を受けて prefix を返す)、`_collect_using_entries`JSON スキャンして entries を集める)、`_build_module_map`modules_list を map 化)を明記済み。
- `HAKO_STAGEB_APPLY_USINGS=0` の時は prefix を空 string にしつつ、depth ガードだけは走らせる仕様もコメントで固定。
- B2: UsingResolver 構造テスト(ループ形/PHIの拡充DONE
- `src/tests/mir_stage1_using_resolver_verify.rs` に Region+next_i ループと early-exit JSON スキャンパターンの軽量テストを追加。
- これらは cargo test 経路で常時緑を維持し、v2 quick スモークへの昇格は「実行時間とイズを見ながら後続フェーズで検討」という扱いにするdocs にメモ済み)。
- B3: pipeline_v2 UsingResolver との責務境界DONE
- `lang/src/compiler/pipeline_v2/using_resolver_box.hako` 冒頭に、「entry 側=ファイル I/O + using 収集」「pipeline_v2 側=modules_json 上の alias/path 解決」と役割メモを追加。
- RegexFlow ベースの単一路ループは Region 化不要stateful helperとし、LoopForm v2 の観点からも「観測対象外」とする。
### C. StageB / LoopSSA / Selfhost まわり(中期タスク)
- C1: StageB 再入ガード `env.set/2` の整理(据え置き)
- dev 専用 extern を Rust 側に追加するか、StageB 箱側で state に寄せるかを決める必要があるが、現在はループ/PHI ラインを優先し保留。
- このタスクに着手するときは「prod/CI 経路から完全に切り離した dev ガード」として設計する。
- C2: .hako LoopSSA v2 実装Rust LoopForm v2 への追従)
- Rust LoopForm v2 の Carrier/Pinned/BodyLocalInOut モデルを `.hako` の LoopSSA に輸入し、StageB Test2 / selfhost CLI でも MirVerifier 緑を目指す中〜長期タスク。
- 具体的な対象: `lang/src/compiler/builder/ssa/loopssa.hako` と StageB/BreakFinder 周辺の region 化済みループ。
- C3: Selfhost / CLI 周辺のテスト整理
- 代表的な selfhost / StageB / Stage1 CLI ケースを tests/tools 側でタグ付けquick/integration/selfhost、Phase 25.1 の「どこまでが Rust 側」「どこからが Stage1 側」を見える化する。
---
@ -268,44 +338,51 @@
次にどの箱から攻めるか決めたら、ここに箇条書きで足していこうね。
## 2-? StageB FuncScanner / LoopSnapshotMergeBox / MIR ロガーin progress
## 3. これからやるタスクのラフ一覧25.1 / Stage1 系
- StageBFuncScannerBox を `compiler_stageb.hako` 内に追加し、StageBDriverBox.main からは FuncScannerBox ではなくこちらを呼ぶ構成にしている
- VM 側の Undefined valueBreakFinderBox._find_loops/2は LoopForm v2 / continue + PHI 修正で解消済み。
- FuncScannerBox.scan_all_boxes/1 については、Phase 25.2 で `LoopSnapshotMergeBox` を導入し、continue/break/exit スナップショットのマージを一元化したことで
13 個の continue を含む複雑ループでも Undefined Value が出ないところまで到達しており、
さらに `lang/src/compiler/tests/funcscanner_fib_min.hako` を使った最小ハーネスでも `NYASH_VM_VERIFY_MIR=1` で Undefined Value が再現しないところまで確認済み。
- LoopSnapshotMergeBox 実装: `src/mir/phi_core/loop_snapshot_merge.rs`
- `merge_continue_for_header` / `merge_exit` / `optimize_same_value` / `sanitize_inputs` など、ヘッダ/exit PHI 用の入力統合ロジックを提供。
- LoopBuilder / LoopFormBuilder は、この箱を経由して continue_merge / exit PHI を構成するようにリファクタ済み。
- 代表テスト:
- ループ系: `mir_stageb_loop_break_continue::*` / `mir_loopform_exit_phi::*` / `mir_stageb_like_args_length::*` はすべて PASS。
- 手書きループ:
- 基本ループ: sum=100+1+2+3+4
- break/continue を含むループ: sum=19, i=7 ✅
- body-local 変数を含むループ: result=6, i=3 ✅exit PHI で body-local を正しく統合)
- StageBFuncScannerBox.scan_all_boxes(src) は依然として fib サンプルに対して defs=[]Program(JSON) に `TestBox.fib` が載っていない)という問題が残っているため、
次の担当者は StageBFuncScannerBox.scan_all_boxes/1 と FuncScannerBox.scan_all_boxes/1 の MIR を比較しつつ、
StageB body 抽出 → StageBFuncScannerBox.scan_all_boxes → Program(JSON v0).defs 注入の導線自体を見直してほしいLoopForm/PHI/スナップショット側は LoopSnapshotMergeBox で安定化済み)。
- Phase 25.3 では、FuncScanner / StageB ラインの観測専用として `.hako` から直接 MIR ログを埋め込める `__mir__` 構文を導入した。
- 構文: `__mir__.log("label", v1, v2, ...)` / `__mir__.mark("label")`
- lowering: `MirInstruction::DebugLog { message: "label", values: [v1, v2, ...] }` に変換されるLoopForm/PHI には影響しない)。
- 実行: `NYASH_MIR_DEBUG_LOG=1` のときだけ `[MIR-LOG] label: %id=value ...` を VM 側で出力し、それ以外は no-op。
- 実装ポイント: `src/mir/builder/calls/build.rs``build_method_call_impl``__mir__` receiver を検出し、`try_build_mir_debug_call``DebugLog` 命令に directly lowering している。
- これにより、FuncScannerBox.scan_all_boxes や StageBFuncScannerBox.scan_all_boxes のループ頭や `box` 検出位置にラベルを打ち、
VM 実行ログと合わせて「どの経路で未初期化の値が残っているか」を .hako 側から追いやすくなった。
- 併せて `MirBuilder::handle_me_method_call``MeCallPolicyBox` で箱化し、me-call のフォールバックを静的メソッド降下に統一した。
- `Box.method/Arity` lowered 関数が存在する場合は従来どおり `me` を先頭引数にした Global call。
- 存在しない場合は `handle_static_method_call(cls, method, arguments)` に委譲し、static helperFuncScannerBox._parse_params / _strip_comments など)呼び出しとして扱う。
- これにより static box 文脈で「実体のない me を receiver にした Method call」が生成される経路が閉じられ、FuncScannerBox._scan_methods/4 系の UndefinedValue は Rust 側で構造的に防止されている。
ここから先は「まず設計と箱分割を書いてから実装」という方針で進めるタスク群だよ
### A. Rust 層解析LoopForm v2 / JSON v0 / Stage1 観測)
## 3. Static Box フィールド仕様ドキュメント(完了)
- A-1: LoopForm v2 / LoopSnapshotMerge の入口確認
- `src/mir/loop_builder.rs` / `src/mir/phi_core/loopform_builder.rs` / `src/mir/phi_core/loop_snapshot_merge.rs` を「Stage1 から見た導線」として読み直し、どのレイヤで Carrier / Pinned / BodyLocalInOut が決まるかを short メモ化する。
- A-2: JSON v0 → MIR ブリッジの導線整理
- `src/runner/json_v0_bridge/lowering/`(特に `loop_.rs`を通して、Program(JSON v0).body / defs.body(Block) が LoopForm v2 までどう運ばれるかを図として docs に落とす。
- A-3: Stage1 UsingResolver の MIR 観測
- 既存テスト `src/tests/mir_stage1_using_resolver_verify.rs` の MIR dump を元に、「どのループが Region+next_i 化候補か」「既に問題なく LoopForm に乗っている場所はどこか」を箇条書きで整理する。
- docs/reference/language/LANGUAGE_REFERENCE_2025.md: 3.4 Static Boxパターンに「static box 内のフィールドはすべて static フィールドとして扱われる」旨を明記した。
- 言語仕様としては、static box の中では `PI: FloatBox` のような宣言で十分であり、追加の `static` キーワードは不要であることをドキュメント上で固定。
- 次タスク候補Claude Code 向け): static box / box のフィールド挙動に関するサンプルとテストVM/JSON v0 両方)の整理、および static box 上の `me` セマンティクス25.1p DebugLog フェーズと連動)の仕様化。
### B. Stage1 UsingResolver 箱化・ループ整理(.hako 側)
- B-1: Stage1UsingResolverBox の責務分割設計
- `lang/src/compiler/entry/using_resolver_box.hako` を、collect_entries / modules_map / file_read などの小さいロジック箱に概念的に分割し、「どの箱が何を責務とするか」を docs に書く(実際のファイル分割は後段)。
- B-2: すべてのループを Region+next_i 形に揃える計画
- entry の UsingResolver 内に残っている「pos++/continue 多発型」ループを洗い出し、Region+next_i 形にどう書き換えるかを phase-25.1 docs に追記する。
- 目的: LoopForm v2 / PHI から見たときに「1 region / 1 backedge / 明確な次位置決定」という形に統一する。
- 状況メモ: entry 側 3 ループentries/JSON scan/modules_listは Region+next_i 化済み。残りなし。
- B-3: pipeline_v2 UsingResolver との役割分担
- `lang/src/compiler/pipeline_v2/using_resolver_box.hako` と entry/Stage1UsingResolverBox のどちらが「テキスト / JSON / modules_map」のどこまでを担当するかを整理し、責務境界をドキュメントに固定する。
- 状況メモ: pipeline_v2 側は modules_json の alias 解決のみRegexFlow.find_from の単一路ループ。Region 化不要として据え置き、境界だけ明記。
- B-4: 構造テストの追加計画
- Region+next_i パターンの軽量 SSA テスト(すでに 1 本追加済み)を基準に、もう 1〜2 本、UsingResolver ソースに近いパターンJSON スキャンearly-exitを追加する方針を決める。
### C. Stage1 CLI / program-json / mir-json SelfHost 準備
- C-1: Stage1 CLI インターフェースの設計メモ
- `.hako → Program(JSON)` / `Program(JSON) → MIR(JSON)` / `MIR(JSON) → 実行` を Stage1 側からどう呼び出すか(関数名・引数レベル)を docs に先に書く。
- C-2: StageB → Stage1 データフロー図
- `compiler_stageb.hako` → Program(JSON v0) → Stage1 UsingResolver → MirBuilder までのパイプラインを Phase25.1 ドキュメントに 1 枚の図としてまとめる。
- C-3: Rust CLI 側ブリッジの最小ガード案
- Stage0/Rust CLI は「Program(JSON/MIR(JSON) を受け取り VM/LLVM に流すだけ」に縮退させる方針と、既定 OFF トグルselfhost 入口)をどう切るかを設計メモとして追加。
### D. 将来フェーズ向けメモvariable_map 決定化ライン)
- D-1: MirBuilder::variable_map / BoxCompilationContext::variable_map BTreeMap 化案
- どの構造を HashMap→BTreeMap 化するか、どのテスト(`mir_funcscanner_skip_ws_vm_debug_flaky` など)で決定性を確認するかを Phase25.x の設計メモとして書いておく。
- D-2: dev 専用 / flaky テストの扱い方針
- どのテストが dev ignore手動実行用で、いつ/何を満たしたら常時有効に戻すかを、このファイルと関連 README に明確に残す。
このセクションは「すぐ実装する TODO ではなく、25.1〜25.x ラインで踏むべき設計タスク一覧」として使う予定だよ。
(静的 me-call 修正の参考メモはここに残しつつ、別セクションは整理済み)
## 3. Phase 25.1q — LoopForm Front UnificationDONE / follow-up 別タスクへ)

View File

@ -43,9 +43,9 @@ Status: design+partial implementationStage1 ビルド導線の初期版まで
- Rust VM/LLVM のコアMIR インタプリタ/コード生成)の提供。
- Stage1 で AOT されたコア関数(後述)を呼び出すランチャ。
**禁止/抑制:**
- パーサ高レイヤStageBMirBuilderAotPrepnumeric core のロジックを Rust 側に新規追加しない。
- 新しい Box 実装やランタイム機能を Rust に持ち込まないPhase 25 Rust Freeze を継続)。
**禁止/抑制(緩和版):**
- パーサ高レイヤStageBMirBuilderAotPrepnumeric core の**意味論そのもの**を Rust 側に新規実装しないSelfHost の責務)
- 新しい Box 実装や高レベルランタイム機能を Rust に持ち込むのは原則避ける(ただし Stage1 ブリッジやエラーログ改善など、導線・可観測性に必要な最小限の変更は許可)。
### Stage1 — Hakorune Selfhost Binary
@ -157,6 +157,11 @@ Status: design+partial implementationStage1 ビルド導線の初期版まで
- [ ] Stage0→Stage1→Stage1' のビルドシーケンスを文章で定義(どの組み合わせで自己一致チェックを行うか)。
- [ ] 「普段使うのは Stage1」「問題発生時に Stage0 から再生成」という運用パターンを docs に記載。
### E. Stage1 UsingResolver / LoopForm v2 対応(設計)
- [x] 設計ドラフトを追加(`stage1-usingresolver-loopform.md`。Region+next_i 形ループと Carrier/Pinned 対応の指針を明文化。
- [x] Rust 側の観測結果を反映し、具体的なリライト手順とテスト項目を更新するJSON→LoopForm v2 導線StageB→Stage1 データフローのテキスト図を追記)。
## 実装チェックリスト25.1 実行順案)
### 1. バイナリ命名と役割の明確化

View File

@ -112,6 +112,7 @@ source -> | Stage-B (block) | --> | Stage-1 UsingRes | --> | MirBuilder (.ha
### ループ洗い出しメモentry / pipeline_v2
- entry UsingResolverlang/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` で固定。
- 残り: なし(現状の 3 ループは all Region 形)
- pipeline_v2 UsingResolverlang/src/compiler/pipeline_v2/using_resolver_box.hako
- 役割: modules_json 上で alias を解決する stateful helperテキスト収集は entry 側)。

View File

@ -1,5 +1,7 @@
# Phase 25.3 — FuncScanner / StageB defs 安定化
Status: 完了StageB fib defs canary 緑2025-11 時点)
## スコープ / ゴール
- 対象レイヤ
@ -22,6 +24,30 @@
- Loop/PHI の意味論は **LoopFormBuilder + LoopSnapshotMergeBox** に完全委譲したまま、
- FuncScanner / StageB は「テキストスキャンJSON 組み立て」の箱として綺麗に分離された状態にする。
## 完了ステータス2024-11-20 時点)
- `tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh` が緑Phase 25.3 の完了条件)。
- `defs``TestBox.fib` / `Main.main` が入り、`TestBox.fib.body.body[*]``Loop` ノードが含まれる状態を固定。
- StageB 本線の整理:
- `StageBDriverBox.main`: main 本文は `{…}` で包んだ block パーサ優先で JSON 化し、defs 側は `{"type":"Block","body":[…]}` に構造化して注入。
- `StageBFuncScannerBox._scan_methods`: block パーサ優先に揃え、Program パーサは `HAKO_STAGEB_FUNC_SCAN_PROG_FALLBACK=1` の opt-in 時だけ使う安全側トグルに変更skip_ws 崩れの再発防止)。
- Rust 層は無改変LoopForm v2 / LoopSnapshotMergeBox の SSOT をそのまま利用)。
## JSON v0 フロントと AOT ルートの契約Phase 25.3 時点)
- Program(JSON v0) の形:
- ルートは常に `{"version":0,"kind":"Program","body":[...]}`
- defs は `\"defs\":[{ \"name\", \"params\", \"box\", \"body\" }]` を追加する形で注入する。
- StageB / FuncScanner 経由の `defs[*].body` は必ず `{\"type\":\"Block\",\"body\":[Stmt...]}` でラップし、AOT/VM 側からは「通常の Block ノード」として読めるようにする。
- フロント/バックエンドの責務分離:
- StageB / FuncScanner: `.hako` テキスト → Program(JSON v0) まで(ループ/PHI の意味論は持たない)。
- Rust 側 LoopForm v2 / JSON v0 Bridge: Program(JSON v0) → MIR/AOT までLoop/PHI/SSA の SSOT
- StageB トグル整理(抜粋):
- `HAKO_STAGEB_FUNC_SCAN=1`(既定): defs を常に埋める。`0` で StageB からの defs 注入を無効化。
- `HAKO_STAGEB_PROGRAM_PARSE_FALLBACK=1`: main 本文で Program パーサ fallback を有効化(既定は OFF、block パーサ優先)。
- `HAKO_STAGEB_FUNC_SCAN_PROG_FALLBACK=1`: FuncScanner 側で Program パーサ fallback を有効化(既定は OFF
通常の開発・CI ではいずれも OFF にしておき、VM/パーサ調査時だけ optin で使う想定。
## すでに前提としている状態
- LoopForm v2 / PHI / snapshot:
@ -118,27 +144,28 @@ FuncScanner / StageB のデバッグ時には、`scan_all_boxes` のループ
- ここでの主なバグは「SSA ではなく、StageB が fib ソースから defs を組み立てきれていないこと」なので、
ループ構造や LoopForm には手を入れず、StageB 側のテキスト処理と defs 生成パスを中心に見る。
### 4. fib defs canary & ドキュメント更新
### 4. fib defs canary & ドキュメント更新(完了)
- 対象:
- `tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh`
- `docs/development/roadmap/phases/phase-25.1q/README.md`
- `CURRENT_TASK.md`
- やること:
- canary スクリプトで:
- `Program.kind == "Program"`
- `defs``TestBox.fib` が存在すること。
- `TestBox.fib.body` `Loop` ノードが含まれること。
を満たした状態で `rc=0` になるまで確認する
- Phase 25.1q / 25.2 の README には、
- 「loop/PHI/スナップショットの SSOT は LoopForm v2 + LoopSnapshotMergeBox」
- 「Phase 25.3 で FuncScanner / StageB defs もこの土台の上に載せた」
というつながりを 1〜2 行で追記する。
- `CURRENT_TASK.md` には:
- Phase 25.3 のタスク完了状況(FuncScanner fib / StageB fib canary 緑)と、
- その後に繋がる Stage1 UsingResolver / Stage1 CLI selfhost ラインへのブリッジ
を短く整理しておく。
今回の結果:
- `tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh``rc=0` で安定緑。
- canary 内部で確認している条件:
- `Program.kind == "Program"`
- `defs``TestBox.fib` / `Main.main` が含まれていること。
- `TestBox.fib.body` の直下(`body.body[*]`)に `Loop` ノードが最低 1 つ含まれていること
- これにより、FuncScanner / StageB 経由の fib defs ラインは LoopForm v2 + LoopSnapshotMergeBox の上で構造的に安定したとみなせる。
ドキュメント側:
- Phase 25.1q / 25.2 の README には、
- 「loop/PHI/スナップショットの SSOT は LoopForm v2 + LoopSnapshotMergeBox」
- Phase 25.3 FuncScanner / StageB defs もこの土台の上に載せた」
という関係を 1〜2 行で追記済み。
- `CURRENT_TASK.md` では Phase 25.3 を「StageB fib defs canary 緑」まで完了したフェーズとして整理し、
次フェーズを Stage1 UsingResolver ループ整理 / Stage1 CLI program-json selfhost 導線に設定した。
### 5. mir_funcscanner_skip_ws 系テストの扱いflaky → dev 専用)

View File

@ -69,7 +69,7 @@ Related docs:
- `IntArrayCore`(数値一次元配列コア)
- `MatI64`行列箱・i64版
などを、「Rust プラグイン実装」ではなく **Hakorune 実装+ごく薄い intrinsic** に置き換えるための設計ロードマップを固める。
- 新しい箱・数値カーネル・標準ランタイム機能は **原則 .hako で実装する** 方針を明文化し、「Rust Freeze PolicySelfHost First)」を Phase 25 で具体化する。
- 新しい箱・数値カーネル・標準ランタイム機能は **原則 .hako で実装する** 方針を明文化し、「Rust Minimal PolicySelfHost First, but not Frozen」として Phase 25 で具体化する。
## レイヤー方針Ring0 / Ring1
@ -82,8 +82,8 @@ Related docs:
- **汎用 intrinsic** のみ提供(例: メモリ確保・生ポインタload/store・基本的な memcpy 等)
**禁止 / 抑制:**
- 新しい Box 種類IntArrayCore / MatI64 / StringBuilder 等)を Rust 側に増やさない。
- 新しい最適化ロジック・言語ルール・Box メソッド実装を Rust に追加しないAGENTS.md 5.2 Rust Freeze Policy に準拠)。
- 新しい Box 種類IntArrayCore / MatI64 / StringBuilder 等)の**本体ロジック**を Rust 側に増やさない(型安全な intrinsic のみに留める)
- 新しい最適化ロジック・言語ルール・Box メソッド実装を Rust に追加しないAGENTS.md 5.2 Rust Minimal Policy に準拠)。
### Ring1Hakorune / Nyash ― System サブセット)
@ -112,11 +112,11 @@ Related docs:
Phase 25 は「設計とロードマップの確定」が主目的。実装・移行作業自体は後続フェーズ22.x/26.x など)で分割実施する。
### 1) Rust Freeze の明文化とチェックリスト
### 1) Rust Minimal Policy の明文化とチェックリスト
- 既存の「Rust Freeze PolicySelfHost First」を、ランタイム/箱/数値系に特化して再整理:
- 既存の「Rust Freeze PolicySelfHost First」を、「SelfHost を支える最小必要な整備は許可する」Rust Minimal Policy として再整理:
- 新規 Box / ランタイム機能は Rust ではなく .hako で実装する。
- Rust 変更は「最小の intrinsic 追加」か「バグ修正」に限定。
- Rust 変更は「最小の intrinsic 追加」「Stage1/StageB ブリッジの改善」「エラーログ・可観測性の向上」か「バグ修正」に限定。
- PR / フェーズ用チェックリスト案を作成:
- [ ] この変更は Ring0 の責務かVM/allocator/LLVM/OS FFI のみ)
- [ ] 新しい Box/アルゴリズムを Rust に追加していないか?

View File

@ -1,6 +1,6 @@
# Stage1 Hakorune CLI DesignProposal
Status: design-onlyPhase 25.1 時点では仕様策定と導線の整理まで)
Status: design-only + Stage0 stub 実装済みPhase 25.1 時点では仕様策定と導線の整理まで。selfhost EXE 本体は未実装
## ゴール
@ -15,6 +15,8 @@ Status: design-onlyPhase 25.1 時点では仕様策定と導線の整理ま
- 役割: プロセス起動・VM/LLVM コア・ny-llvmc 呼び出しなどの「Ring0」。
- Ny 側からは `env.mirbuilder.emit` / `env.codegen.emit_object` / `env.codegen.link_object` などの extern 名で見える。
- 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`)はまだ設計段階。
- Stage1Hakorune selfhost CLI
- 実体: `target/selfhost/hakorune`Phase 25.1 では Ny Executor プロトタイプ)。
- 役割: `.hako → Program(JSON) → MIR(JSON) → 実行/EXE` というパイプラインの制御。
@ -64,6 +66,10 @@ Phase 25.1a では、**`emit program-json` / `emit mir-json` / `build exe` の 3
- `--quiet` でログ抑制、`-o/--out` で出力 EXE パスを指定可能。C-API トグル(`NYASH_LLVM_USE_CAPI`, `HAKO_V1_EXTERN_PROVIDER_C_ABI`)が無効な場合は fail-fast。
- Stage1 バイナリ(`target/selfhost/hakorune`)を直接叩く際は `NYASH_NYRT_SILENT_RESULT=1` を付与し、stdout に JSON だけを流す運用を徹底する(`tools/selfhost/run_stage1_cli.sh` が環境セットとバイナリ検出を担当)。
#### デバッグ TipsPhase 25.1a
- `STAGE1_CLI_DEBUG=1` を付けると `.hako``stage1_main` の ENTRY ログが出る。Rust ブリッジが正しく Stage1 stub を呼んでいるか確認する際に使う。
- `NYASH_CLI_VERBOSE=1``2` を付けると Rust 側 bridge (`stage1_bridge.rs`) が子プロセス起動ログを出力する。
## `run` コマンド
```text

View File

@ -98,6 +98,67 @@ python3 tools/phi_trace_check.py --file tmp/phi_trace.jsonl --summary
- `tools/smokes/phi_trace_local.sh`(ビルド→サンプル実行→チェックを一括)
- `tools/smokes/v2/run.sh --profile quick|integration` で代表スモークを実行
## MIR デバッグの入口まとめ
### 1. CLI レベルの MIR ダンプ
- ソースから直接 MIR を確認:
- `./target/release/nyash --dump-mir path/to/program.hako`
- VM 実行経路で MIR を一緒に吐く:
- `NYASH_VM_DUMP_MIR=1 ./target/release/nyash path/to/program.hako`
- JSON で詳細解析したい場合:
- `./target/release/nyash --emit-mir-json mir.json path/to/program.hako`
- 例: `jq '.functions[0].blocks' mir.json` でブロック構造を確認。
### 2. Scope / Loop ヒントNYASH_MIR_HINTS
- 環境変数でヒント出力を制御:
- `NYASH_MIR_HINTS="<target>|<filters>..."`
- 例:
- `NYASH_MIR_HINTS="trace|all"`stderr へ全ヒント)
- `NYASH_MIR_HINTS="jsonl=tmp/hints.jsonl|loop"`(ループ関連のみ JSONL 出力)
- 詳細: `docs/guides/scopebox.md`, `src/mir/hints.rs`
### 3. __mir__ ロガー(.hako から仕込む MIR ログ)
- 目的:
- `.hako` 側のループや SSA まわりを「MIR レベル」で観測するための dev 専用フック。
- 実行意味論には影響しないEffect::DebugLog のみ)。
- 構文(.hako 内):
```hako
__mir__.log("label", v1, v2, ...)
__mir__.mark("label")
```
- 第1引数は String リテラル必須(それ以外は通常の呼び出し扱い)。
- `log` は第2引数以降の式を評価し、その ValueId 群を記録。
- `mark` はラベルだけのマーカー。
- 戻り値は Void 定数扱いのため、式コンテキストに書いても型崩れしない。
- 実行時の有効化:
- `NYASH_MIR_DEBUG_LOG=1 ./target/release/nyash path/to/program.hako`
- VM の MIR interpreter が次のようなログを stderr に出力:
```text
[MIR-LOG] label: %10=123 %11="foo"
```
- 実装位置:
- lowering: `src/mir/builder/calls/build.rs` の `try_build_mir_debug_call`receiver が `__mir__` のときに `MirInstruction::DebugLog` を挿入)。
- 実行: `src/backend/mir_interpreter/handlers/mod.rs``NYASH_MIR_DEBUG_LOG=1` のときだけログを出す)。
- 利用例(ループ観測の定番パターン):
```hako
loop(i < n) {
__mir__.log("loop/head", i, n)
...
}
__mir__.mark("loop/exit")
```
- FuncScanner / StageB では、skip_whitespace や scan_all_boxes のループ頭・出口に挿入して、ValueId と値の流れを追跡する用途で使用している。
## 🔌 **プラグインテスターBID-FFI診断ツール**
```bash

View File

@ -795,8 +795,8 @@ static box StageBFuncScannerBox {
}
local body = s.substring(open_idx + 1, close_idx)
// Include Main.main as well so defs covers entry + helpers
local skip_main = 0
if box_name == "Main" { skip_main = 1 }
local include_me = 0
if box_name != "Main" { include_me = 1 }
local defs_box = StageBFuncScannerBox._scan_methods(body, box_name, skip_main, include_me)
@ -979,16 +979,29 @@ static box StageBFuncScannerBox {
method_body = FuncScannerBox._strip_comments(method_body)
method_body = FuncScannerBox._trim(method_body)
// Parse method body to JSON (statement list) using block parser
// Parse method body to JSON (statement list)
// Policy: block parser first (braced) to avoid VM dispatch misrouting, then Program parser as backup.
local body_json = null
if method_body.length() > 0 {
local p = new ParserBox()
p.stage3_enable(1)
local block_src = "{" + method_body + "}"
local block_res = p.parse_block2(block_src, 0)
local at = block_res.lastIndexOf("@")
if at >= 0 {
body_json = block_res.substring(0, at)
// 1st: block parser with explicit braces (LoopForm-friendly, avoids ctx misbinding)
{
local p = new ParserBox()
p.stage3_enable(1)
local block_src = "{" + method_body + "}"
local block_res = p.parse_block2(block_src, 0)
local at = block_res.lastIndexOf("@")
if at >= 0 { body_json = block_res.substring(0, at) }
}
// 2nd: program parser trims body JSON if block path produced nothing (opt-in)
if body_json == null || body_json == "" || body_json == "[]" {
local allow_prog = env.get("HAKO_STAGEB_FUNC_SCAN_PROG_FALLBACK")
if allow_prog != null && ("" + allow_prog) == "1" {
local p2 = new ParserBox()
p2.stage3_enable(1)
local prog_json = p2.parse_program2(method_body)
local extracted = me._extract_body_from_program(prog_json)
if extracted.length() > 0 { body_json = extracted }
}
}
}
@ -1017,6 +1030,49 @@ static box StageBFuncScannerBox {
return 0
}
// Helper: extract Program.body array from Program(JSON) string
_extract_body_from_program(prog_json) {
if prog_json == null { return "" }
local s = "" + prog_json
local n = s.length()
local head = "\"body\":"
local hlen = head.length()
local start = -1
{
local i = 0
loop(i + hlen <= n) {
if s.substring(i, i + hlen) == head { start = i + hlen break }
i = i + 1
}
}
if start < 0 { return "" }
// Find '[' after "body":
local open = -1
{
local j = start
loop(j < n) {
local ch = s.substring(j, j + 1)
if ch == "[" { open = j break }
j = j + 1
}
}
if open < 0 { return "" }
local depth = 0
local k = open
loop(k < n) {
local ch2 = s.substring(k, k + 1)
if ch2 == "[" { depth = depth + 1 }
else if ch2 == "]" {
depth = depth - 1
if depth == 0 { return s.substring(open, k + 1) }
}
k = k + 1
}
return ""
}
// Helper: 空白文字スキップFuncScannerBox に委譲)
_skip_whitespace(s, idx) {
return FuncScannerBox.skip_whitespace(s, idx)
@ -1151,14 +1207,30 @@ static box StageBDriverBox {
// 6) Parse and emit Stage1 JSON v0 (Program)
// Bridge(JSON v0) が Program v0 を受け取り MIR に lowering するため、ここでは AST(JSON v0) を出力する。
// 既定で MIR 直出力は行わない(重い経路を避け、一行出力を保証)。
// StageB/selfhost: body_src は Main.main 内のブロック本文なので、ParserBox の block パーサ
// を直接使って Program(JSON) を構成する(トップレベル parser_loop は使わない)
local block_res = p.parse_block2(body_src, 0)
// StageB/selfhost: body_src は Main.main 内のブロック本文(外側の {} は含まない)。
// 安定性向上のため Program パーサを優先し、空の場合のみ block パーサで再度包んで解釈する
local ast_json = "{\"version\":0,\"kind\":\"Program\",\"body\":[]}"
{
local at = block_res.lastIndexOf("@")
if at >= 0 {
local body_json = block_res.substring(0, at)
local body_json = null
// Prefer block parser with explicit braces to avoid VM dispatch drift seen on program parser.
if body_src != null {
local block_src = "{" + body_src + "}"
local block_res = p.parse_block2(block_src, 0)
local at = block_res.lastIndexOf("@")
if at >= 0 { body_json = block_res.substring(0, at) }
}
// Optional: enable program parser fallback only when explicitly requested
if (body_json == null || body_json == "" || body_json == "[]") {
local allow_prog = env.get("HAKO_STAGEB_PROGRAM_PARSE_FALLBACK")
if allow_prog != null && ("" + allow_prog) == "1" {
local prog_json = p.parse_program2(body_src)
body_json = StageBFuncScannerBox._extract_body_from_program(prog_json)
}
}
if body_json != null && body_json != "" {
ast_json = "{\"version\":0,\"kind\":\"Program\",\"body\":" + body_json + "}"
}
}
@ -1246,6 +1318,8 @@ static box StageBDriverBox {
local mparams = def.get("params")
local mbody = "" + def.get("body_json")
local mbox = "" + def.get("box")
// Wrap body in Block node to align with Program.body shape expectations
local wrapped_body = "{\"type\":\"Block\",\"body\":" + mbody + "}"
// Build params array JSON
local params_arr = "["
local pi = 0
@ -1257,7 +1331,7 @@ static box StageBDriverBox {
}
params_arr = params_arr + "]"
if mi > 0 { defs_json = defs_json + "," }
defs_json = defs_json + "{\"name\":\"" + mname + "\",\"params\":" + params_arr + ",\"body\":" + mbody + ",\"box\":\"" + mbox + "\"}"
defs_json = defs_json + "{\"name\":\"" + mname + "\",\"params\":" + params_arr + ",\"body\":" + wrapped_body + ",\"box\":\"" + mbox + "\"}"
mi = mi + 1
}
defs_json = defs_json + "]"

View File

@ -3,6 +3,9 @@
// - Collect line-based using declarations via UsingCollectorBox.
// - Resolve namespace targets against `nyash.toml` `[modules]` entries (fed via env).
// - Read the referenced .hako files and return a concatenated prefix for StageB.
// Structure tags:
// [Region] JSON/ArrayBox/MapBox ループはすべて Region+next_i 形に統一して SSA を安定化。
// [LoopForm] carrier=pinned を明示し、continue/break を next_i で合流させる。
//
// Env requirements:
// - HAKO_STAGEB_MODULES_LIST: `name=path` entries joined by "|||".
@ -13,6 +16,10 @@ using lang.compiler.parser.scan.parser_common_utils_box as ParserCommonUtilsBox
using selfhost.shared.json.utils.json_frag as JsonFragBox
static box Stage1UsingResolverBox {
// Entrypoint: collect `using` entries and stitch a prefix.
// - HAKO_STAGEB_APPLY_USINGS=0 → prefix ""defs/body には何も足さない)
// - Depth guard prevents recursive self-calls from cascading.
// [Region] entries 走査は pos/next_pos で一意に前進。pinned: entries/seen, carrier: i。
resolve_for_source(src) {
// Shallow recursion guard to prevent accidental self-recursion in Stage1 using resolver.
{
@ -24,50 +31,88 @@ static box Stage1UsingResolverBox {
env.set("HAKO_STAGEB_USING_DEPTH", "1")
}
if src == null { return "" }
local apply_flag = env.get("HAKO_STAGEB_APPLY_USINGS")
if apply_flag != null && ("" + apply_flag) == "0" { return "" }
local entries = me._collect_using_entries(src)
if entries == null || entries.length() == 0 { return "" }
local modules = me._build_module_map()
local seen = new MapBox()
local prefix = ""
if src != null {
local apply_flag = env.get("HAKO_STAGEB_APPLY_USINGS")
// apply_flag==0 なら prefix は空のまま返すStageB defs/body に変更なし)
if apply_flag == null || ("" + apply_flag) != "0" {
local entries = me._collect_using_entries(src)
if entries != null && entries.length() > 0 {
local modules = me._build_module_map()
local seen = new MapBox()
local i = 0
local n = entries.length()
loop(i < n) {
local entry = entries.get(i)
local name = "" + entry.get("name")
if name == "" { i = i + 1 continue }
local i = 0
local n = entries.length()
loop(i < n) {
// Region+next_i 形で多経路を末尾合流させるSSA/PHI 安定化)
local next_i = i + 1
local entry = entries.get(i)
local name = "" + entry.get("name")
local path = entry.get("path")
if path == null || ("" + path) == "" {
path = me._lookup_module_path(modules, name)
} else {
path = "" + path
// 早期スキップ条件をまとめて evaluate
local should_emit = 1
if name == "" { should_emit = 0 }
// path 解決(明示 path 優先、無ければ modules マップ)
local path = null
if should_emit == 1 {
path = entry.get("path")
if path == null || ("" + path) == "" {
path = me._lookup_module_path(modules, name)
} else {
path = "" + path
}
if path == null || path == "" { should_emit = 0 }
}
// abs path 化
local abs_path = null
if should_emit == 1 {
abs_path = me._abs_path(path)
if abs_path == null || abs_path == "" { should_emit = 0 }
}
// 重複 guard
if should_emit == 1 {
if seen.get(abs_path) == "1" { should_emit = 0 }
}
// ファイル読み込み
if should_emit == 1 {
local code = me._read_file(abs_path)
if code == null { should_emit = 0 }
if should_emit == 1 {
seen.set(abs_path, "1")
prefix = prefix + "\n" + code + "\n"
}
}
i = next_i
}
}
}
if path == null || path == "" { i = i + 1 continue }
local abs_path = me._abs_path(path)
if abs_path == null || abs_path == "" { i = i + 1 continue }
if seen.get(abs_path) == "1" { i = i + 1 continue }
local code = me._read_file(abs_path)
if code == null { i = i + 1 continue }
seen.set(abs_path, "1")
prefix = prefix + "\n" + code + "\n"
i = i + 1
}
// Clear depth guard before returning
// Clear depth guard before returning(適用なしでも guard を戻す)
env.set("HAKO_STAGEB_USING_DEPTH", "0")
return prefix
}
// Program(JSON v0) に対する using 解決IOなし、SSOT委譲
// - modules_json: nyash.toml [modules] 相当の JSON
// - using_entries_json: UsingCollectorBox.collect の生出力
// - ctx: resolve_ssot_box 向けのヒントmodules/cwd/using_paths など)
resolve_for_program_json(program_json, modules_json, using_entries_json, ctx) {
using hako.using.resolve.ssot as UsingResolveSSOTBox
// MVP: IO せずに SSOT が shape を整えるだけ。現状は透過返し。
local resolved = UsingResolveSSOTBox.resolve_modules(modules_json, using_entries_json, ctx)
// 返すものは modules_json をそのまま(今は純粋パス)。将来 prefix/string を付与する。
return resolved
}
// Collect entries from UsingCollectorBox JSON output.
// Responsibility: JSON を配列にパースするだけpath 解決やファイル読み込みは呼び元)。
// [Region] JSON スキャンは pos/next_pos を明示し、early-exit も next_pos=n で一本化。
_collect_using_entries(src) {
local json = UsingCollectorBox.collect(src)
if json == null || json == "" || json == "[]" { return null }
@ -75,27 +120,36 @@ static box Stage1UsingResolverBox {
local pos = 0
local n = json.length()
loop(pos < n) {
// Region+next_pos 形break を next_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_idx < 0 {
next_pos = n
} else {
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 }
local path = null
local path_idx = JsonFragBox.index_of_from(json, "\"path\":\"", name_idx)
if path_idx >= 0 && path_idx < obj_end {
path = JsonFragBox.read_string_after(json, path_idx + 7)
local path = null
local path_idx = JsonFragBox.index_of_from(json, "\"path\":\"", name_idx)
if path_idx >= 0 && path_idx < obj_end {
path = JsonFragBox.read_string_after(json, path_idx + 7)
}
local entry = new MapBox()
entry.set("name", name)
if path != null { entry.set("path", path) }
out.push(entry)
next_pos = obj_end + 1
}
local entry = new MapBox()
entry.set("name", name)
if path != null { entry.set("path", path) }
out.push(entry)
pos = obj_end + 1
pos = next_pos
}
return out
}
// Build modules map from env `HAKO_STAGEB_MODULES_LIST` ("name=path" joined by "|||").
// Responsibility: env を MapBox に積むだけ。存在しない/空なら空 Map を返す。
// [Region] delim 走査も start/next_start で一本化し、continue/break を排除した形に固定。
_build_module_map() {
local map = new MapBox()
local raw = env.get("HAKO_STAGEB_MODULES_LIST")
@ -105,13 +159,20 @@ static box Stage1UsingResolverBox {
local delim = "|||"
local start = 0
loop(true) {
local cont = 1
loop(cont == 1) {
local next_start = str.length()
local next = ParserCommonUtilsBox.index_of(str, start, delim)
local seg = ""
if next >= 0 { seg = str.substring(start, next) } else { seg = str.substring(start, str.length()) }
if next >= 0 {
seg = str.substring(start, next)
next_start = next + delim.length()
} else {
seg = str.substring(start, str.length())
cont = 0
}
me._push_module_entry(map, seg)
if next < 0 { break }
start = next + delim.length()
start = next_start
}
return map
}
@ -152,7 +213,14 @@ static box Stage1UsingResolverBox {
if path == null { return null }
@fb = new FileBox()
@ok = fb.open(path, "r")
if ok != 1 { return null }
if ok != 1 {
// Dev note: keep quiet by default; enable verbose by setting HAKO_STAGEB_USING_DEBUG=1.
local dbg = env.get("HAKO_STAGEB_USING_DEBUG")
if dbg != null && ("" + dbg) == "1" {
print("[stageb/using_resolver] failed to open file: " + path)
}
return null
}
@content = fb.read()
fb.close()
return content

View File

@ -51,11 +51,14 @@ box ParserBox {
// === JSON utilities ===
esc_json(s) {
// Defensive: accept null/void and normalize to string once
if s == null { return "" }
local str = "" + s
local out = ""
local i = 0
local n = s.length()
local n = str.length()
loop(i < n) {
local ch = s.substring(i, i+1)
local ch = str.substring(i, i+1)
if ch == "\\" { out = out + "\\\\" }
else { if ch == "\"" { out = out + "\\\"" }
else { out = out + ch } }

View File

@ -1,20 +1,116 @@
// Moved from apps/selfhost-compiler/boxes/parser/scan/parser_string_utils_box.hako
// ParserStringUtilsBox — Delegation to StringHelpers (unified string utilities)
// ParserStringUtilsBox — Minimal self-contained string helpers
// Responsibility: Backward compatibility wrapper for parser code
// Notes: All functionality now provided by apps/selfhost/common/string_helpers.hako
using sh_core as StringHelpers
// Notes: sh_core への依存で VM 差分が出たため、ここに最小実装を内包する。
static box ParserStringUtilsBox {
// Delegate all methods to StringHelpers (centralized implementation)
i2s(v) { return StringHelpers.int_to_str(v) }
is_digit(ch) { return StringHelpers.is_digit(ch) }
is_space(ch) { return StringHelpers.is_space(ch) }
is_alpha(ch) { return StringHelpers.is_alpha(ch) }
starts_with(src, i, pat) { return StringHelpers.starts_with(src, i, pat) }
starts_with_kw(src, i, kw) { return StringHelpers.starts_with_kw(src, i, kw) }
index_of(src, i, pat) { return StringHelpers.index_of(src, i, pat) }
trim(s) { return StringHelpers.trim(s) }
to_int(s) { return StringHelpers.to_i64(s) }
skip_ws(src, i) { return StringHelpers.skip_ws(src, i) }
// Numeric to string (minimal)
i2s(v) {
local n = ParserStringUtilsBox.to_int(v)
if n == 0 { return "0" }
if n < 0 { return "-" + ParserStringUtilsBox.i2s(0 - n) }
local out = ""
loop(n > 0) {
local d = n % 10
out = "0123456789".substring(d, d + 1) + out
n = n / 10
}
return out
}
is_digit(ch) { return ch >= "0" && ch <= "9" }
is_space(ch) { return ch == " " || ch == "\t" || ch == "\n" || ch == "\r" }
is_alpha(ch) { return (ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z") || ch == "_" }
starts_with(src, i, pat) {
if src == null || pat == null { return 0 }
local n = src.length()
local m = pat.length()
if i + m > n { return 0 }
local k = 0
loop(k < m) {
if src.substring(i + k, i + k + 1) != pat.substring(k, k + 1) { return 0 }
k = k + 1
}
return 1
}
// Keyword match with boundaryStageB 安定性のためここで実装)
starts_with_kw(src, i, kw) {
if src == null || kw == null { return 0 }
local n = src.length()
local m = kw.length()
if i + m > n { return 0 }
local k = 0
loop(k < m) {
if src.substring(i + k, i + k + 1) != kw.substring(k, k + 1) { return 0 }
k = k + 1
}
local j = i + m
if j >= n { return 1 }
local ch = src.substring(j, j + 1)
if ParserStringUtilsBox.is_alpha(ch) == 1 || ParserStringUtilsBox.is_digit(ch) == 1 { return 0 }
return 1
}
index_of(src, i, pat) {
if src == null || pat == null { return -1 }
local n = src.length()
local m = pat.length()
if m == 0 { return i }
local j = i
loop(j + m <= n) {
if ParserStringUtilsBox.starts_with(src, j, pat) == 1 { return j }
j = j + 1
}
return -1
}
trim(s) {
if s == null { return "" }
local str = "" + s
local n = str.length()
local b = 0
loop(b < n) {
local ch = str.substring(b, b + 1)
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { b = b + 1 } else { break }
}
local e = n
loop(e > b) {
local ch = str.substring(e - 1, e)
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { e = e - 1 } else { break }
}
if e > b { return str.substring(b, e) }
return ""
}
to_int(s) {
if s == null { return 0 }
local str = "" + s
local n = str.length()
if n == 0 { return 0 }
local neg = 0
local i = 0
if str.substring(0, 1) == "-" { neg = 1 i = 1 }
local acc = 0
loop(i < n) {
local ch = str.substring(i, i + 1)
if ch < "0" || ch > "9" { break }
acc = acc * 10 + ("0123456789".indexOf(ch))
i = i + 1
}
if neg == 1 { return 0 - acc }
return acc
}
skip_ws(src, i) {
if src == null { return i }
local n = src.length()
local j = i
loop(j < n) {
local ch = src.substring(j, j + 1)
if ParserStringUtilsBox.is_space(ch) == 1 { j = j + 1 } else { break }
}
return j
}
}

View File

@ -7,15 +7,16 @@ using sh_core as StringHelpers // Required: using chain resolution not implemen
static box ParserControlBox {
// Parse: if (cond) { ... } else { ... }
parse_if(src, i, stmt_start, ctx) {
// Shallow recursion guard for StageB / selfhost: protect parse_if from
// accidental self-recursion via malformed input or control flow bugs.
// Depth guard (allow nesting, cap runaway recursion)
local if_depth_before = 0
{
local depth = env.get("HAKO_STAGEB_IF_DEPTH")
if depth != null && ("" + depth) != "0" {
local depth_raw = env.get("HAKO_STAGEB_IF_DEPTH")
if depth_raw != null { if_depth_before = StringHelpers.to_i64("" + depth_raw) }
if if_depth_before >= 64 {
print("[stageb/recursion] ParserControlBox.parse_if recursion detected")
return "{\"type\":\"If\",\"cond\":{\"type\":\"Bool\",\"value\":false},\"then\":[]}"
}
env.set("HAKO_STAGEB_IF_DEPTH", "1")
env.set("HAKO_STAGEB_IF_DEPTH", StringHelpers.int_to_str(if_depth_before + 1))
}
local j = i + 2 // skip "if"
j = ctx.skip_ws(src, j)
@ -52,7 +53,7 @@ static box ParserControlBox {
}
ctx.gpos_set(j)
env.set("HAKO_STAGEB_IF_DEPTH", "0")
env.set("HAKO_STAGEB_IF_DEPTH", StringHelpers.int_to_str(if_depth_before))
if else_json == null {
return "{\"type\":\"If\",\"cond\":" + cond + ",\"then\":" + then_json + "}"
} else {
@ -63,14 +64,15 @@ static box ParserControlBox {
// Parse: loop(cond) { ... }
// Dev tracing: set HAKO_PARSER_TRACE_LOOP=1 to enable debug prints
parse_loop(src, i, stmt_start, ctx) {
// Shallow recursion guard for parse_loop
local loop_depth_before = 0
{
local depth = env.get("HAKO_STAGEB_LOOP_DEPTH")
if depth != null && ("" + depth) != "0" {
local depth_raw = env.get("HAKO_STAGEB_LOOP_DEPTH")
if depth_raw != null { loop_depth_before = StringHelpers.to_i64("" + depth_raw) }
if loop_depth_before >= 64 {
print("[stageb/recursion] ParserControlBox.parse_loop recursion detected")
return "{\"type\":\"Loop\",\"cond\":{\"type\":\"Bool\",\"value\":false},\"body\":[]}"
}
env.set("HAKO_STAGEB_LOOP_DEPTH", "1")
env.set("HAKO_STAGEB_LOOP_DEPTH", StringHelpers.int_to_str(loop_depth_before + 1))
}
local trace = 0 // dev-only (disabled in Stage-B runtime: no env API here)
@ -154,7 +156,7 @@ static box ParserControlBox {
if j < src.length() { j = j + 1 } else { j = src.length() }
}
ctx.gpos_set(j)
env.set("HAKO_STAGEB_LOOP_DEPTH", "0")
env.set("HAKO_STAGEB_LOOP_DEPTH", StringHelpers.int_to_str(loop_depth_before))
return "{\"type\":\"Loop\",\"cond\":" + cond + ",\"body\":" + body_json + "}"
}
@ -222,15 +224,27 @@ static box ParserControlBox {
// Parse: { stmt1; stmt2; ... }
parse_block(src, i, ctx) {
local depth_before = 0
{
local depth = env.get("HAKO_STAGEB_BLOCK_DEPTH")
if depth != null && ("" + depth) != "0" {
local depth_raw = env.get("HAKO_STAGEB_BLOCK_DEPTH")
if depth_raw != null { depth_before = StringHelpers.to_i64("" + depth_raw) }
if depth_before >= 64 {
print("[stageb/recursion] ParserControlBox.parse_block recursion detected")
return "[]@" + ctx.i2s(i)
}
env.set("HAKO_STAGEB_BLOCK_DEPTH", "1")
env.set("HAKO_STAGEB_BLOCK_DEPTH", StringHelpers.int_to_str(depth_before + 1))
}
local j = ctx.skip_ws(src, i)
// StageB safety: inline whitespace skip in case ctx.skip_ws fails to advance on VM
{
local n = src.length()
local jj = j
loop(jj < n) {
local ch = src.substring(jj, jj+1)
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { jj = jj + 1 } else { break }
}
j = jj
}
if src.substring(j, j+1) != "{" { return "[]@" + ctx.i2s(j) }
j = j + 1
@ -283,7 +297,7 @@ static box ParserControlBox {
}
body = body + "]"
env.set("HAKO_STAGEB_BLOCK_DEPTH", "0")
env.set("HAKO_STAGEB_BLOCK_DEPTH", StringHelpers.int_to_str(depth_before))
return body + "@" + ctx.i2s(j)
}
}

View File

@ -15,12 +15,23 @@ static box ParserStmtBox {
local depth = env.get("HAKO_STAGEB_STMT_DEPTH")
if depth != null && ("" + depth) != "0" {
print("[stageb/recursion] ParserStmtBox.parse recursion detected")
// Fail-fast: return empty stmt and keep position unchanged
return ""
// Allow re-entry: reset depth to 1 and continueドロップしない
env.set("HAKO_STAGEB_STMT_DEPTH", "1")
} else {
env.set("HAKO_STAGEB_STMT_DEPTH", "1")
}
env.set("HAKO_STAGEB_STMT_DEPTH", "1")
}
local j = ctx.skip_ws(src, i)
// VM 差分で skip_ws が前進しないパスがあったため、手動で最小限の空白スキップをフォローする。
{
local n = src.length()
local jj = j
loop(jj < n) {
local ch = src.substring(jj, jj+1)
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { jj = jj + 1 } else { break }
}
j = jj
}
{
local tr = env.get("NYASH_PARSER_TRACE")
if tr != null && ("" + tr) == "1" {
@ -86,49 +97,6 @@ static box ParserStmtBox {
return out_using
}
// assignment: IDENT '=' expr
if j < src.length() && ctx.is_alpha(src.substring(j, j+1)) {
{
local tr = env.get("NYASH_PARSER_TRACE")
if tr != null && ("" + tr) == "1" {
print("[parser/stmt] kind=assign-or-local j=" + ("" + j))
}
}
local idp0 = ctx.read_ident2(src, j)
local at0 = idp0.lastIndexOf("@")
if at0 > 0 {
local name0 = idp0.substring(0, at0)
local k0 = ctx.to_int(idp0.substring(at0+1, idp0.length()))
k0 = ctx.skip_ws(src, k0)
if k0 < src.length() && src.substring(k0, k0+1) == "=" {
local eq_two = "="
if k0 + 1 < src.length() { eq_two = src.substring(k0, k0+2) }
if eq_two != "==" {
k0 = k0 + 1
k0 = ctx.skip_ws(src, k0)
local default_local = "{\"type\":\"Int\",\"value\":0}"
local expr_json0 = default_local
local end_pos0 = k0
if k0 < src.length() {
local ahead = src.substring(k0, k0+1)
if ahead != "}" && ahead != ";" {
expr_json0 = ctx.parse_expr2(src, k0)
end_pos0 = ctx.gpos_get()
}
}
k0 = end_pos0
if k0 <= stmt_start {
if k0 < src.length() { k0 = k0 + 1 } else { k0 = src.length() }
}
ctx.gpos_set(k0)
// Reset recursion guard before returning
env.set("HAKO_STAGEB_STMT_DEPTH", "0")
return "{\"type\":\"Local\",\"name\":\"" + name0 + "\",\"expr\":" + expr_json0 + "}"
}
}
}
}
// return statement
if ctx.starts_with_kw(src, j, "return") == 1 {
{
@ -288,6 +256,49 @@ static box ParserStmtBox {
return out_try
}
// assignment: IDENT '=' exprキーワードより後段に配置して、'loop' などを誤認しないようにする)
if j < src.length() && ctx.is_alpha(src.substring(j, j+1)) {
{
local tr = env.get("NYASH_PARSER_TRACE")
if tr != null && ("" + tr) == "1" {
print("[parser/stmt] kind=assign-or-local j=" + ("" + j))
}
}
local idp0 = ctx.read_ident2(src, j)
local at0 = idp0.lastIndexOf("@")
if at0 > 0 {
local name0 = idp0.substring(0, at0)
local k0 = ctx.to_int(idp0.substring(at0+1, idp0.length()))
k0 = ctx.skip_ws(src, k0)
if k0 < src.length() && src.substring(k0, k0+1) == "=" {
local eq_two = "="
if k0 + 1 < src.length() { eq_two = src.substring(k0, k0+2) }
if eq_two != "==" {
k0 = k0 + 1
k0 = ctx.skip_ws(src, k0)
local default_local = "{\"type\":\"Int\",\"value\":0}"
local expr_json0 = default_local
local end_pos0 = k0
if k0 < src.length() {
local ahead = src.substring(k0, k0+1)
if ahead != "}" && ahead != ";" {
expr_json0 = ctx.parse_expr2(src, k0)
end_pos0 = ctx.gpos_get()
}
}
k0 = end_pos0
if k0 <= stmt_start {
if k0 < src.length() { k0 = k0 + 1 } else { k0 = src.length() }
}
ctx.gpos_set(k0)
// Reset recursion guard before returning
env.set("HAKO_STAGEB_STMT_DEPTH", "0")
return "{\"type\":\"Local\",\"name\":\"" + name0 + "\",\"expr\":" + expr_json0 + "}"
}
}
}
}
// Fallback: expression or unknown token
{
local tr = env.get("NYASH_PARSER_TRACE")
@ -365,6 +376,8 @@ static box ParserStmtBox {
if j < src.length() { j = j + 1 } else { j = src.length() }
}
ctx.gpos_set(j)
// Reset recursion guard before returning (using statements participate in stmt depth tracking)
env.set("HAKO_STAGEB_STMT_DEPTH", "0")
return ""
}
}

View File

@ -1,4 +1,8 @@
// UsingResolverBox — static, stateful resolver helpersインスタンス禁止・VM互換
// Boundary memo:
// - entry/using_resolver_box: file I/O + using 収集、modules_list を MapBox へ積む役。
// - pipeline_v2/using_resolver_box: modules_json 上で alias/path をテキスト検索で解決する役。
// RegexFlow の単一路ループのみで continue/backedge 分岐を持たないため、Region+next_i 化は不要と判断。
// State layout (Map): {
// alias_paths: Map, alias_names: Map, alias_keys: Array,
// modules_map: Map, modules_keys: Array

View File

@ -107,6 +107,10 @@ static box Stage1Cli {
// CLI dispatcher (stub)
method stage1_main(args) {
if env.get("STAGE1_CLI_DEBUG") == "1" {
local argc = 0; if args != null { argc = args.size() }
print("[stage1-cli/debug] stage1_main ENTRY: argc=" + ("" + argc) + " env_emits={prog=" + ("" + env.get("STAGE1_EMIT_PROGRAM_JSON")) + ",mir=" + ("" + env.get("STAGE1_EMIT_MIR_JSON")) + "} backend=" + ("" + env.get("STAGE1_BACKEND")))
}
{
local use_cli = env.get("NYASH_USE_STAGE1_CLI")
if use_cli == null || ("" + use_cli) != "1" {

View File

@ -9,6 +9,18 @@
// - ctx.using_paths: Array<String>(将来のヒント/本箱では純粋合成のみ)
// - ctx.cwd: String相対の基準
// Quick README (Phase 25.1 using設計)
// - 役割: using 解決の唯一の窓口SSOT。parser/StageB/Stage1 は IO や名前探索をここに委譲する。
// - I/F:
// - resolve(name, ctx) -> path|null : modules/cwd/using_paths から純粋に合成して返すIOなし
// - resolve_modules(modules_json, using_entries_json, ctx) -> modules_json_resolved|null :
// modules_jsonnyash.toml 相当)と using_entries_jsonUsingCollector 出力)を突き合わせ、
// 「解決済み modules_json」同名重複などの調停結果を返す。IOなし。
// - resolve_prefix(using_entries_json, modules_json, ctx) -> prefix_string :
// using_entries_json を modules_json を使ってパス解決し、ファイル読まずに
// 「prefix 文字列空でよいを組み立てるための情報」を返すスコープMVPは空文字返しでOK
// - ポリシー: IO/ファイル読み込みは絶対にしない。より重い処理は entry/pipeline 側の責務。
static box UsingResolveSSOTBox {
/// Resolve a module name to a file path string (or null when not found).
/// name: requested module name (e.g., "hako.mir.builder.internal.lower_return_int")
@ -55,4 +67,16 @@ static box UsingResolveSSOTBox {
if base.endsWith("/") { return base + leaf }
return base + "/" + leaf
}
// Merge modules_json + using_entries_json into resolved modules table (pure, no IO).
resolve_modules(modules_json, using_entries_json, ctx) {
// MVP: just echo back modules_json (keep behavior unchanged) to establish interface.
return modules_json
}
// Build prefix string from using_entries_json with modules_json (pure, no IO).
resolve_prefix(using_entries_json, modules_json, ctx) {
// MVP: no file read, so prefix is empty. Interface is reserved for Stage1 entry.
return ""
}
}

View File

@ -136,6 +136,7 @@ path = "lang/src/shared/common/string_helpers.hako"
"lang.compiler.entry.compiler" = "lang/src/compiler/entry/compiler.hako"
"lang.compiler.entry.compiler_stageb" = "lang/src/compiler/entry/compiler_stageb.hako"
"lang.compiler.entry.bundle_resolver" = "lang/src/compiler/entry/bundle_resolver.hako"
"lang.compiler.entry.using_resolver_box" = "lang/src/compiler/entry/using_resolver_box.hako"
"lang.compiler.entry.using_resolver" = "lang/src/compiler/entry/using_resolver_box.hako"
"lang.compiler.emit.mir_emitter_box" = "lang/src/compiler/emit/mir_emitter_box.hako"
"lang.compiler.emit.common.json_emit_box" = "lang/src/compiler/emit/common/json_emit_box.hako"

View File

@ -2,16 +2,22 @@
// Kept tiny and isolated. Linkage names match include/nyrt.h.
#[no_mangle]
pub extern "C" fn nyrt_init() -> i32 { 0 }
pub extern "C" fn nyrt_init() -> i32 {
0
}
#[no_mangle]
pub extern "C" fn nyrt_teardown() { }
pub extern "C" fn nyrt_teardown() {}
#[no_mangle]
pub extern "C" fn nyrt_load_mir_json(_json_text: *const ::std::os::raw::c_char) -> u64 { 1 }
pub extern "C" fn nyrt_load_mir_json(_json_text: *const ::std::os::raw::c_char) -> u64 {
1
}
#[no_mangle]
pub extern "C" fn nyrt_exec_main(_module_handle: u64) -> i32 { 0 }
pub extern "C" fn nyrt_exec_main(_module_handle: u64) -> i32 {
0
}
#[no_mangle]
pub extern "C" fn nyrt_hostcall(
@ -20,7 +26,9 @@ pub extern "C" fn nyrt_hostcall(
_payload_json: *const ::std::os::raw::c_char,
_out_buf: *mut ::std::os::raw::c_char,
_out_buf_len: u32,
) -> i32 { 0 }
) -> i32 {
0
}
#[cfg(test)]
mod tests {

View File

@ -10,8 +10,8 @@ use std::collections::HashMap;
use std::fmt;
mod span;
pub use span::Span;
mod utils;
mod nodes;
mod utils;
pub use nodes::*;
// Span は src/ast/span.rs へ分離re-export で後方互換維持)
@ -573,7 +573,6 @@ pub enum ASTNode {
/// meフィールドアクセス: me.field
MeField { field: String, span: Span },
/// ローカル変数宣言: local x, y, z
Local {
variables: Vec<String>,
@ -584,10 +583,7 @@ pub enum ASTNode {
/// ScopeBoxオプション: 診断/マクロ可視性のためのno-opスコープ。
/// 正規化で注入され、MIRビルダがブロックとして処理意味不変
ScopeBox {
body: Vec<ASTNode>,
span: Span,
},
ScopeBox { body: Vec<ASTNode>, span: Span },
/// Outbox変数宣言: outbox x, y, z (static関数内専用)
Outbox {

View File

@ -22,7 +22,15 @@ impl TryFrom<ASTNode> for AssignStmt {
type Error = ASTNode;
fn try_from(node: ASTNode) -> Result<Self, Self::Error> {
match node {
ASTNode::Assignment { target, value, span } => Ok(AssignStmt { target, value, span }),
ASTNode::Assignment {
target,
value,
span,
} => Ok(AssignStmt {
target,
value,
span,
}),
other => Err(other),
}
}
@ -30,7 +38,11 @@ impl TryFrom<ASTNode> for AssignStmt {
impl From<AssignStmt> for ASTNode {
fn from(s: AssignStmt) -> Self {
ASTNode::Assignment { target: s.target, value: s.value, span: s.span }
ASTNode::Assignment {
target: s.target,
value: s.value,
span: s.span,
}
}
}
@ -52,7 +64,10 @@ impl TryFrom<ASTNode> for ReturnStmt {
impl From<ReturnStmt> for ASTNode {
fn from(s: ReturnStmt) -> Self {
ASTNode::Return { value: s.value, span: s.span }
ASTNode::Return {
value: s.value,
span: s.span,
}
}
}
@ -68,7 +83,17 @@ impl TryFrom<ASTNode> for IfStmt {
type Error = ASTNode;
fn try_from(node: ASTNode) -> Result<Self, Self::Error> {
match node {
ASTNode::If { condition, then_body, else_body, span } => Ok(IfStmt { condition, then_body, else_body, span }),
ASTNode::If {
condition,
then_body,
else_body,
span,
} => Ok(IfStmt {
condition,
then_body,
else_body,
span,
}),
other => Err(other),
}
}
@ -76,7 +101,12 @@ impl TryFrom<ASTNode> for IfStmt {
impl From<IfStmt> for ASTNode {
fn from(s: IfStmt) -> Self {
ASTNode::If { condition: s.condition, then_body: s.then_body, else_body: s.else_body, span: s.span }
ASTNode::If {
condition: s.condition,
then_body: s.then_body,
else_body: s.else_body,
span: s.span,
}
}
}
@ -96,8 +126,17 @@ impl TryFrom<ASTNode> for BinaryExpr {
type Error = ASTNode;
fn try_from(node: ASTNode) -> Result<Self, Self::Error> {
match node {
ASTNode::BinaryOp { operator, left, right, span } =>
Ok(BinaryExpr { operator, left, right, span }),
ASTNode::BinaryOp {
operator,
left,
right,
span,
} => Ok(BinaryExpr {
operator,
left,
right,
span,
}),
other => Err(other),
}
}
@ -105,7 +144,12 @@ impl TryFrom<ASTNode> for BinaryExpr {
impl From<BinaryExpr> for ASTNode {
fn from(e: BinaryExpr) -> Self {
ASTNode::BinaryOp { operator: e.operator, left: e.left, right: e.right, span: e.span }
ASTNode::BinaryOp {
operator: e.operator,
left: e.left,
right: e.right,
span: e.span,
}
}
}
@ -120,7 +164,15 @@ impl TryFrom<ASTNode> for CallExpr {
type Error = ASTNode;
fn try_from(node: ASTNode) -> Result<Self, Self::Error> {
match node {
ASTNode::FunctionCall { name, arguments, span } => Ok(CallExpr { name, arguments, span }),
ASTNode::FunctionCall {
name,
arguments,
span,
} => Ok(CallExpr {
name,
arguments,
span,
}),
other => Err(other),
}
}
@ -128,7 +180,11 @@ impl TryFrom<ASTNode> for CallExpr {
impl From<CallExpr> for ASTNode {
fn from(c: CallExpr) -> Self {
ASTNode::FunctionCall { name: c.name, arguments: c.arguments, span: c.span }
ASTNode::FunctionCall {
name: c.name,
arguments: c.arguments,
span: c.span,
}
}
}
@ -144,8 +200,17 @@ impl TryFrom<ASTNode> for MethodCallExpr {
type Error = ASTNode;
fn try_from(node: ASTNode) -> Result<Self, Self::Error> {
match node {
ASTNode::MethodCall { object, method, arguments, span } =>
Ok(MethodCallExpr { object, method, arguments, span }),
ASTNode::MethodCall {
object,
method,
arguments,
span,
} => Ok(MethodCallExpr {
object,
method,
arguments,
span,
}),
other => Err(other),
}
}
@ -153,7 +218,12 @@ impl TryFrom<ASTNode> for MethodCallExpr {
impl From<MethodCallExpr> for ASTNode {
fn from(m: MethodCallExpr) -> Self {
ASTNode::MethodCall { object: m.object, method: m.method, arguments: m.arguments, span: m.span }
ASTNode::MethodCall {
object: m.object,
method: m.method,
arguments: m.arguments,
span: m.span,
}
}
}
@ -168,8 +238,15 @@ impl TryFrom<ASTNode> for FieldAccessExpr {
type Error = ASTNode;
fn try_from(node: ASTNode) -> Result<Self, Self::Error> {
match node {
ASTNode::FieldAccess { object, field, span } =>
Ok(FieldAccessExpr { object, field, span }),
ASTNode::FieldAccess {
object,
field,
span,
} => Ok(FieldAccessExpr {
object,
field,
span,
}),
other => Err(other),
}
}
@ -177,6 +254,10 @@ impl TryFrom<ASTNode> for FieldAccessExpr {
impl From<FieldAccessExpr> for ASTNode {
fn from(f: FieldAccessExpr) -> Self {
ASTNode::FieldAccess { object: f.object, field: f.field, span: f.span }
ASTNode::FieldAccess {
object: f.object,
field: f.field,
span: f.span,
}
}
}

View File

@ -35,7 +35,7 @@ impl ASTNode {
ASTNode::FromCall { .. } => "FromCall",
ASTNode::ThisField { .. } => "ThisField",
ASTNode::MeField { .. } => "MeField",
ASTNode::Local { .. } => "Local",
ASTNode::Outbox { .. } => "Outbox",
ASTNode::FunctionCall { .. } => "FunctionCall",
@ -277,7 +277,7 @@ impl ASTNode {
ASTNode::MeField { field, .. } => {
format!("MeField({})", field)
}
ASTNode::Local { variables, .. } => {
format!("Local({})", variables.join(", "))
}
@ -368,7 +368,7 @@ impl ASTNode {
ASTNode::FromCall { span, .. } => *span,
ASTNode::ThisField { span, .. } => *span,
ASTNode::MeField { span, .. } => *span,
ASTNode::Local { span, .. } => *span,
ASTNode::Outbox { span, .. } => *span,
ASTNode::FunctionCall { span, .. } => *span,

View File

@ -54,10 +54,20 @@ pub fn eq_vm(a: &VMValue, b: &VMValue) -> bool {
(Float(x), Integer(y)) => *x == (*y as f64),
(BoxRef(ax), BoxRef(by)) => Arc::ptr_eq(ax, by),
// Treat BoxRef(VoidBox/MissingBox) as equal to Void (null) for backward compatibility
(BoxRef(bx), Void) => bx.as_any().downcast_ref::<VoidBox>().is_some()
|| bx.as_any().downcast_ref::<crate::boxes::missing_box::MissingBox>().is_some(),
(Void, BoxRef(bx)) => bx.as_any().downcast_ref::<VoidBox>().is_some()
|| bx.as_any().downcast_ref::<crate::boxes::missing_box::MissingBox>().is_some(),
(BoxRef(bx), Void) => {
bx.as_any().downcast_ref::<VoidBox>().is_some()
|| bx
.as_any()
.downcast_ref::<crate::boxes::missing_box::MissingBox>()
.is_some()
}
(Void, BoxRef(bx)) => {
bx.as_any().downcast_ref::<VoidBox>().is_some()
|| bx
.as_any()
.downcast_ref::<crate::boxes::missing_box::MissingBox>()
.is_some()
}
_ => false,
}
}

View File

@ -20,9 +20,7 @@ impl MirInterpreter {
if self.call_depth > MAX_CALL_DEPTH {
eprintln!(
"[vm-call-depth] exceeded {} in fn={} (depth={})",
MAX_CALL_DEPTH,
func.signature.name,
self.call_depth
MAX_CALL_DEPTH, func.signature.name, self.call_depth
);
self.call_depth = self.call_depth.saturating_sub(1);
return Err(VMError::InvalidInstruction(format!(
@ -31,7 +29,9 @@ impl MirInterpreter {
)));
}
// Phase 1: delegate cross-class reroute / narrow fallbacks to method_router
if let Some(r) = super::method_router::pre_exec_reroute(self, func, arg_vals) { return r; }
if let Some(r) = super::method_router::pre_exec_reroute(self, func, arg_vals) {
return r;
}
let saved_regs = mem::take(&mut self.regs);
let saved_fn = self.cur_fn.clone();
self.cur_fn = Some(func.signature.name.clone());
@ -59,7 +59,11 @@ impl MirInterpreter {
let max_steps: u64 = std::env::var("HAKO_VM_MAX_STEPS")
.ok()
.and_then(|v| v.parse::<u64>().ok())
.or_else(|| std::env::var("NYASH_VM_MAX_STEPS").ok().and_then(|v| v.parse::<u64>().ok()))
.or_else(|| {
std::env::var("NYASH_VM_MAX_STEPS")
.ok()
.and_then(|v| v.parse::<u64>().ok())
})
.unwrap_or(1_000_000);
let mut steps: u64 = 0;
@ -133,9 +137,7 @@ impl MirInterpreter {
if trace_phi {
eprintln!(
"[vm-trace-phi] enter bb={:?} last_pred={:?} preds={:?}",
block.id,
last_pred,
block.predecessors
block.id, last_pred, block.predecessors
);
}
for inst in block.phi_instructions() {
@ -154,7 +156,11 @@ impl MirInterpreter {
Ok(v) => v,
Err(e) => {
// Dev safety valve: tolerate undefined phi inputs by substituting Void
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") {
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED")
.ok()
.as_deref()
== Some("1")
{
if Self::trace_enabled() {
eprintln!("[vm-trace] phi tolerate undefined input {:?} -> Void (err={:?})", val, e);
}
@ -179,17 +185,41 @@ impl MirInterpreter {
// Strict PHI (default ON). Can be disabled with HAKO_VM_PHI_STRICT=0 (or NYASH_VM_PHI_STRICT=0).
let strict = {
let on = |s: &str| {
matches!(s, "1"|"true"|"on"|"yes"|"y"|"t"|"enabled"|"enable")
matches!(
s,
"1" | "true" | "on" | "yes" | "y" | "t" | "enabled" | "enable"
)
};
let off = |s: &str| {
matches!(s, "0"|"false"|"off"|"no"|"n"|"f"|"disabled"|"disable")
matches!(
s,
"0" | "false"
| "off"
| "no"
| "n"
| "f"
| "disabled"
| "disable"
)
};
if let Ok(v) = std::env::var("HAKO_VM_PHI_STRICT") {
let v = v.to_ascii_lowercase();
if off(&v) { false } else if on(&v) { true } else { true }
if off(&v) {
false
} else if on(&v) {
true
} else {
true
}
} else if let Ok(v) = std::env::var("NYASH_VM_PHI_STRICT") {
let v = v.to_ascii_lowercase();
if off(&v) { false } else if on(&v) { true } else { true }
if off(&v) {
false
} else if on(&v) {
true
} else {
true
}
} else {
true
}
@ -213,7 +243,11 @@ impl MirInterpreter {
let v = match self.reg_load(*val0) {
Ok(v) => v,
Err(e) => {
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") {
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED")
.ok()
.as_deref()
== Some("1")
{
if Self::trace_enabled() {
eprintln!("[vm-trace] phi missing pred match {:?}; fallback first input {:?} -> Void (err={:?})", pred, val0, e);
}
@ -243,7 +277,11 @@ impl MirInterpreter {
let v = match self.reg_load(*val) {
Ok(v) => v,
Err(e) => {
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") {
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED")
.ok()
.as_deref()
== Some("1")
{
if Self::trace_enabled() {
eprintln!("[vm-trace] phi tolerate undefined default input {:?} -> Void (err={:?})", val, e);
}
@ -255,10 +293,7 @@ impl MirInterpreter {
};
self.regs.insert(dst_id, v);
if Self::trace_enabled() {
eprintln!(
"[vm-trace] phi dst={:?} take default val={:?}",
dst_id, val
);
eprintln!("[vm-trace] phi dst={:?} take default val={:?}", dst_id, val);
}
}
}

View File

@ -32,7 +32,8 @@ impl MirInterpreter {
if let Some(op_fn) = self.functions.get("AddOperator.apply/2").cloned() {
if !in_guard {
if crate::config::env::operator_box_add_adopt() {
let out = self.exec_function_inner(&op_fn, Some(&[a.clone(), b.clone()]))?;
let out =
self.exec_function_inner(&op_fn, Some(&[a.clone(), b.clone()]))?;
self.regs.insert(dst, out);
return Ok(());
} else {

View File

@ -20,7 +20,9 @@ impl MirInterpreter {
let s_opt: Option<String> = match v0.clone() {
VMValue::String(s) => Some(s),
VMValue::BoxRef(b) => {
if let Some(sb) = b.as_any().downcast_ref::<crate::boxes::basic::StringBox>() {
if let Some(sb) =
b.as_any().downcast_ref::<crate::boxes::basic::StringBox>()
{
Some(sb.value.clone())
} else {
None
@ -29,10 +31,13 @@ impl MirInterpreter {
_ => None,
};
if let Some(s) = s_opt {
let boxed: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::boxes::basic::StringBox::new(s));
let boxed: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::boxes::basic::StringBox::new(s));
let created_vm = VMValue::from_nyash_box(boxed);
self.regs.insert(dst, created_vm);
if Self::box_trace_enabled() { self.box_trace_emit_new(box_type, args.len()); }
if Self::box_trace_enabled() {
self.box_trace_emit_new(box_type, args.len());
}
return Ok(());
}
}
@ -88,7 +93,7 @@ impl MirInterpreter {
Err(e) => {
return Err(self.err_with_context(
&format!("PluginInvoke {}.{}", p.box_type, method),
&format!("{:?}", e)
&format!("{:?}", e),
))
}
}
@ -115,7 +120,10 @@ impl MirInterpreter {
return Ok(());
}
if let VMValue::BoxRef(b) = self.reg_load(box_val)? {
if b.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
if b.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
self.write_string(dst, "null".to_string());
return Ok(());
}
@ -125,7 +133,8 @@ impl MirInterpreter {
if Self::box_trace_enabled() {
let cls = match self.reg_load(box_val).unwrap_or(VMValue::Void) {
VMValue::BoxRef(b) => {
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>()
{
inst.class_name.clone()
} else {
b.type_name().to_string()
@ -167,7 +176,10 @@ impl MirInterpreter {
}
}
VMValue::BoxRef(ref b) => {
if b.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
if b.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
if let Some(val) = super::boxes_void_guards::handle_void_method(method) {
self.write_result(dst, val);
return Ok(());
@ -252,9 +264,12 @@ impl MirInterpreter {
// Determine receiver class name when possible
let recv_cls: Option<String> = match self.reg_load(box_val).ok() {
Some(VMValue::BoxRef(b)) => {
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>()
{
Some(inst.class_name.clone())
} else { None }
} else {
None
}
}
_ => None,
};
@ -262,13 +277,19 @@ impl MirInterpreter {
let prefix = format!("{}.", want);
cands.retain(|k| k.starts_with(&prefix));
}
if cands.len() == 1 { self.functions.get(&cands[0]).cloned() } else { None }
if cands.len() == 1 {
self.functions.get(&cands[0]).cloned()
} else {
None
}
} {
// Build argv: pass receiver as first arg (me)
let recv_vm = self.reg_load(box_val)?;
let mut argv: Vec<VMValue> = Vec::with_capacity(1 + args.len());
argv.push(recv_vm);
for a in args { argv.push(self.reg_load(*a)?); }
for a in args {
argv.push(self.reg_load(*a)?);
}
let ret = self.exec_function_inner(&func, Some(&argv))?;
self.write_result(dst, ret);
return Ok(());
@ -312,5 +333,4 @@ impl MirInterpreter {
) -> Result<bool, VMError> {
super::boxes_array::try_handle_array_box(self, dst, box_val, method, args)
}
}

View File

@ -8,56 +8,58 @@ pub(super) fn try_handle_array_box(
method: &str,
args: &[ValueId],
) -> Result<bool, VMError> {
let recv = this.reg_load(box_val)?;
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
VMValue::BoxRef(b) => b.share_box(),
other => other.to_nyash_box(),
};
if let Some(ab) = recv_box_any
.as_any()
.downcast_ref::<crate::boxes::array::ArrayBox>()
{
match method {
"birth" => {
// No-op constructor init
this.write_void(dst);
return Ok(true);
}
"push" => {
this.validate_args_exact("push", args, 1)?;
let val = this.load_as_box(args[0])?;
let _ = ab.push(val);
this.write_void(dst);
return Ok(true);
}
"pop" => {
if !args.is_empty() { return Err(this.err_invalid("pop expects 0 args")); }
let ret = ab.pop();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"len" | "length" | "size" => {
let ret = ab.length();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"get" => {
this.validate_args_exact("get", args, 1)?;
let idx = this.load_as_box(args[0])?;
let ret = ab.get(idx);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"set" => {
this.validate_args_exact("set", args, 2)?;
let idx = this.load_as_box(args[0])?;
let val = this.load_as_box(args[1])?;
let _ = ab.set(idx, val);
this.write_void(dst);
return Ok(true);
}
_ => {}
let recv = this.reg_load(box_val)?;
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
VMValue::BoxRef(b) => b.share_box(),
other => other.to_nyash_box(),
};
if let Some(ab) = recv_box_any
.as_any()
.downcast_ref::<crate::boxes::array::ArrayBox>()
{
match method {
"birth" => {
// No-op constructor init
this.write_void(dst);
return Ok(true);
}
"push" => {
this.validate_args_exact("push", args, 1)?;
let val = this.load_as_box(args[0])?;
let _ = ab.push(val);
this.write_void(dst);
return Ok(true);
}
"pop" => {
if !args.is_empty() {
return Err(this.err_invalid("pop expects 0 args"));
}
let ret = ab.pop();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"len" | "length" | "size" => {
let ret = ab.length();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"get" => {
this.validate_args_exact("get", args, 1)?;
let idx = this.load_as_box(args[0])?;
let ret = ab.get(idx);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"set" => {
this.validate_args_exact("set", args, 2)?;
let idx = this.load_as_box(args[0])?;
let val = this.load_as_box(args[1])?;
let _ = ab.set(idx, val);
this.write_void(dst);
return Ok(true);
}
_ => {}
}
Ok(false)
}
Ok(false)
}

View File

@ -14,26 +14,39 @@ pub(super) fn try_handle_instance_box(
other => other.to_nyash_box(),
};
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "toString" {
eprintln!("[vm-trace] instance-check recv_box_any.type={} args_len={}", recv_box_any.type_name(), args.len());
eprintln!(
"[vm-trace] instance-check recv_box_any.type={} args_len={}",
recv_box_any.type_name(),
args.len()
);
}
if let Some(inst) = recv_box_any.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = recv_box_any
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
// Development guard: ensure JsonScanner core fields have sensible defaults
if inst.class_name == "JsonScanner" {
// populate missing fields to avoid Void in comparisons inside is_eof/advance
if inst.get_field_ng("position").is_none() {
let _ = inst.set_field_ng("position".to_string(), crate::value::NyashValue::Integer(0));
let _ =
inst.set_field_ng("position".to_string(), crate::value::NyashValue::Integer(0));
}
if inst.get_field_ng("length").is_none() {
let _ = inst.set_field_ng("length".to_string(), crate::value::NyashValue::Integer(0));
let _ =
inst.set_field_ng("length".to_string(), crate::value::NyashValue::Integer(0));
}
if inst.get_field_ng("line").is_none() {
let _ = inst.set_field_ng("line".to_string(), crate::value::NyashValue::Integer(1));
}
if inst.get_field_ng("column").is_none() {
let _ = inst.set_field_ng("column".to_string(), crate::value::NyashValue::Integer(1));
let _ =
inst.set_field_ng("column".to_string(), crate::value::NyashValue::Integer(1));
}
if inst.get_field_ng("text").is_none() {
let _ = inst.set_field_ng("text".to_string(), crate::value::NyashValue::String(String::new()));
let _ = inst.set_field_ng(
"text".to_string(),
crate::value::NyashValue::String(String::new()),
);
}
}
// JsonNodeInstance narrow bridges removed: rely on builder rewrite and instance dispatch
@ -47,11 +60,26 @@ pub(super) fn try_handle_instance_box(
);
}
// Resolve lowered method function: "Class.method/arity"
let primary = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len()));
let primary = format!(
"{}.{}{}",
inst.class_name,
method,
format!("/{}", args.len())
);
// Alternate naming: "ClassInstance.method/arity"
let alt = format!("{}Instance.{}{}", inst.class_name, method, format!("/{}", args.len()));
let alt = format!(
"{}Instance.{}{}",
inst.class_name,
method,
format!("/{}", args.len())
);
// Static method variant that takes 'me' explicitly as first arg: "Class.method/(arity+1)"
let static_variant = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len() + 1));
let static_variant = format!(
"{}.{}{}",
inst.class_name,
method,
format!("/{}", args.len() + 1)
);
// Special-case: toString() → stringify/0 if present
// Prefer base class (strip trailing "Instance") stringify when available.
let (stringify_base, stringify_inst) = if method == "toString" && args.is_empty() {
@ -64,27 +92,43 @@ pub(super) fn try_handle_instance_box(
Some(format!("{}.stringify/0", base_name)),
Some(format!("{}.stringify/0", inst.class_name)),
)
} else { (None, None) };
} else {
(None, None)
};
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!(
"[vm-trace] instance-dispatch class={} method={} arity={} candidates=[{}, {}, {}]",
inst.class_name, method, args.len(), primary, alt, static_variant
inst.class_name,
method,
args.len(),
primary,
alt,
static_variant
);
}
// Prefer stringify for toString() if present (semantic alias). Try instance first, then base.
let func_opt = if let Some(ref sname) = stringify_inst {
this.functions.get(sname).cloned()
} else { None }
.or_else(|| stringify_base.as_ref().and_then(|n| this.functions.get(n).cloned()))
} else {
None
}
.or_else(|| {
stringify_base
.as_ref()
.and_then(|n| this.functions.get(n).cloned())
})
.or_else(|| this.functions.get(&primary).cloned())
.or_else(|| this.functions.get(&alt).cloned())
.or_else(|| this.functions.get(&static_variant).cloned());
if let Some(func) = func_opt {
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] instance-dispatch hit -> {}", func.signature.name);
eprintln!(
"[vm-trace] instance-dispatch hit -> {}",
func.signature.name
);
}
// Build argv: me + args (works for both instance and static(me, ...))
let mut argv: Vec<VMValue> = Vec::with_capacity(1 + args.len());
@ -95,7 +139,9 @@ pub(super) fn try_handle_instance_box(
}
}
argv.push(recv_vm.clone());
for a in args { argv.push(this.reg_load(*a)?); }
for a in args {
argv.push(this.reg_load(*a)?);
}
let ret = this.exec_function_inner(&func, Some(&argv))?;
this.write_result(dst, ret);
return Ok(true);
@ -120,19 +166,28 @@ pub(super) fn try_handle_instance_box(
if filtered.len() == 1 {
let fname = &filtered[0];
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] instance-dispatch fallback (scoped) -> {}", fname);
eprintln!(
"[vm-trace] instance-dispatch fallback (scoped) -> {}",
fname
);
}
if let Some(func) = this.functions.get(fname).cloned() {
let mut argv: Vec<VMValue> = Vec::with_capacity(1 + args.len());
if method == "birth" && crate::config::env::using_is_dev() {
if matches!(recv_vm, VMValue::Void) {
return Err(this.err_invalid("Dev assert: birth(me==Void) is forbidden"));
return Err(
this.err_invalid("Dev assert: birth(me==Void) is forbidden")
);
}
}
argv.push(recv_vm.clone());
for a in args { argv.push(this.reg_load(*a)?); }
for a in args {
argv.push(this.reg_load(*a)?);
}
let ret = this.exec_function_inner(&func, Some(&argv))?;
if let Some(d) = dst { this.regs.insert(d, ret); }
if let Some(d) = dst {
this.regs.insert(d, ret);
}
return Ok(true);
}
} else if filtered.len() > 1 {
@ -143,7 +198,11 @@ pub(super) fn try_handle_instance_box(
} else {
// No same-class candidate: do not dispatch cross-class
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] instance-dispatch no same-class candidate for tail .{}{}", method, format!("/{}", args.len()));
eprintln!(
"[vm-trace] instance-dispatch no same-class candidate for tail .{}{}",
method,
format!("/{}", args.len())
);
}
}
}

View File

@ -8,115 +8,130 @@ pub(super) fn try_handle_map_box(
method: &str,
args: &[ValueId],
) -> Result<bool, VMError> {
let recv = this.reg_load(box_val)?;
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
VMValue::BoxRef(b) => b.share_box(),
other => other.to_nyash_box(),
};
if let Some(mb) = recv_box_any
.as_any()
.downcast_ref::<crate::boxes::map_box::MapBox>()
{
match method {
"birth" => {
// No-op constructor init for MapBox
this.write_void(dst);
return Ok(true);
}
// Field bridge: treat getField/setField as get/set with string key
"getField" => {
this.validate_args_exact("MapBox.getField", args, 1)?;
let k_vm = this.reg_load(args[0])?;
// Field access expects a String key; otherwise return a stable tag.
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(dst, VMValue::String("[map/bad-key] field name must be string".to_string()));
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let ret = mb.get(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"setField" => {
this.validate_args_exact("MapBox.setField", args, 2)?;
let k_vm = this.reg_load(args[0])?;
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(dst, VMValue::String("[map/bad-key] field name must be string".to_string()));
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let v = this.load_as_box(args[1])?;
let ret = mb.set(k, v);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"set" => {
this.validate_args_exact("MapBox.set", args, 2)?;
let k_vm = this.reg_load(args[0])?;
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(dst, VMValue::String("[map/bad-key] key must be string".to_string()));
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let v = this.load_as_box(args[1])?;
let ret = mb.set(k, v);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"get" => {
this.validate_args_exact("MapBox.get", args, 1)?;
let k_vm = this.reg_load(args[0])?;
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(dst, VMValue::String("[map/bad-key] key must be string".to_string()));
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let ret = mb.get(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"has" => {
this.validate_args_exact("MapBox.has", args, 1)?;
let k = this.load_as_box(args[0])?;
let ret = mb.has(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"delete" => {
this.validate_args_exact("MapBox.delete", args, 1)?;
let k_vm = this.reg_load(args[0])?;
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(dst, VMValue::String("[map/bad-key] key must be string".to_string()));
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let ret = mb.delete(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"len" | "length" | "size" => {
let ret = mb.size();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"keys" => {
let ret = mb.keys();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"values" => {
let ret = mb.values();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"clear" => {
// Reset map to empty; return a neutral value
let ret = mb.clear();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
_ => {}
let recv = this.reg_load(box_val)?;
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
VMValue::BoxRef(b) => b.share_box(),
other => other.to_nyash_box(),
};
if let Some(mb) = recv_box_any
.as_any()
.downcast_ref::<crate::boxes::map_box::MapBox>()
{
match method {
"birth" => {
// No-op constructor init for MapBox
this.write_void(dst);
return Ok(true);
}
// Field bridge: treat getField/setField as get/set with string key
"getField" => {
this.validate_args_exact("MapBox.getField", args, 1)?;
let k_vm = this.reg_load(args[0])?;
// Field access expects a String key; otherwise return a stable tag.
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(
dst,
VMValue::String("[map/bad-key] field name must be string".to_string()),
);
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let ret = mb.get(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"setField" => {
this.validate_args_exact("MapBox.setField", args, 2)?;
let k_vm = this.reg_load(args[0])?;
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(
dst,
VMValue::String("[map/bad-key] field name must be string".to_string()),
);
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let v = this.load_as_box(args[1])?;
let ret = mb.set(k, v);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"set" => {
this.validate_args_exact("MapBox.set", args, 2)?;
let k_vm = this.reg_load(args[0])?;
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(
dst,
VMValue::String("[map/bad-key] key must be string".to_string()),
);
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let v = this.load_as_box(args[1])?;
let ret = mb.set(k, v);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"get" => {
this.validate_args_exact("MapBox.get", args, 1)?;
let k_vm = this.reg_load(args[0])?;
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(
dst,
VMValue::String("[map/bad-key] key must be string".to_string()),
);
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let ret = mb.get(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"has" => {
this.validate_args_exact("MapBox.has", args, 1)?;
let k = this.load_as_box(args[0])?;
let ret = mb.has(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"delete" => {
this.validate_args_exact("MapBox.delete", args, 1)?;
let k_vm = this.reg_load(args[0])?;
if !matches!(k_vm, VMValue::String(_)) {
this.write_result(
dst,
VMValue::String("[map/bad-key] key must be string".to_string()),
);
return Ok(true);
}
let k = this.load_as_box(args[0])?;
let ret = mb.delete(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"len" | "length" | "size" => {
let ret = mb.size();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"keys" => {
let ret = mb.keys();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"values" => {
let ret = mb.values();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"clear" => {
// Reset map to empty; return a neutral value
let ret = mb.clear();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
_ => {}
}
Ok(false)
}
Ok(false)
}

View File

@ -9,8 +9,8 @@ pub(super) fn try_handle_object_fields(
) -> Result<bool, VMError> {
// Local helpers to bridge NyashValue <-> VMValue for InstanceBox fields
fn vm_to_nv(v: &VMValue) -> crate::value::NyashValue {
use crate::value::NyashValue as NV;
use super::VMValue as VV;
use crate::value::NyashValue as NV;
match v {
VV::Integer(i) => NV::Integer(*i),
VV::Float(f) => NV::Float(*f),
@ -18,12 +18,12 @@ pub(super) fn try_handle_object_fields(
VV::String(s) => NV::String(s.clone()),
VV::Void => NV::Void,
VV::Future(_) => NV::Void, // not expected in fields
VV::BoxRef(_) => NV::Void, // store minimal; complex object fields are not required here
VV::BoxRef(_) => NV::Void, // store minimal; complex object fields are not required here
}
}
fn nv_to_vm(v: &crate::value::NyashValue) -> VMValue {
use crate::value::NyashValue as NV;
use super::VMValue as VV;
use crate::value::NyashValue as NV;
match v {
NV::Integer(i) => VV::Integer(*i),
NV::Float(f) => VV::Float(*f),
@ -48,7 +48,8 @@ pub(super) fn try_handle_object_fields(
// Create a temporary value to hold the singleton
let temp_id = ValueId(999999999); // Temporary ID for singleton
this.regs.insert(temp_id, VMValue::from_nyash_box(Box::new(instance_clone)));
this.regs
.insert(temp_id, VMValue::from_nyash_box(Box::new(instance_clone)));
temp_id
} else {
box_val
@ -59,18 +60,27 @@ pub(super) fn try_handle_object_fields(
// MapBox special-case: bridge to MapBox.get, with string-only key
if let Ok(VMValue::BoxRef(bref)) = this.reg_load(actual_box_val) {
if bref.as_any().downcast_ref::<crate::boxes::map_box::MapBox>().is_some() {
if bref
.as_any()
.downcast_ref::<crate::boxes::map_box::MapBox>()
.is_some()
{
let key_vm = this.reg_load(args[0])?;
if let VMValue::String(_) = key_vm {
let k = key_vm.to_nyash_box();
let map = bref.share_box();
if let Some(mb) = map.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
if let Some(mb) =
map.as_any().downcast_ref::<crate::boxes::map_box::MapBox>()
{
let ret = mb.get(k);
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
} else {
this.write_string(dst, "[map/bad-key] field name must be string".to_string());
this.write_string(
dst,
"[map/bad-key] field name must be string".to_string(),
);
return Ok(true);
}
}
@ -94,15 +104,22 @@ pub(super) fn try_handle_object_fields(
};
// Prefer InstanceBox internal storage (structural correctness)
if let VMValue::BoxRef(bref) = this.reg_load(actual_box_val)? {
if let Some(inst) = bref.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = bref
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] getField instance class={}", inst.class_name);
}
// Special-case bridge: JsonParser.length -> tokens.length()
if inst.class_name == "JsonParser" && fname == "length" {
if let Some(tokens_shared) = inst.get_field("tokens") {
let tokens_box: Box<dyn crate::box_trait::NyashBox> = tokens_shared.share_box();
if let Some(arr) = tokens_box.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
let tokens_box: Box<dyn crate::box_trait::NyashBox> =
tokens_shared.share_box();
if let Some(arr) = tokens_box
.as_any()
.downcast_ref::<crate::boxes::array::ArrayBox>()
{
let len_box = arr.length();
this.write_result(dst, VMValue::from_nyash_box(len_box));
return Ok(true);
@ -112,7 +129,9 @@ pub(super) fn try_handle_object_fields(
// First: prefer fields_ng (NyashValue) when present
if let Some(nv) = inst.get_field_ng(&fname) {
// Dev trace: JsonToken field get
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && inst.class_name == "JsonToken" {
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
&& inst.class_name == "JsonToken"
{
eprintln!("[vm-trace] JsonToken.getField name={} nv={:?}", fname, nv);
}
// Treat complex Box-like values as "missing" for internal storage so that
@ -129,13 +148,18 @@ pub(super) fn try_handle_object_fields(
);
if !is_missing {
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] getField internal {}.{} -> {:?}", inst.class_name, fname, nv);
eprintln!(
"[vm-trace] getField internal {}.{} -> {:?}",
inst.class_name, fname, nv
);
}
// Special-case: NV::Box should surface as VMValue::BoxRef
if let crate::value::NyashValue::Box(ref arc_m) = nv {
if let Ok(guard) = arc_m.lock() {
let cloned: Box<dyn crate::box_trait::NyashBox> = guard.clone_box();
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(cloned);
let cloned: Box<dyn crate::box_trait::NyashBox> =
guard.clone_box();
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> =
std::sync::Arc::from(cloned);
this.write_result(dst, VMValue::BoxRef(arc));
} else {
this.write_void(dst);
@ -170,8 +194,12 @@ pub(super) fn try_handle_object_fields(
_ => None,
};
if let Some(v) = def {
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] getField default JsonScanner.{} -> {:?}", fname, v);
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
{
eprintln!(
"[vm-trace] getField default JsonScanner.{} -> {:?}",
fname, v
);
}
this.write_result(dst, v);
return Ok(true);
@ -229,10 +257,14 @@ pub(super) fn try_handle_object_fields(
// JsonScanner fields inside JsonScanner.{is_eof,current,advance}, provide
// pragmatic defaults to avoid Void comparisons during bring-up.
if let VMValue::Void = v {
let guard_on = std::env::var("NYASH_VM_SCANNER_DEFAULTS").ok().as_deref() == Some("1");
let guard_on =
std::env::var("NYASH_VM_SCANNER_DEFAULTS").ok().as_deref() == Some("1");
let fn_ctx = this.cur_fn.as_deref().unwrap_or("");
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] getField guard_check ctx={} guard_on={} name={}", fn_ctx, guard_on, fname);
eprintln!(
"[vm-trace] getField guard_check ctx={} guard_on={} name={}",
fn_ctx, guard_on, fname
);
}
if guard_on {
let fn_ctx = this.cur_fn.as_deref().unwrap_or("");
@ -243,7 +275,10 @@ pub(super) fn try_handle_object_fields(
if is_scanner_ctx {
// Try class-aware default first
if let Ok(VMValue::BoxRef(bref2)) = this.reg_load(actual_box_val) {
if let Some(inst2) = bref2.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst2) = bref2
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
if inst2.class_name == "JsonScanner" {
let fallback = match fname.as_str() {
"position" | "length" => Some(VMValue::Integer(0)),
@ -252,8 +287,13 @@ pub(super) fn try_handle_object_fields(
_ => None,
};
if let Some(val) = fallback {
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] getField final_default {} -> {:?}", fname, val);
if std::env::var("NYASH_VM_TRACE").ok().as_deref()
== Some("1")
{
eprintln!(
"[vm-trace] getField final_default {} -> {:?}",
fname, val
);
}
v = val;
}
@ -280,7 +320,11 @@ pub(super) fn try_handle_object_fields(
}
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
if let VMValue::BoxRef(b) = &v {
eprintln!("[vm-trace] getField legacy {} -> BoxRef({})", fname, b.type_name());
eprintln!(
"[vm-trace] getField legacy {} -> BoxRef({})",
fname,
b.type_name()
);
} else {
eprintln!("[vm-trace] getField legacy {} -> {:?}", fname, v);
}
@ -299,9 +343,13 @@ pub(super) fn try_handle_object_fields(
// class name unknown here; use receiver type name if possible
let cls = match this.reg_load(actual_box_val).unwrap_or(VMValue::Void) {
VMValue::BoxRef(b) => {
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) =
b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>()
{
inst.class_name.clone()
} else { b.type_name().to_string() }
} else {
b.type_name().to_string()
}
}
_ => "<unknown>".to_string(),
};
@ -322,7 +370,8 @@ pub(super) fn try_handle_object_fields(
// Create a temporary value to hold the singleton
let temp_id = ValueId(999999998); // Temporary ID for singleton (different from getField)
this.regs.insert(temp_id, VMValue::from_nyash_box(Box::new(instance_clone)));
this.regs
.insert(temp_id, VMValue::from_nyash_box(Box::new(instance_clone)));
temp_id
} else {
box_val
@ -333,19 +382,28 @@ pub(super) fn try_handle_object_fields(
// MapBox special-case: bridge to MapBox.set, with string-only key
if let Ok(VMValue::BoxRef(bref)) = this.reg_load(actual_box_val) {
if bref.as_any().downcast_ref::<crate::boxes::map_box::MapBox>().is_some() {
if bref
.as_any()
.downcast_ref::<crate::boxes::map_box::MapBox>()
.is_some()
{
let key_vm = this.reg_load(args[0])?;
if let VMValue::String(_) = key_vm {
let k = key_vm.to_nyash_box();
let v = this.reg_load(args[1])?.to_nyash_box();
let map = bref.share_box();
if let Some(mb) = map.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
if let Some(mb) =
map.as_any().downcast_ref::<crate::boxes::map_box::MapBox>()
{
let _ = mb.set(k, v);
this.write_void(dst);
return Ok(true);
}
} else {
this.write_string(dst, "[map/bad-key] field name must be string".to_string());
this.write_string(
dst,
"[map/bad-key] field name must be string".to_string(),
);
return Ok(true);
}
}
@ -358,9 +416,15 @@ pub(super) fn try_handle_object_fields(
// Dev trace: JsonToken field set
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
if let VMValue::BoxRef(bref) = this.reg_load(actual_box_val)? {
if let Some(inst) = bref.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = bref
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
if inst.class_name == "JsonToken" {
eprintln!("[vm-trace] JsonToken.setField name={} vmval={:?}", fname, valv);
eprintln!(
"[vm-trace] JsonToken.setField name={} vmval={:?}",
fname, valv
);
}
}
}
@ -377,9 +441,13 @@ pub(super) fn try_handle_object_fields(
};
let cls = match this.reg_load(actual_box_val).unwrap_or(VMValue::Void) {
VMValue::BoxRef(b) => {
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) =
b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>()
{
inst.class_name.clone()
} else { b.type_name().to_string() }
} else {
b.type_name().to_string()
}
}
_ => "<unknown>".to_string(),
};
@ -387,28 +455,52 @@ pub(super) fn try_handle_object_fields(
}
// Prefer InstanceBox internal storage
if let VMValue::BoxRef(bref) = this.reg_load(actual_box_val)? {
if let Some(inst) = bref.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = bref
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
// Primitives → 内部保存
if matches!(valv, VMValue::Integer(_) | VMValue::Float(_) | VMValue::Bool(_) | VMValue::String(_) | VMValue::Void) {
if matches!(
valv,
VMValue::Integer(_)
| VMValue::Float(_)
| VMValue::Bool(_)
| VMValue::String(_)
| VMValue::Void
) {
let _ = inst.set_field_ng(fname.clone(), vm_to_nv(&valv));
return Ok(true);
}
// BoxRef のうち、Integer/Float/Bool/String はプリミティブに剥がして内部保存
if let VMValue::BoxRef(bx) = &valv {
if let Some(ib) = bx.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Integer(ib.value));
if let Some(ib) = bx.as_any().downcast_ref::<crate::box_trait::IntegerBox>()
{
let _ = inst.set_field_ng(
fname.clone(),
crate::value::NyashValue::Integer(ib.value),
);
return Ok(true);
}
if let Some(fb) = bx.as_any().downcast_ref::<crate::boxes::FloatBox>() {
let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Float(fb.value));
let _ = inst.set_field_ng(
fname.clone(),
crate::value::NyashValue::Float(fb.value),
);
return Ok(true);
}
if let Some(bb) = bx.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Bool(bb.value));
let _ = inst.set_field_ng(
fname.clone(),
crate::value::NyashValue::Bool(bb.value),
);
return Ok(true);
}
if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::String(sb.value.clone()));
if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
{
let _ = inst.set_field_ng(
fname.clone(),
crate::value::NyashValue::String(sb.value.clone()),
);
return Ok(true);
}
// For complex Box values (InstanceBox/MapBox/ArrayBox...), store into
@ -419,10 +511,7 @@ pub(super) fn try_handle_object_fields(
}
}
let key = this.object_key_for(actual_box_val);
this.obj_fields
.entry(key)
.or_default()
.insert(fname, valv);
this.obj_fields.entry(key).or_default().insert(fname, valv);
Ok(true)
}
_ => Ok(false),

View File

@ -52,7 +52,7 @@ pub(super) fn invoke_plugin_box(
}
Err(e) => Err(this.err_with_context(
&format!("BoxCall {}.{}", p.box_type, method),
&format!("{:?}", e)
&format!("{:?}", e),
)),
}
} else if recv_box.type_name() == "StringBox" {
@ -99,10 +99,7 @@ pub(super) fn invoke_plugin_box(
if let Some(arg_id) = args.get(0) {
let ch = this.reg_load(*arg_id)?.to_string();
let c = ch.chars().next().unwrap_or('\0');
let is_alpha =
('A'..='Z').contains(&c) ||
('a'..='z').contains(&c) ||
c == '_';
let is_alpha = ('A'..='Z').contains(&c) || ('a'..='z').contains(&c) || c == '_';
this.write_result(dst, VMValue::Bool(is_alpha));
Ok(())
} else {
@ -115,14 +112,21 @@ pub(super) fn invoke_plugin_box(
// Special-case: minimal runtime fallback for common InstanceBox methods when
// lowered functions are not available (dev robustness). Keeps behavior stable
// without changing semantics in the normal path.
if let Some(inst) = recv_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = recv_box
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
// Generic current() fallback: if object has integer 'position' and string 'text',
// return one character at that position (or empty at EOF). This covers JsonScanner
// and compatible scanners without relying on class name.
if method == "current" && args.is_empty() {
if let Some(crate::value::NyashValue::Integer(pos)) = inst.get_field_ng("position") {
if let Some(crate::value::NyashValue::String(text)) = inst.get_field_ng("text") {
let s = if pos < 0 || (pos as usize) >= text.len() { String::new() } else {
if let Some(crate::value::NyashValue::Integer(pos)) = inst.get_field_ng("position")
{
if let Some(crate::value::NyashValue::String(text)) = inst.get_field_ng("text")
{
let s = if pos < 0 || (pos as usize) >= text.len() {
String::new()
} else {
let bytes = text.as_bytes();
let i = pos as usize;
let j = (i + 1).min(bytes.len());
@ -137,7 +141,11 @@ pub(super) fn invoke_plugin_box(
// Generic toString fallback for any non-plugin box
if method == "toString" {
// Map VoidBox.toString → "null" for JSON-friendly semantics
let s = if recv_box.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
let s = if recv_box
.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
"null".to_string()
} else {
recv_box.to_string_box().value
@ -148,7 +156,10 @@ pub(super) fn invoke_plugin_box(
// Minimal runtime fallback for common InstanceBox.is_eof when lowered function is not present.
// This avoids cross-class leaks and hard errors in union-like flows.
if method == "is_eof" && args.is_empty() {
if let Some(inst) = recv_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = recv_box
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
if inst.class_name == "JsonToken" {
let is = match inst.get_field_ng("type") {
Some(crate::value::NyashValue::String(ref s)) => s == "EOF",
@ -158,8 +169,14 @@ pub(super) fn invoke_plugin_box(
return Ok(());
}
if inst.class_name == "JsonScanner" {
let pos = match inst.get_field_ng("position") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 };
let len = match inst.get_field_ng("length") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 };
let pos = match inst.get_field_ng("position") {
Some(crate::value::NyashValue::Integer(i)) => i,
_ => 0,
};
let len = match inst.get_field_ng("length") {
Some(crate::value::NyashValue::Integer(i)) => i,
_ => 0,
};
let is = pos >= len;
this.write_result(dst, VMValue::Bool(is));
return Ok(());
@ -167,7 +184,10 @@ pub(super) fn invoke_plugin_box(
}
}
// Dynamic fallback for user-defined InstanceBox: dispatch to lowered function "Class.method/Arity"
if let Some(inst) = recv_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = recv_box
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
let class_name = inst.class_name.clone();
let arity = args.len(); // function name arity excludes 'me'
let fname = format!("{}.{}{}", class_name, method, format!("/{}", arity));
@ -190,10 +210,10 @@ pub(super) fn invoke_plugin_box(
this.write_string(dst, String::new());
return Ok(());
}
// VoidBox graceful handling for common container-like methods
// Treat null.receiver.* as safe no-ops that return null/0 where appropriate
if recv_box.type_name() == "VoidBox" {
match method {
// VoidBox graceful handling for common container-like methods
// Treat null.receiver.* as safe no-ops that return null/0 where appropriate
if recv_box.type_name() == "VoidBox" {
match method {
"object_get" | "array_get" | "toString" => {
this.write_void(dst);
return Ok(());

View File

@ -7,207 +7,231 @@ pub(super) fn try_handle_string_box(
method: &str,
args: &[ValueId],
) -> Result<bool, VMError> {
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] try_handle_string_box(method={})", method);
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] try_handle_string_box(method={})", method);
}
let recv = this.reg_load(box_val)?;
// Ultra-fast path: raw VM string receiver for length/size (no boxing at all)
if (method == "length" || method == "size")
&& std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1")
{
if let VMValue::String(ref raw) = recv {
let use_cp = std::env::var("NYASH_STR_CP").ok().as_deref() == Some("1");
let n = if use_cp {
raw.chars().count() as i64
} else {
raw.len() as i64
};
this.write_result(dst, VMValue::Integer(n));
return Ok(true);
}
let recv = this.reg_load(box_val)?;
// Ultra-fast path: raw VM string receiver for length/size (no boxing at all)
if (method == "length" || method == "size")
&& std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1")
{
if let VMValue::String(ref raw) = recv {
}
// Handle ONLY when the receiver is actually a string.
// Do NOT coerce arbitrary boxes to StringBox (e.g., ArrayBox.length()).
let sb_norm_opt: Option<crate::box_trait::StringBox> = match recv.clone() {
VMValue::String(s) => Some(crate::box_trait::StringBox::new(s)),
VMValue::BoxRef(b) => {
if b.as_any()
.downcast_ref::<crate::box_trait::StringBox>()
.is_some()
{
Some(b.to_string_box())
} else {
None
}
}
_ => None,
};
let Some(sb_norm) = sb_norm_opt else {
return Ok(false);
};
// Only handle known string methods here (receiver is confirmed string)
match method {
"length" | "size" => {
// Bench/profile fast path: return VMValue::Integer directly (avoid boxing overhead)
if std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1") {
let use_cp = std::env::var("NYASH_STR_CP").ok().as_deref() == Some("1");
let n = if use_cp { raw.chars().count() as i64 } else { raw.len() as i64 };
let n = if use_cp {
sb_norm.value.chars().count() as i64
} else {
sb_norm.value.len() as i64
};
this.write_result(dst, VMValue::Integer(n));
return Ok(true);
}
let ret = sb_norm.length();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
// Handle ONLY when the receiver is actually a string.
// Do NOT coerce arbitrary boxes to StringBox (e.g., ArrayBox.length()).
let sb_norm_opt: Option<crate::box_trait::StringBox> = match recv.clone() {
VMValue::String(s) => Some(crate::box_trait::StringBox::new(s)),
VMValue::BoxRef(b) => {
if b.as_any().downcast_ref::<crate::box_trait::StringBox>().is_some() {
Some(b.to_string_box())
} else {
None
}
}
_ => None,
};
let Some(sb_norm) = sb_norm_opt else { return Ok(false) };
// Only handle known string methods here (receiver is confirmed string)
match method {
"length" | "size" => {
// Bench/profile fast path: return VMValue::Integer directly (avoid boxing overhead)
if std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1") {
let use_cp = std::env::var("NYASH_STR_CP").ok().as_deref() == Some("1");
let n = if use_cp { sb_norm.value.chars().count() as i64 } else { sb_norm.value.len() as i64 };
this.write_result(dst, VMValue::Integer(n));
return Ok(true);
}
let ret = sb_norm.length();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"replace" => {
// replace(old, new) -> string with first occurrence replaced (Rust replace = all; match Core minimal
this.validate_args_exact("replace", args, 2)?;
let old_s = this.reg_load(args[0])?.to_string();
let new_s = this.reg_load(args[1])?.to_string();
// Core policy: replace only the first occurrence
let out = if let Some(pos) = sb_norm.value.find(&old_s) {
let mut s = String::with_capacity(sb_norm.value.len() + new_s.len());
s.push_str(&sb_norm.value[..pos]);
s.push_str(&new_s);
s.push_str(&sb_norm.value[pos + old_s.len()..]);
s
} else {
sb_norm.value.clone()
};
this.write_result(dst, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(out))));
return Ok(true);
}
"trim" => {
let ret = sb_norm.trim();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"indexOf" => {
// Support both 1-arg indexOf(search) and 2-arg indexOf(search, fromIndex)
let (needle, from_index) = match args.len() {
1 => {
// indexOf(search) - search from beginning
let n = this.reg_load(args[0])?.to_string();
(n, 0)
}
2 => {
// indexOf(search, fromIndex) - search from specified position
let n = this.reg_load(args[0])?.to_string();
let from = this.reg_load(args[1])?.as_integer().unwrap_or(0);
(n, from.max(0) as usize)
}
_ => {
return Err(this.err_invalid(
"indexOf expects 1 or 2 args (search [, fromIndex])"
));
}
};
// Search for needle starting from from_index
let search_str = if from_index >= sb_norm.value.len() {
""
} else {
&sb_norm.value[from_index..]
};
let idx = search_str.find(&needle)
.map(|i| (from_index + i) as i64)
.unwrap_or(-1);
this.write_result(dst, VMValue::Integer(idx));
return Ok(true);
}
"contains" => {
// contains(search) -> boolean (true if found, false otherwise)
// Implemented as indexOf(search) >= 0
this.validate_args_exact("contains", args, 1)?;
let needle = this.reg_load(args[0])?.to_string();
let found = sb_norm.value.contains(&needle);
this.write_result(dst, VMValue::Bool(found));
return Ok(true);
}
"lastIndexOf" => {
// lastIndexOf(substr) -> last index or -1
this.validate_args_exact("lastIndexOf", args, 1)?;
let needle = this.reg_load(args[0])?.to_string();
let idx = sb_norm.value.rfind(&needle).map(|i| i as i64).unwrap_or(-1);
this.write_result(dst, VMValue::Integer(idx));
return Ok(true);
}
"stringify" => {
// JSON-style stringify for strings: quote and escape common characters
let mut quoted = String::with_capacity(sb_norm.value.len() + 2);
quoted.push('"');
for ch in sb_norm.value.chars() {
match ch {
'"' => quoted.push_str("\\\""),
'\\' => quoted.push_str("\\\\"),
'\n' => quoted.push_str("\\n"),
'\r' => quoted.push_str("\\r"),
'\t' => quoted.push_str("\\t"),
c if c.is_control() => quoted.push(' '),
c => quoted.push(c),
}
}
quoted.push('"');
this.write_result(dst, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(quoted))));
return Ok(true);
}
"substring" => {
// Support both 1-arg (start to end) and 2-arg (start, end) forms
let (s_idx, e_idx) = match args.len() {
1 => {
// substring(start) - from start to end of string
let s = this.reg_load(args[0])?.as_integer().unwrap_or(0);
let len = sb_norm.value.chars().count() as i64;
(s, len)
}
2 => {
// substring(start, end) - half-open interval [start, end)
let s = this.reg_load(args[0])?.as_integer().unwrap_or(0);
let e = this.reg_load(args[1])?.as_integer().unwrap_or(0);
(s, e)
}
_ => {
return Err(this.err_invalid(
"substring expects 1 or 2 args (start [, end])"
));
}
};
let len = sb_norm.value.chars().count() as i64;
let start = s_idx.max(0).min(len) as usize;
let end = e_idx.max(start as i64).min(len) as usize;
let chars: Vec<char> = sb_norm.value.chars().collect();
let sub: String = chars[start..end].iter().collect();
this.write_result(dst, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(sub))));
return Ok(true);
}
"concat" => {
this.validate_args_exact("concat", args, 1)?;
let rhs = this.reg_load(args[0])?;
let new_s = format!("{}{}", sb_norm.value, rhs.to_string());
this.write_result(dst, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(new_s))));
return Ok(true);
}
"is_digit_char" => {
// Accept either 0-arg (use first char of receiver) or 1-arg (string/char to test)
let ch_opt = if args.is_empty() {
sb_norm.value.chars().next()
} else if args.len() == 1 {
let s = this.reg_load(args[0])?.to_string();
s.chars().next()
} else {
return Err(this.err_invalid("is_digit_char expects 0 or 1 arg"));
};
let is_digit = ch_opt.map(|c| c.is_ascii_digit()).unwrap_or(false);
this.write_result(dst, VMValue::Bool(is_digit));
return Ok(true);
}
"is_hex_digit_char" => {
let ch_opt = if args.is_empty() {
sb_norm.value.chars().next()
} else if args.len() == 1 {
let s = this.reg_load(args[0])?.to_string();
s.chars().next()
} else {
return Err(this.err_invalid("is_hex_digit_char expects 0 or 1 arg"));
};
let is_hex = ch_opt.map(|c| c.is_ascii_hexdigit()).unwrap_or(false);
this.write_result(dst, VMValue::Bool(is_hex));
return Ok(true);
}
_ => {}
"replace" => {
// replace(old, new) -> string with first occurrence replaced (Rust replace = all; match Core minimal
this.validate_args_exact("replace", args, 2)?;
let old_s = this.reg_load(args[0])?.to_string();
let new_s = this.reg_load(args[1])?.to_string();
// Core policy: replace only the first occurrence
let out = if let Some(pos) = sb_norm.value.find(&old_s) {
let mut s = String::with_capacity(sb_norm.value.len() + new_s.len());
s.push_str(&sb_norm.value[..pos]);
s.push_str(&new_s);
s.push_str(&sb_norm.value[pos + old_s.len()..]);
s
} else {
sb_norm.value.clone()
};
this.write_result(
dst,
VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(out))),
);
return Ok(true);
}
Ok(false)
"trim" => {
let ret = sb_norm.trim();
this.write_result(dst, VMValue::from_nyash_box(ret));
return Ok(true);
}
"indexOf" => {
// Support both 1-arg indexOf(search) and 2-arg indexOf(search, fromIndex)
let (needle, from_index) = match args.len() {
1 => {
// indexOf(search) - search from beginning
let n = this.reg_load(args[0])?.to_string();
(n, 0)
}
2 => {
// indexOf(search, fromIndex) - search from specified position
let n = this.reg_load(args[0])?.to_string();
let from = this.reg_load(args[1])?.as_integer().unwrap_or(0);
(n, from.max(0) as usize)
}
_ => {
return Err(
this.err_invalid("indexOf expects 1 or 2 args (search [, fromIndex])")
);
}
};
// Search for needle starting from from_index
let search_str = if from_index >= sb_norm.value.len() {
""
} else {
&sb_norm.value[from_index..]
};
let idx = search_str
.find(&needle)
.map(|i| (from_index + i) as i64)
.unwrap_or(-1);
this.write_result(dst, VMValue::Integer(idx));
return Ok(true);
}
"contains" => {
// contains(search) -> boolean (true if found, false otherwise)
// Implemented as indexOf(search) >= 0
this.validate_args_exact("contains", args, 1)?;
let needle = this.reg_load(args[0])?.to_string();
let found = sb_norm.value.contains(&needle);
this.write_result(dst, VMValue::Bool(found));
return Ok(true);
}
"lastIndexOf" => {
// lastIndexOf(substr) -> last index or -1
this.validate_args_exact("lastIndexOf", args, 1)?;
let needle = this.reg_load(args[0])?.to_string();
let idx = sb_norm.value.rfind(&needle).map(|i| i as i64).unwrap_or(-1);
this.write_result(dst, VMValue::Integer(idx));
return Ok(true);
}
"stringify" => {
// JSON-style stringify for strings: quote and escape common characters
let mut quoted = String::with_capacity(sb_norm.value.len() + 2);
quoted.push('"');
for ch in sb_norm.value.chars() {
match ch {
'"' => quoted.push_str("\\\""),
'\\' => quoted.push_str("\\\\"),
'\n' => quoted.push_str("\\n"),
'\r' => quoted.push_str("\\r"),
'\t' => quoted.push_str("\\t"),
c if c.is_control() => quoted.push(' '),
c => quoted.push(c),
}
}
quoted.push('"');
this.write_result(
dst,
VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(quoted))),
);
return Ok(true);
}
"substring" => {
// Support both 1-arg (start to end) and 2-arg (start, end) forms
let (s_idx, e_idx) = match args.len() {
1 => {
// substring(start) - from start to end of string
let s = this.reg_load(args[0])?.as_integer().unwrap_or(0);
let len = sb_norm.value.chars().count() as i64;
(s, len)
}
2 => {
// substring(start, end) - half-open interval [start, end)
let s = this.reg_load(args[0])?.as_integer().unwrap_or(0);
let e = this.reg_load(args[1])?.as_integer().unwrap_or(0);
(s, e)
}
_ => {
return Err(this.err_invalid("substring expects 1 or 2 args (start [, end])"));
}
};
let len = sb_norm.value.chars().count() as i64;
let start = s_idx.max(0).min(len) as usize;
let end = e_idx.max(start as i64).min(len) as usize;
let chars: Vec<char> = sb_norm.value.chars().collect();
let sub: String = chars[start..end].iter().collect();
this.write_result(
dst,
VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(sub))),
);
return Ok(true);
}
"concat" => {
this.validate_args_exact("concat", args, 1)?;
let rhs = this.reg_load(args[0])?;
let new_s = format!("{}{}", sb_norm.value, rhs.to_string());
this.write_result(
dst,
VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(new_s))),
);
return Ok(true);
}
"is_digit_char" => {
// Accept either 0-arg (use first char of receiver) or 1-arg (string/char to test)
let ch_opt = if args.is_empty() {
sb_norm.value.chars().next()
} else if args.len() == 1 {
let s = this.reg_load(args[0])?.to_string();
s.chars().next()
} else {
return Err(this.err_invalid("is_digit_char expects 0 or 1 arg"));
};
let is_digit = ch_opt.map(|c| c.is_ascii_digit()).unwrap_or(false);
this.write_result(dst, VMValue::Bool(is_digit));
return Ok(true);
}
"is_hex_digit_char" => {
let ch_opt = if args.is_empty() {
sb_norm.value.chars().next()
} else if args.len() == 1 {
let s = this.reg_load(args[0])?.to_string();
s.chars().next()
} else {
return Err(this.err_invalid("is_hex_digit_char expects 0 or 1 arg"));
};
let is_hex = ch_opt.map(|c| c.is_ascii_hexdigit()).unwrap_or(false);
this.write_result(dst, VMValue::Bool(is_hex));
return Ok(true);
}
_ => {}
}
Ok(false)
}

View File

@ -22,9 +22,15 @@ impl MirInterpreter {
match &v {
VMValue::Void => println!("null"),
VMValue::BoxRef(bx) => {
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
if bx
.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
println!("null");
} else if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
} else if let Some(sb) =
bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
{
println!("{}", sb.value);
} else {
println!("{}", v.to_string());
@ -56,8 +62,12 @@ impl MirInterpreter {
};
panic!("{}", msg);
}
"hostbridge.extern_invoke" => Err(self.err_invalid("hostbridge.extern_invoke should be routed via extern_provider_dispatch")),
_ => Err(self.err_with_context("extern function", &format!("Unknown: {}", extern_name))),
"hostbridge.extern_invoke" => Err(self.err_invalid(
"hostbridge.extern_invoke should be routed via extern_provider_dispatch",
)),
_ => {
Err(self.err_with_context("extern function", &format!("Unknown: {}", extern_name)))
}
}
}
}

View File

@ -12,7 +12,9 @@ impl MirInterpreter {
// Module-local/global function: execute by function table if present (use original name)
if let Some(func) = self.functions.get(func_name).cloned() {
let mut argv: Vec<VMValue> = Vec::with_capacity(args.len());
for a in args { argv.push(self.reg_load(*a)?); }
for a in args {
argv.push(self.reg_load(*a)?);
}
return self.exec_function_inner(&func, Some(&argv));
}
@ -60,13 +62,19 @@ impl MirInterpreter {
let s = self.reg_load(*a0)?.to_string();
let opts = crate::host_providers::llvm_codegen::Opts {
out: None,
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from),
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok().or(Some("0".to_string())),
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT")
.ok()
.map(std::path::PathBuf::from),
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL")
.ok()
.or(Some("0".to_string())),
timeout_ms: None,
};
match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts) {
Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())),
Err(e) => Err(self.err_with_context("env.codegen.emit_object", &e.to_string())),
Err(e) => {
Err(self.err_with_context("env.codegen.emit_object", &e.to_string()))
}
}
} else {
Err(self.err_invalid("env.codegen.emit_object expects 1 arg"))
@ -74,32 +82,51 @@ impl MirInterpreter {
}
"env.codegen.link_object" | "env.codegen.link_object/3" => {
// C-API route only; args[2] is expected to be an ArrayBox [obj_path, exe_out?]
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
.ok()
.as_deref()
!= Some("1")
{
return Err(self.err_invalid("env.codegen.link_object: C-API route disabled"));
}
if args.len() < 3 { return Err(self.err_arg_count("env.codegen.link_object", 3, args.len())); }
if args.len() < 3 {
return Err(self.err_arg_count("env.codegen.link_object", 3, args.len()));
}
let v = self.reg_load(args[2])?;
let (obj_path, exe_out) = match v {
VMValue::BoxRef(b) => {
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
{
let idx0: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(0));
let elem0 = ab.get(idx0).to_string_box().value;
let mut exe: Option<String> = None;
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
let idx1: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(1));
let e1 = ab.get(idx1).to_string_box().value;
if !e1.is_empty() { exe = Some(e1); }
if !e1.is_empty() {
exe = Some(e1);
}
(elem0, exe)
} else { (b.to_string_box().value, None) }
} else {
(b.to_string_box().value, None)
}
}
_ => (v.to_string(), None),
};
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
let obj = std::path::PathBuf::from(obj_path);
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
let exe = exe_out
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(
&obj,
&exe,
extra.as_deref(),
) {
Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())),
Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string()))
Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string())),
}
}
"nyash.builtin.error" => {

View File

@ -22,18 +22,40 @@ impl MirInterpreter {
if let Some(block) = func.blocks.get(&bb) {
let mut last_recv: Option<ValueId> = None;
for inst in &block.instructions {
if let crate::mir::MirInstruction::NewBox { dst, box_type, .. } = inst {
if box_type == box_name { last_recv = Some(*dst); }
if let crate::mir::MirInstruction::NewBox {
dst,
box_type,
..
} = inst
{
if box_type == box_name {
last_recv = Some(*dst);
}
}
}
if let Some(rid) = last_recv {
if let Ok(v) = self.reg_load(rid) { v } else { return Err(e); }
if let Ok(v) = self.reg_load(rid) {
v
} else {
return Err(e);
}
} else {
// Dev fallback (guarded): use args[0] as surrogate receiver if explicitly allowed
let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK").ok().as_deref() == Some("1")
|| std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1");
if tolerate { if let Some(a0) = args.get(0) { self.reg_load(*a0)? } else { return Err(e); } }
else { return Err(e); }
let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK")
.ok()
.as_deref()
== Some("1")
|| std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref()
== Some("1");
if tolerate {
if let Some(a0) = args.get(0) {
self.reg_load(*a0)?
} else {
return Err(e);
}
} else {
return Err(e);
}
}
} else {
return Err(e);
@ -43,10 +65,18 @@ impl MirInterpreter {
}
} else {
// Dev fallback (guarded): use args[0] as surrogate receiver if explicitly allowed
let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK").ok().as_deref() == Some("1")
let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK").ok().as_deref()
== Some("1")
|| std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1");
if tolerate { if let Some(a0) = args.get(0) { self.reg_load(*a0)? } else { return Err(e); } }
else { return Err(e); }
if tolerate {
if let Some(a0) = args.get(0) {
self.reg_load(*a0)?
} else {
return Err(e);
}
} else {
return Err(e);
}
}
}
};
@ -57,7 +87,9 @@ impl MirInterpreter {
// ArrayBox bridge
if let Some(arr) = bx.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
match method {
"birth" => { return Ok(VMValue::Void); }
"birth" => {
return Ok(VMValue::Void);
}
"push" => {
if let Some(a0) = args.get(0) {
let v = self.load_as_box(*a0)?;
@ -162,14 +194,20 @@ impl MirInterpreter {
"substring" => {
let start = if let Some(a0) = args.get(0) {
self.reg_load(*a0)?.as_integer().unwrap_or(0)
} else { 0 };
} else {
0
};
let end = if let Some(a1) = args.get(1) {
self.reg_load(*a1)?.as_integer().unwrap_or(s.len() as i64)
} else { s.len() as i64 };
} else {
s.len() as i64
};
let len = s.len() as i64;
let i0 = start.max(0).min(len) as usize;
let i1 = end.max(0).min(len) as usize;
if i0 > i1 { return Ok(VMValue::String(String::new())); }
if i0 > i1 {
return Ok(VMValue::String(String::new()));
}
// Note: operating on bytes; Nyash strings are UTF8, but tests are ASCII only here
let bytes = s.as_bytes();
let sub = String::from_utf8(bytes[i0..i1].to_vec()).unwrap_or_default();
@ -209,8 +247,7 @@ impl MirInterpreter {
"is_space" => {
if let Some(arg_id) = args.get(0) {
let ch = self.reg_load(*arg_id)?.to_string();
let is_ws =
ch == " " || ch == "\t" || ch == "\n" || ch == "\r";
let is_ws = ch == " " || ch == "\t" || ch == "\n" || ch == "\r";
Ok(VMValue::Bool(is_ws))
} else {
Err(self.err_invalid("is_space requires 1 argument"))
@ -221,10 +258,9 @@ impl MirInterpreter {
if let Some(arg_id) = args.get(0) {
let ch = self.reg_load(*arg_id)?.to_string();
let c = ch.chars().next().unwrap_or('\0');
let is_alpha =
('A'..='Z').contains(&c) ||
('a'..='z').contains(&c) ||
c == '_';
let is_alpha = ('A'..='Z').contains(&c)
|| ('a'..='z').contains(&c)
|| c == '_';
Ok(VMValue::Bool(is_alpha))
} else {
Err(self.err_invalid("is_alpha requires 1 argument"))
@ -234,8 +270,8 @@ impl MirInterpreter {
}
} else if let Some(p) = box_ref
.as_any()
.downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>()
{
.downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>(
) {
let host = crate::runtime::plugin_loader_unified::get_global_plugin_host();
let host = host.read().unwrap();
let argv = self.load_args_as_boxes(args)?;
@ -249,14 +285,17 @@ impl MirInterpreter {
Ok(None) => Ok(VMValue::Void),
Err(e) => Err(self.err_with_context(
&format!("Plugin method {}.{}", p.box_type, method),
&format!("{:?}", e)
&format!("{:?}", e),
)),
}
} else {
Err(self.err_method_not_found(&box_ref.type_name(), method))
}
}
_ => Err(self.err_with_context("method call", &format!("{} not supported on {:?}", method, receiver))),
_ => Err(self.err_with_context(
"method call",
&format!("{} not supported on {:?}", method, receiver),
)),
}
}
}

View File

@ -4,9 +4,9 @@
use super::*;
mod externs;
mod global;
mod method;
mod externs;
// legacy by-name resolver has been removed (Phase 2 complete)
impl MirInterpreter {
@ -19,18 +19,43 @@ impl MirInterpreter {
) -> Result<(), VMError> {
if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") {
match callee {
Some(Callee::Global(n)) => eprintln!("[hb:path] call Callee::Global {} argc={}", n, args.len()),
Some(Callee::Method{ box_name, method, ..}) => eprintln!("[hb:path] call Callee::Method {}.{} argc={}", box_name, method, args.len()),
Some(Callee::Constructor{ box_type }) => eprintln!("[hb:path] call Callee::Constructor {} argc={}", box_type, args.len()),
Some(Callee::Closure{ .. }) => eprintln!("[hb:path] call Callee::Closure argc={}", args.len()),
Some(Callee::Value(_)) => eprintln!("[hb:path] call Callee::Value argc={}", args.len()),
Some(Callee::Extern(n)) => eprintln!("[hb:path] call Callee::Extern {} argc={}", n, args.len()),
None => eprintln!("[hb:path] call Legacy func_id={:?} argc={}", func, args.len()),
Some(Callee::Global(n)) => {
eprintln!("[hb:path] call Callee::Global {} argc={}", n, args.len())
}
Some(Callee::Method {
box_name, method, ..
}) => eprintln!(
"[hb:path] call Callee::Method {}.{} argc={}",
box_name,
method,
args.len()
),
Some(Callee::Constructor { box_type }) => eprintln!(
"[hb:path] call Callee::Constructor {} argc={}",
box_type,
args.len()
),
Some(Callee::Closure { .. }) => {
eprintln!("[hb:path] call Callee::Closure argc={}", args.len())
}
Some(Callee::Value(_)) => {
eprintln!("[hb:path] call Callee::Value argc={}", args.len())
}
Some(Callee::Extern(n)) => {
eprintln!("[hb:path] call Callee::Extern {} argc={}", n, args.len())
}
None => eprintln!(
"[hb:path] call Legacy func_id={:?} argc={}",
func,
args.len()
),
}
}
// SSOT fast-path: route hostbridge.extern_invoke to extern dispatcher regardless of resolution form
if let Some(Callee::Global(func_name)) = callee {
if func_name == "hostbridge.extern_invoke" || func_name.starts_with("hostbridge.extern_invoke/") {
if func_name == "hostbridge.extern_invoke"
|| func_name.starts_with("hostbridge.extern_invoke/")
{
let v = self.execute_extern_function("hostbridge.extern_invoke", args)?;
self.write_result(dst, v);
return Ok(());
@ -44,7 +69,9 @@ impl MirInterpreter {
if let VMValue::String(ref s) = name_val {
if let Some(f) = self.functions.get(s).cloned() {
let mut argv: Vec<VMValue> = Vec::with_capacity(args.len());
for a in args { argv.push(self.reg_load(*a)?); }
for a in args {
argv.push(self.reg_load(*a)?);
}
self.exec_function_inner(&f, Some(&argv))?
} else {
return Err(self.err_with_context("call", &format!(
@ -53,7 +80,10 @@ impl MirInterpreter {
)));
}
} else {
return Err(self.err_with_context("call", "by-name calls unsupported without Callee attachment"));
return Err(self.err_with_context(
"call",
"by-name calls unsupported without Callee attachment",
));
}
};
self.write_result(dst, call_result);
@ -67,10 +97,15 @@ impl MirInterpreter {
) -> Result<VMValue, VMError> {
match callee {
Callee::Global(func_name) => self.execute_global_function(func_name, args),
Callee::Method { box_name, method, receiver, .. } => {
self.execute_method_callee(box_name, method, receiver, args)
Callee::Method {
box_name,
method,
receiver,
..
} => self.execute_method_callee(box_name, method, receiver, args),
Callee::Constructor { box_type } => {
Err(self.err_unsupported(&format!("Constructor calls for {}", box_type)))
}
Callee::Constructor { box_type } => Err(self.err_unsupported(&format!("Constructor calls for {}", box_type))),
Callee::Closure { .. } => Err(self.err_unsupported("Closure creation in VM")),
Callee::Value(func_val_id) => {
let _ = self.reg_load(*func_val_id)?;
@ -79,5 +114,4 @@ impl MirInterpreter {
Callee::Extern(extern_name) => self.execute_extern_function(extern_name, args),
}
}
}

View File

@ -10,8 +10,12 @@ impl MirInterpreter {
let key = format!("{}.{}", target, method);
for pat in flt.split(',') {
let p = pat.trim();
if p.is_empty() { continue; }
if p == method || p == key { return true; }
if p.is_empty() {
continue;
}
if p == method || p == key {
return true;
}
}
return false;
}
@ -23,7 +27,9 @@ impl MirInterpreter {
if let JsonValue::Object(ref mut m) = v {
if !m.contains_key("version") {
m.insert("version".to_string(), JsonValue::from(0));
if let Ok(out) = serde_json::to_string(&v) { return out; }
if let Ok(out) = serde_json::to_string(&v) {
return out;
}
}
}
s.to_string()
@ -64,9 +70,15 @@ impl MirInterpreter {
VMValue::Void => println!("null"),
VMValue::String(s) => println!("{}", s),
VMValue::BoxRef(bx) => {
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
if bx
.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
println!("null");
} else if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
} else if let Some(sb) =
bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
{
println!("{}", sb.value);
} else {
println!("{}", v.to_string());
@ -90,9 +102,15 @@ impl MirInterpreter {
VMValue::Void => eprintln!("[warn] null"),
VMValue::String(s) => eprintln!("[warn] {}", s),
VMValue::BoxRef(bx) => {
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
if bx
.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
eprintln!("[warn] null");
} else if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
} else if let Some(sb) =
bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
{
eprintln!("[warn] {}", sb.value);
} else {
eprintln!("[warn] {}", v.to_string());
@ -116,9 +134,15 @@ impl MirInterpreter {
VMValue::Void => eprintln!("[error] null"),
VMValue::String(s) => eprintln!("[error] {}", s),
VMValue::BoxRef(bx) => {
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
if bx
.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
eprintln!("[error] null");
} else if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
} else if let Some(sb) =
bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
{
eprintln!("[error] {}", sb.value);
} else {
eprintln!("[error] {}", v.to_string());
@ -140,15 +164,29 @@ impl MirInterpreter {
if std::env::var("HAKO_V1_EXTERN_PROVIDER").ok().as_deref() == Some("1") {
return Some(Ok(VMValue::String(String::new())));
}
if args.is_empty() { return Some(Err(ErrorBuilder::arg_count_mismatch("env.mirbuilder.emit", 1, args.len()))); }
let program_json = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
if args.is_empty() {
return Some(Err(ErrorBuilder::arg_count_mismatch(
"env.mirbuilder.emit",
1,
args.len(),
)));
}
let program_json = match self.reg_load(args[0]) {
Ok(v) => v.to_string(),
Err(e) => return Some(Err(e)),
};
// Phase 21.8: Read imports from environment variable if present
let imports = if let Ok(imports_json) = std::env::var("HAKO_MIRBUILDER_IMPORTS") {
match serde_json::from_str::<std::collections::HashMap<String, String>>(&imports_json) {
match serde_json::from_str::<std::collections::HashMap<String, String>>(
&imports_json,
) {
Ok(map) => map,
Err(e) => {
eprintln!("[mirbuilder/imports] Failed to parse HAKO_MIRBUILDER_IMPORTS: {}", e);
eprintln!(
"[mirbuilder/imports] Failed to parse HAKO_MIRBUILDER_IMPORTS: {}",
e
);
std::collections::HashMap::new()
}
}
@ -156,10 +194,17 @@ impl MirInterpreter {
std::collections::HashMap::new()
};
let res = match crate::host_providers::mir_builder::program_json_to_mir_json_with_imports(&program_json, imports) {
Ok(s) => Ok(VMValue::String(Self::patch_mir_json_version(&s))),
Err(e) => Err(ErrorBuilder::with_context("env.mirbuilder.emit", &e.to_string())),
};
let res =
match crate::host_providers::mir_builder::program_json_to_mir_json_with_imports(
&program_json,
imports,
) {
Ok(s) => Ok(VMValue::String(Self::patch_mir_json_version(&s))),
Err(e) => Err(ErrorBuilder::with_context(
"env.mirbuilder.emit",
&e.to_string(),
)),
};
Some(res)
}
"env.codegen.emit_object" => {
@ -167,55 +212,114 @@ impl MirInterpreter {
if std::env::var("HAKO_V1_EXTERN_PROVIDER").ok().as_deref() == Some("1") {
return Some(Ok(VMValue::String(String::new())));
}
if args.is_empty() { return Some(Err(ErrorBuilder::arg_count_mismatch("env.codegen.emit_object", 1, args.len()))); }
let mir_json_raw = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
if args.is_empty() {
return Some(Err(ErrorBuilder::arg_count_mismatch(
"env.codegen.emit_object",
1,
args.len(),
)));
}
let mir_json_raw = match self.reg_load(args[0]) {
Ok(v) => v.to_string(),
Err(e) => return Some(Err(e)),
};
// Normalize to v1 shape if missing/legacy (prevents harness NoneType errors)
let mir_json = Self::patch_mir_json_version(&mir_json_raw);
let opts = crate::host_providers::llvm_codegen::Opts {
out: None,
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from),
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok().or(Some("0".to_string())),
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT")
.ok()
.map(std::path::PathBuf::from),
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL")
.ok()
.or(Some("0".to_string())),
timeout_ms: None,
};
let res = match crate::host_providers::llvm_codegen::mir_json_to_object(&mir_json, opts) {
let res = match crate::host_providers::llvm_codegen::mir_json_to_object(
&mir_json, opts,
) {
Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())),
Err(e) => Err(ErrorBuilder::with_context("env.codegen.emit_object", &e.to_string())),
Err(e) => Err(ErrorBuilder::with_context(
"env.codegen.emit_object",
&e.to_string(),
)),
};
Some(res)
}
"env.codegen.link_object" => {
// Only supported on C-API route; expect 1 or 2 args: obj_path [, exe_out]
let obj_path = match args.get(0) {
Some(v) => match self.reg_load(*v) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) },
None => return Some(Err(self.err_invalid("env.codegen.link_object expects 1+ args"))),
Some(v) => match self.reg_load(*v) {
Ok(v) => v.to_string(),
Err(e) => return Some(Err(e)),
},
None => {
return Some(Err(
self.err_invalid("env.codegen.link_object expects 1+ args")
))
}
};
let exe_out = match args.get(1) {
Some(v) => Some(match self.reg_load(*v) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }),
Some(v) => Some(match self.reg_load(*v) {
Ok(v) => v.to_string(),
Err(e) => return Some(Err(e)),
}),
None => None,
};
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
// Require C-API toggles
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
return Some(Err(ErrorBuilder::invalid_instruction("env.codegen.link_object: C-API route disabled")));
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
.ok()
.as_deref()
!= Some("1")
{
return Some(Err(ErrorBuilder::invalid_instruction(
"env.codegen.link_object: C-API route disabled",
)));
}
let obj = std::path::PathBuf::from(obj_path);
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
let exe = exe_out
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(
&obj,
&exe,
extra.as_deref(),
) {
Ok(()) => Some(Ok(VMValue::String(exe.to_string_lossy().into_owned()))),
Err(e) => Some(Err(ErrorBuilder::with_context("env.codegen.link_object", &e.to_string()))),
Err(e) => Some(Err(ErrorBuilder::with_context(
"env.codegen.link_object",
&e.to_string(),
))),
}
}
// Environment
"env.get" => {
if args.is_empty() { return Some(Err(ErrorBuilder::arg_count_mismatch("env.get", 1, args.len()))); }
let key = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
if args.is_empty() {
return Some(Err(ErrorBuilder::arg_count_mismatch(
"env.get",
1,
args.len(),
)));
}
let key = match self.reg_load(args[0]) {
Ok(v) => v.to_string(),
Err(e) => return Some(Err(e)),
};
let val = std::env::var(&key).ok();
Some(Ok(match val { Some(s) => VMValue::String(s), None => VMValue::Void }))
Some(Ok(match val {
Some(s) => VMValue::String(s),
None => VMValue::Void,
}))
}
"env.set" => {
if args.len() < 2 {
return Some(Err(ErrorBuilder::arg_count_mismatch("env.set", 2, args.len())));
return Some(Err(ErrorBuilder::arg_count_mismatch(
"env.set",
2,
args.len(),
)));
}
let key = match self.reg_load(args[0]) {
Ok(v) => v.to_string(),
@ -247,24 +351,24 @@ impl MirInterpreter {
}
}
} else {
return Some(Err(self.err_invalid(
"env.box_introspect.kind expects 1 arg",
)));
return Some(Err(
self.err_invalid("env.box_introspect.kind expects 1 arg")
));
}
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
let result = plugin_loader_v2::handle_box_introspect("kind", &collected);
#[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))]
let result: crate::bid::BidResult<Option<Box<dyn crate::box_trait::NyashBox>>> =
Err(crate::bid::BidError::PluginError);
let result: crate::bid::BidResult<
Option<Box<dyn crate::box_trait::NyashBox>>,
> = Err(crate::bid::BidError::PluginError);
match result {
Ok(Some(b)) => Some(Ok(VMValue::BoxRef(Arc::from(b)))),
Ok(None) => Some(Ok(VMValue::Void)),
Err(e) => Some(Err(self.err_with_context(
"env.box_introspect.kind",
&format!("{:?}", e),
))),
Err(e) => Some(Err(
self.err_with_context("env.box_introspect.kind", &format!("{:?}", e))
)),
}
}
"hostbridge.extern_invoke" => {
@ -272,17 +376,30 @@ impl MirInterpreter {
eprintln!("[hb:entry:provider] hostbridge.extern_invoke");
}
if args.len() < 2 {
return Some(Err(self.err_invalid("extern_invoke expects at least 2 args")));
return Some(Err(
self.err_invalid("extern_invoke expects at least 2 args")
));
}
let name = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
let method = match self.reg_load(args[1]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
let name = match self.reg_load(args[0]) {
Ok(v) => v.to_string(),
Err(e) => return Some(Err(e)),
};
let method = match self.reg_load(args[1]) {
Ok(v) => v.to_string(),
Err(e) => return Some(Err(e)),
};
// Extract first payload arg (optional)
let mut first_arg_str: Option<String> = None;
if let Some(a2) = args.get(2) {
let v = match self.reg_load(*a2) { Ok(v) => v, Err(e) => return Some(Err(e)) };
let v = match self.reg_load(*a2) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
match v {
VMValue::BoxRef(b) => {
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
if let Some(ab) =
b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
{
let idx: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(0));
let elem = ab.get(idx);
@ -299,16 +416,27 @@ impl MirInterpreter {
eprintln!("[hb:dispatch:provider] {} {}", name, method);
}
let out = match (name.as_str(), method.as_str()) {
("env.codegen", "link_object") if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") => {
("env.codegen", "link_object")
if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") =>
{
// Trace payload shape before actual handling
if let Some(a2) = args.get(2) {
let v = match self.reg_load(*a2) { Ok(v) => v, Err(_) => VMValue::Void };
let v = match self.reg_load(*a2) {
Ok(v) => v,
Err(_) => VMValue::Void,
};
match &v {
VMValue::BoxRef(b) => {
if b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>().is_some() {
if b.as_any()
.downcast_ref::<crate::boxes::array::ArrayBox>()
.is_some()
{
eprintln!("[hb:provider:args] link_object third=ArrayBox");
} else {
eprintln!("[hb:provider:args] link_object third=BoxRef({})", b.type_name());
eprintln!(
"[hb:provider:args] link_object third=BoxRef({})",
b.type_name()
);
}
}
other => {
@ -327,13 +455,19 @@ impl MirInterpreter {
};
match v {
VMValue::BoxRef(b) => {
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
if let Some(ab) =
b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
{
let idx0: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(0));
let elem0 = ab.get(idx0).to_string_box().value;
let mut exe: Option<String> = None;
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
let idx1: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(1));
let e1 = ab.get(idx1).to_string_box().value;
if !e1.is_empty() { exe = Some(e1); }
if !e1.is_empty() {
exe = Some(e1);
}
(elem0, exe)
} else {
(b.to_string_box().value, None)
@ -342,25 +476,47 @@ impl MirInterpreter {
_ => (v.to_string(), None),
}
} else {
return Some(Err(self.err_invalid("extern_invoke env.codegen.link_object expects args array")));
return Some(Err(self.err_invalid(
"extern_invoke env.codegen.link_object expects args array",
)));
};
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
return Some(Err(ErrorBuilder::invalid_instruction("env.codegen.link_object: C-API route disabled")));
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
.ok()
.as_deref()
!= Some("1")
{
return Some(Err(ErrorBuilder::invalid_instruction(
"env.codegen.link_object: C-API route disabled",
)));
}
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
let obj = std::path::PathBuf::from(objs);
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
let exe = exe_out
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(
&obj,
&exe,
extra.as_deref(),
) {
Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())),
Err(e) => Err(ErrorBuilder::with_context("env.codegen.link_object", &e.to_string()))
Err(e) => Err(ErrorBuilder::with_context(
"env.codegen.link_object",
&e.to_string(),
)),
}
}
("env.mirbuilder", "emit") => {
if let Some(s) = first_arg_str {
// Phase 21.8: Read imports from environment variable if present
let imports = if let Ok(imports_json) = std::env::var("HAKO_MIRBUILDER_IMPORTS") {
match serde_json::from_str::<std::collections::HashMap<String, String>>(&imports_json) {
let imports = if let Ok(imports_json) =
std::env::var("HAKO_MIRBUILDER_IMPORTS")
{
match serde_json::from_str::<
std::collections::HashMap<String, String>,
>(&imports_json)
{
Ok(map) => map,
Err(e) => {
eprintln!("[mirbuilder/imports] Failed to parse HAKO_MIRBUILDER_IMPORTS: {}", e);
@ -383,16 +539,21 @@ impl MirInterpreter {
if let Some(s) = first_arg_str {
let opts = crate::host_providers::llvm_codegen::Opts {
out: None,
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from),
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT")
.ok()
.map(std::path::PathBuf::from),
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok(),
timeout_ms: None,
};
match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts) {
match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts)
{
Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())),
Err(e) => Err(self.err_with_context("env.codegen.emit_object", &e.to_string())),
Err(e) => Err(self
.err_with_context("env.codegen.emit_object", &e.to_string())),
}
} else {
Err(self.err_invalid("extern_invoke env.codegen.emit_object expects 1 arg"))
Err(self
.err_invalid("extern_invoke env.codegen.emit_object expects 1 arg"))
}
}
("env.codegen", "link_object") => {
@ -408,12 +569,18 @@ impl MirInterpreter {
};
match v {
VMValue::BoxRef(b) => {
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
if let Some(ab) =
b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
{
let idx0: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(0));
obj_s = Some(ab.get(idx0).to_string_box().value);
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
let idx1: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(1));
let s1 = ab.get(idx1).to_string_box().value;
if !s1.is_empty() { exe_s = Some(s1); }
if !s1.is_empty() {
exe_s = Some(s1);
}
} else {
obj_s = Some(b.to_string_box().value);
}
@ -426,18 +593,37 @@ impl MirInterpreter {
}
let objs = match obj_s {
Some(s) => s,
None => return Some(Err(self.err_invalid("extern_invoke env.codegen.link_object expects args"))),
None => {
return Some(Err(self.err_invalid(
"extern_invoke env.codegen.link_object expects args",
)))
}
};
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
return Some(Err(ErrorBuilder::invalid_instruction("env.codegen.link_object: C-API route disabled")));
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
.ok()
.as_deref()
!= Some("1")
{
return Some(Err(ErrorBuilder::invalid_instruction(
"env.codegen.link_object: C-API route disabled",
)));
}
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
let obj = std::path::PathBuf::from(objs);
let exe = exe_s.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
let exe = exe_s
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
match crate::host_providers::llvm_codegen::link_object_capi(
&obj,
&exe,
extra.as_deref(),
) {
Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())),
Err(e) => Err(ErrorBuilder::with_context("env.codegen.link_object", &e.to_string()))
Err(e) => Err(ErrorBuilder::with_context(
"env.codegen.link_object",
&e.to_string(),
)),
}
}
("env.box_introspect", "kind") => {
@ -487,9 +673,7 @@ impl MirInterpreter {
}
}
other => {
if std::env::var("NYASH_BOX_INTROSPECT_TRACE")
.ok()
.as_deref()
if std::env::var("NYASH_BOX_INTROSPECT_TRACE").ok().as_deref()
== Some("1")
{
eprintln!(
@ -509,16 +693,15 @@ impl MirInterpreter {
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
let result = plugin_loader_v2::handle_box_introspect("kind", &collected);
#[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))]
let result: crate::bid::BidResult<Option<Box<dyn NyashBox>>> =
Err(crate::bid::BidError::PluginError);
let result: crate::bid::BidResult<
Option<Box<dyn NyashBox>>,
> = Err(crate::bid::BidError::PluginError);
match result {
Ok(Some(b)) => Ok(VMValue::BoxRef(Arc::from(b))),
Ok(None) => Ok(VMValue::Void),
Err(e) => Err(self.err_with_context(
"env.box_introspect.kind",
&format!("{:?}", e),
)),
Err(e) => Err(self
.err_with_context("env.box_introspect.kind", &format!("{:?}", e))),
}
}
_ => {
@ -529,7 +712,7 @@ impl MirInterpreter {
"hostbridge.extern_invoke unsupported for {}.{} [provider] (check extern_provider_dispatch / env.* handlers)",
name, method
)))
},
}
};
Some(out)
}

View File

@ -10,7 +10,9 @@ impl MirInterpreter {
if let JsonValue::Object(ref mut m) = v {
if !m.contains_key("version") {
m.insert("version".to_string(), JsonValue::from(0));
if let Ok(out) = serde_json::to_string(&v) { return out; }
if let Ok(out) = serde_json::to_string(&v) {
return out;
}
}
}
s.to_string()
@ -52,23 +54,47 @@ impl MirInterpreter {
if let Some(a0) = args.get(0) {
let v = self.reg_load(*a0)?;
// Dev-only: mirror print-trace for extern console.log
if Self::print_trace_enabled() { self.print_trace_emit(&v); }
if Self::print_trace_enabled() {
self.print_trace_emit(&v);
}
// Treat VM Void and BoxRef(VoidBox) as JSON null for dev ergonomics
match &v {
VMValue::Void => { println!("null"); self.write_void(dst); return Ok(()); }
VMValue::Void => {
println!("null");
self.write_void(dst);
return Ok(());
}
VMValue::BoxRef(bx) => {
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
println!("null"); self.write_void(dst); return Ok(());
if bx
.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
println!("null");
self.write_void(dst);
return Ok(());
}
if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
println!("{}", sb.value); self.write_void(dst); return Ok(());
if let Some(sb) =
bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
{
println!("{}", sb.value);
self.write_void(dst);
return Ok(());
}
}
VMValue::String(s) => { println!("{}", s); self.write_void(dst); return Ok(()); }
VMValue::String(s) => {
println!("{}", s);
self.write_void(dst);
return Ok(());
}
_ => {}
}
// Operator Box (Stringify) dev flag gated
if std::env::var("NYASH_OPERATOR_BOX_STRINGIFY").ok().as_deref() == Some("1") {
if std::env::var("NYASH_OPERATOR_BOX_STRINGIFY")
.ok()
.as_deref()
== Some("1")
{
if let Some(op) = self.functions.get("StringifyOperator.apply/1").cloned() {
let out = self.exec_function_inner(&op, Some(&[v.clone()]))?;
println!("{}", out.to_string());
@ -151,20 +177,28 @@ impl MirInterpreter {
Ok(())
}
("env.mirbuilder", "emit") => {
let ret = self.extern_provider_dispatch("env.mirbuilder.emit", args).unwrap_or(Ok(VMValue::Void))?;
let ret = self
.extern_provider_dispatch("env.mirbuilder.emit", args)
.unwrap_or(Ok(VMValue::Void))?;
self.write_result(dst, ret);
Ok(())
}
("env.codegen", "emit_object") => {
let ret = self.extern_provider_dispatch("env.codegen.emit_object", args).unwrap_or(Ok(VMValue::Void))?;
let ret = self
.extern_provider_dispatch("env.codegen.emit_object", args)
.unwrap_or(Ok(VMValue::Void))?;
self.write_result(dst, ret);
Ok(())
}
("env.codegen", "link_object") => {
// Args in third param (ArrayBox): [obj_path, exe_out?]
// Note: This branch is used for ExternCall form; provider toggles must be ON.
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
.ok()
.as_deref()
!= Some("1")
{
return Err(self.err_invalid("env.codegen.link_object: C-API route disabled"));
}
// Extract array payload
@ -172,13 +206,19 @@ impl MirInterpreter {
let v = self.reg_load(*a2)?;
match v {
VMValue::BoxRef(b) => {
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
if let Some(ab) =
b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
{
let idx0: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(0));
let elem0 = ab.get(idx0).to_string_box().value;
let mut exe: Option<String> = None;
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
let idx1: Box<dyn crate::box_trait::NyashBox> =
Box::new(crate::box_trait::IntegerBox::new(1));
let e1 = ab.get(idx1).to_string_box().value;
if !e1.is_empty() { exe = Some(e1); }
if !e1.is_empty() {
exe = Some(e1);
}
(elem0, exe)
} else {
(b.to_string_box().value, None)
@ -187,13 +227,18 @@ impl MirInterpreter {
_ => (v.to_string(), None),
}
} else {
return Err(self.err_invalid("extern_invoke env.codegen.link_object expects args array"));
return Err(self
.err_invalid("extern_invoke env.codegen.link_object expects args array"));
};
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
let obj = std::path::PathBuf::from(obj_path);
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
let exe = exe_out
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref())
.map_err(|e| self.err_with_context("env.codegen.link_object", &e.to_string()))?;
.map_err(|e| {
self.err_with_context("env.codegen.link_object", &e.to_string())
})?;
self.write_result(dst, VMValue::String(exe.to_string_lossy().into_owned()));
Ok(())
}
@ -208,17 +253,18 @@ impl MirInterpreter {
("hostbridge", "extern_invoke") => {
if let Some(res) = self.extern_provider_dispatch("hostbridge.extern_invoke", args) {
match res {
Ok(v) => { self.write_result(dst, v); }
Err(e) => { return Err(e); }
Ok(v) => {
self.write_result(dst, v);
}
Err(e) => {
return Err(e);
}
}
return Ok(());
}
return Err(self.err_invalid("hostbridge.extern_invoke unsupported [externals]"));
}
_ => Err(self.err_invalid(format!(
"ExternCall {}.{} not supported",
iface, method
))),
_ => Err(self.err_invalid(format!("ExternCall {}.{} not supported", iface, method))),
}
}
}

View File

@ -13,16 +13,28 @@ impl MirInterpreter {
let v = self.reg_load(value)?;
// Align with calls.rs behavior: Void/BoxRef(VoidBox) prints as null; raw String/StringBox unquoted
match &v {
VMValue::Void => { println!("null"); return Ok(()); }
VMValue::Void => {
println!("null");
return Ok(());
}
VMValue::BoxRef(bx) => {
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
println!("null"); return Ok(());
if bx
.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
println!("null");
return Ok(());
}
if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
println!("{}", sb.value); return Ok(());
println!("{}", sb.value);
return Ok(());
}
}
VMValue::String(s) => { println!("{}", s); return Ok(()); }
VMValue::String(s) => {
println!("{}", s);
return Ok(());
}
_ => {}
}
println!("{}", v.to_string());

View File

@ -3,9 +3,7 @@ use super::*;
// VM dispatch trace macro (used across handlers)
macro_rules! trace_dispatch {
($method:expr, $handler:expr) => {
if $method == "length"
&& std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
{
if $method == "length" && std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-trace] length dispatch handler={}", $handler);
}
};
@ -14,15 +12,15 @@ macro_rules! trace_dispatch {
mod arithmetic;
mod boxes;
mod boxes_array;
mod boxes_string;
mod boxes_instance;
mod boxes_map;
mod boxes_object_fields;
mod boxes_instance;
mod boxes_plugin;
mod boxes_string;
mod boxes_void_guards;
mod calls;
mod externals;
mod extern_provider;
mod externals;
mod memory;
mod misc;
@ -90,10 +88,7 @@ impl MirInterpreter {
}
MirInstruction::DebugLog { message, values } => {
// Dev-only: MIR-level debug logging (no new values defined).
if std::env::var("NYASH_MIR_DEBUG_LOG")
.ok()
.as_deref() == Some("1")
{
if std::env::var("NYASH_MIR_DEBUG_LOG").ok().as_deref() == Some("1") {
eprint!("[MIR-LOG] {}:", message);
for vid in values {
let v = self.reg_load(*vid).unwrap_or(VMValue::Void);

View File

@ -8,10 +8,26 @@ impl MirInterpreter {
match v {
VMValue::Void => "void",
VMValue::BoxRef(b) => {
if b.as_any().downcast_ref::<crate::boxes::null_box::NullBox>().is_some() { "null" }
else if b.as_any().downcast_ref::<crate::boxes::missing_box::MissingBox>().is_some() { "missing" }
else if b.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() { "void" }
else { "" }
if b.as_any()
.downcast_ref::<crate::boxes::null_box::NullBox>()
.is_some()
{
"null"
} else if b
.as_any()
.downcast_ref::<crate::boxes::missing_box::MissingBox>()
.is_some()
{
"missing"
} else if b
.as_any()
.downcast_ref::<crate::box_trait::VoidBox>()
.is_some()
{
"void"
} else {
""
}
}
_ => "",
}
@ -23,11 +39,7 @@ impl MirInterpreter {
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
|| std::env::var("NYASH_VM_TRACE_EXEC").ok().as_deref() == Some("1")
{
let keys: Vec<String> = self
.regs
.keys()
.map(|k| format!("{:?}", k))
.collect();
let keys: Vec<String> = self.regs.keys().map(|k| format!("{:?}", k)).collect();
eprintln!(
"[vm-trace] reg_load undefined id={:?} fn={} last_block={:?} last_inst={:?} regs={}",
id,
@ -39,8 +51,16 @@ impl MirInterpreter {
}
// Dev-time safety valve: tolerate undefined registers as Void when enabled
let tolerate = std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1")
|| std::env::var("HAKO_PHI_VERIFY").ok().map(|v| v.to_ascii_lowercase()).map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false)
|| std::env::var("NYASH_PHI_VERIFY").ok().map(|v| v.to_ascii_lowercase()).map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false);
|| std::env::var("HAKO_PHI_VERIFY")
.ok()
.map(|v| v.to_ascii_lowercase())
.map(|v| v == "1" || v == "true" || v == "on")
.unwrap_or(false)
|| std::env::var("NYASH_PHI_VERIFY")
.ok()
.map(|v| v.to_ascii_lowercase())
.map(|v| v == "1" || v == "true" || v == "on")
.unwrap_or(false);
if tolerate {
return Ok(VMValue::Void);
}
@ -87,21 +107,43 @@ impl MirInterpreter {
v
};
(norm(a), norm(b))
} else { (a, b) };
} else {
(a, b)
};
// Dev: nullish trace for binop
if crate::config::env::null_missing_box_enabled() && Self::box_trace_enabled() {
let (ak, bk) = (crate::backend::abi_util::tag_of_vm(&a), crate::backend::abi_util::tag_of_vm(&b));
let (ak, bk) = (
crate::backend::abi_util::tag_of_vm(&a),
crate::backend::abi_util::tag_of_vm(&b),
);
let (an, bn) = (Self::tag_nullish(&a), Self::tag_nullish(&b));
let op_s = match op { BinaryOp::Add=>"Add", BinaryOp::Sub=>"Sub", BinaryOp::Mul=>"Mul", BinaryOp::Div=>"Div", BinaryOp::Mod=>"Mod", BinaryOp::BitAnd=>"BitAnd", BinaryOp::BitOr=>"BitOr", BinaryOp::BitXor=>"BitXor", BinaryOp::And=>"And", BinaryOp::Or=>"Or", BinaryOp::Shl=>"Shl", BinaryOp::Shr=>"Shr" };
let op_s = match op {
BinaryOp::Add => "Add",
BinaryOp::Sub => "Sub",
BinaryOp::Mul => "Mul",
BinaryOp::Div => "Div",
BinaryOp::Mod => "Mod",
BinaryOp::BitAnd => "BitAnd",
BinaryOp::BitOr => "BitOr",
BinaryOp::BitXor => "BitXor",
BinaryOp::And => "And",
BinaryOp::Or => "Or",
BinaryOp::Shl => "Shl",
BinaryOp::Shr => "Shr",
};
eprintln!("{{\"ev\":\"binop\",\"op\":\"{}\",\"a_k\":\"{}\",\"b_k\":\"{}\",\"a_n\":\"{}\",\"b_n\":\"{}\"}}", op_s, ak, bk, an, bn);
}
Ok(match (op, a, b) {
// Dev-only safety valves for Add (guarded by tolerance or --dev):
// - Treat Void as 0 for numeric +
// - Treat Void as empty string for string +
(Add, VMValue::Void, Integer(y)) | (Add, Integer(y), VMValue::Void) if tolerate => Integer(y),
(Add, VMValue::Void, Integer(y)) | (Add, Integer(y), VMValue::Void) if tolerate => {
Integer(y)
}
(Add, VMValue::Void, Float(y)) | (Add, Float(y), VMValue::Void) if tolerate => Float(y),
(Add, String(s), VMValue::Void) | (Add, VMValue::Void, String(s)) if tolerate => String(s),
(Add, String(s), VMValue::Void) | (Add, VMValue::Void, String(s)) if tolerate => {
String(s)
}
// Dev-only safety valve for Sub (guarded): treat Void as 0
(Sub, Integer(x), VMValue::Void) if tolerate => Integer(x),
(Sub, VMValue::Void, Integer(y)) if tolerate => Integer(0 - y),
@ -129,7 +171,7 @@ impl MirInterpreter {
(BitOr, Integer(x), Integer(y)) => Integer(x | y),
(BitXor, Integer(x), Integer(y)) => Integer(x ^ y),
(And, VMValue::Bool(x), VMValue::Bool(y)) => VMValue::Bool(x && y),
(Or, VMValue::Bool(x), VMValue::Bool(y)) => VMValue::Bool(x || y),
(Or, VMValue::Bool(x), VMValue::Bool(y)) => VMValue::Bool(x || y),
(Shl, Integer(x), Integer(y)) => Integer(x.wrapping_shl(y as u32)),
(Shr, Integer(x), Integer(y)) => Integer(x.wrapping_shr(y as u32)),
(opk, va, vb) => {
@ -142,7 +184,7 @@ impl MirInterpreter {
return Err(VMError::TypeError(format!(
"unsupported binop {:?} on {:?} and {:?}",
opk, va, vb
)))
)));
}
})
}
@ -162,7 +204,9 @@ impl MirInterpreter {
v
};
(norm(a), norm(b))
} else { (a, b) };
} else {
(a, b)
};
// Dev-only safety valve: tolerate Void in comparisons when enabled or in --dev
// → treat Void as 0 for numeric, empty for string
let (a2, b2) = if tolerate {
@ -195,9 +239,19 @@ impl MirInterpreter {
};
// Dev: nullish trace for compare
if crate::config::env::null_missing_box_enabled() && Self::box_trace_enabled() {
let (ak, bk) = (crate::backend::abi_util::tag_of_vm(&a2), crate::backend::abi_util::tag_of_vm(&b2));
let (ak, bk) = (
crate::backend::abi_util::tag_of_vm(&a2),
crate::backend::abi_util::tag_of_vm(&b2),
);
let (an, bn) = (Self::tag_nullish(&a2), Self::tag_nullish(&b2));
let op_s = match op { CompareOp::Eq=>"Eq", CompareOp::Ne=>"Ne", CompareOp::Lt=>"Lt", CompareOp::Le=>"Le", CompareOp::Gt=>"Gt", CompareOp::Ge=>"Ge" };
let op_s = match op {
CompareOp::Eq => "Eq",
CompareOp::Ne => "Ne",
CompareOp::Lt => "Lt",
CompareOp::Le => "Le",
CompareOp::Gt => "Gt",
CompareOp::Ge => "Ge",
};
eprintln!("{{\"ev\":\"cmp\",\"op\":\"{}\",\"a_k\":\"{}\",\"b_k\":\"{}\",\"a_n\":\"{}\",\"b_n\":\"{}\"}}", op_s, ak, bk, an, bn);
}
let result = match (op, &a3, &b3) {
@ -230,7 +284,6 @@ impl MirInterpreter {
};
Ok(result)
}
}
// ---- Box trace (dev-only observer) ----
@ -243,11 +296,15 @@ impl MirInterpreter {
fn box_trace_filter_match(class_name: &str) -> bool {
if let Ok(filt) = std::env::var("NYASH_BOX_TRACE_FILTER") {
let want = filt.trim();
if want.is_empty() { return true; }
if want.is_empty() {
return true;
}
// comma/space separated tokens; match if any token is contained in class
for tok in want.split(|c: char| c == ',' || c.is_whitespace()) {
let t = tok.trim();
if !t.is_empty() && class_name.contains(t) { return true; }
if !t.is_empty() && class_name.contains(t) {
return true;
}
}
false
} else {
@ -272,34 +329,49 @@ impl MirInterpreter {
}
pub(super) fn box_trace_emit_new(&self, class_name: &str, argc: usize) {
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { return; }
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) {
return;
}
eprintln!(
"{{\"ev\":\"new\",\"class\":\"{}\",\"argc\":{}}}",
Self::json_escape(class_name), argc
Self::json_escape(class_name),
argc
);
}
pub(super) fn box_trace_emit_call(&self, class_name: &str, method: &str, argc: usize) {
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { return; }
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) {
return;
}
eprintln!(
"{{\"ev\":\"call\",\"class\":\"{}\",\"method\":\"{}\",\"argc\":{}}}",
Self::json_escape(class_name), Self::json_escape(method), argc
Self::json_escape(class_name),
Self::json_escape(method),
argc
);
}
pub(super) fn box_trace_emit_get(&self, class_name: &str, field: &str, val_kind: &str) {
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { return; }
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) {
return;
}
eprintln!(
"{{\"ev\":\"get\",\"class\":\"{}\",\"field\":\"{}\",\"val\":\"{}\"}}",
Self::json_escape(class_name), Self::json_escape(field), Self::json_escape(val_kind)
Self::json_escape(class_name),
Self::json_escape(field),
Self::json_escape(val_kind)
);
}
pub(super) fn box_trace_emit_set(&self, class_name: &str, field: &str, val_kind: &str) {
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { return; }
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) {
return;
}
eprintln!(
"{{\"ev\":\"set\",\"class\":\"{}\",\"field\":\"{}\",\"val\":\"{}\"}}",
Self::json_escape(class_name), Self::json_escape(field), Self::json_escape(val_kind)
Self::json_escape(class_name),
Self::json_escape(field),
Self::json_escape(val_kind)
);
}
}
@ -312,7 +384,9 @@ impl MirInterpreter {
}
pub(super) fn print_trace_emit(&self, val: &VMValue) {
if !Self::print_trace_enabled() { return; }
if !Self::print_trace_enabled() {
return;
}
let (kind, class, nullish) = match val {
VMValue::Integer(_) => ("Integer", "".to_string(), None),
VMValue::Float(_) => ("Float", "".to_string(), None),
@ -324,17 +398,43 @@ impl MirInterpreter {
// Prefer InstanceBox.class_name when available
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
let tag = if crate::config::env::null_missing_box_enabled() {
if b.as_any().downcast_ref::<crate::boxes::null_box::NullBox>().is_some() { Some("null") }
else if b.as_any().downcast_ref::<crate::boxes::missing_box::MissingBox>().is_some() { Some("missing") }
else { None }
} else { None };
if b.as_any()
.downcast_ref::<crate::boxes::null_box::NullBox>()
.is_some()
{
Some("null")
} else if b
.as_any()
.downcast_ref::<crate::boxes::missing_box::MissingBox>()
.is_some()
{
Some("missing")
} else {
None
}
} else {
None
};
("BoxRef", inst.class_name.clone(), tag)
} else {
let tag = if crate::config::env::null_missing_box_enabled() {
if b.as_any().downcast_ref::<crate::boxes::null_box::NullBox>().is_some() { Some("null") }
else if b.as_any().downcast_ref::<crate::boxes::missing_box::MissingBox>().is_some() { Some("missing") }
else { None }
} else { None };
if b.as_any()
.downcast_ref::<crate::boxes::null_box::NullBox>()
.is_some()
{
Some("null")
} else if b
.as_any()
.downcast_ref::<crate::boxes::missing_box::MissingBox>()
.is_some()
{
Some("missing")
} else {
None
}
} else {
None
};
("BoxRef", b.type_name().to_string(), tag)
}
}

View File

@ -12,8 +12,8 @@
*/
use super::{MirFunction, MirInterpreter};
use serde_json::json;
use crate::backend::vm::{VMError, VMValue};
use serde_json::json;
#[derive(Debug, Clone)]
struct ParsedSig<'a> {
@ -25,16 +25,25 @@ struct ParsedSig<'a> {
fn parse_method_signature(name: &str) -> Option<ParsedSig<'_>> {
let dot = name.find('.')?;
let slash = name.rfind('/')?;
if dot >= slash { return None; }
if dot >= slash {
return None;
}
let class = &name[..dot];
let method = &name[dot + 1..slash];
let arity_str = &name[slash + 1..];
Some(ParsedSig { class, method, arity_str })
Some(ParsedSig {
class,
method,
arity_str,
})
}
fn extract_instance_box_class(arg0: &VMValue) -> Option<String> {
if let VMValue::BoxRef(bx) = arg0 {
if let Some(inst) = bx.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = bx
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
return Some(inst.class_name.clone());
}
}
@ -47,7 +56,12 @@ fn reroute_to_correct_method(
parsed: &ParsedSig<'_>,
arg_vals: Option<&[VMValue]>,
) -> Option<Result<VMValue, VMError>> {
let target = format!("{}.{}{}", recv_cls, parsed.method, format!("/{}", parsed.arity_str));
let target = format!(
"{}.{}{}",
recv_cls,
parsed.method,
format!("/{}", parsed.arity_str)
);
if let Some(f) = interp.functions.get(&target).cloned() {
// Debug: emit class-reroute event (dev-only)
crate::debug::hub::emit(
@ -149,7 +163,10 @@ fn try_special_method(
if parsed.method == "is_eof" && parsed.arity_str == "0" {
if let Some(args) = arg_vals {
if let VMValue::BoxRef(bx) = &args[0] {
if let Some(inst) = bx.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
if let Some(inst) = bx
.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
{
if recv_cls == "JsonToken" {
let is = match inst.get_field_ng("type") {
Some(crate::value::NyashValue::String(ref s)) => s == "EOF",
@ -158,8 +175,14 @@ fn try_special_method(
return Some(Ok(VMValue::Bool(is)));
}
if recv_cls == "JsonScanner" {
let pos = match inst.get_field_ng("position") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 };
let len = match inst.get_field_ng("length") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 };
let pos = match inst.get_field_ng("position") {
Some(crate::value::NyashValue::Integer(i)) => i,
_ => 0,
};
let len = match inst.get_field_ng("length") {
Some(crate::value::NyashValue::Integer(i)) => i,
_ => 0,
};
return Some(Ok(VMValue::Bool(pos >= len)));
}
}
@ -183,16 +206,35 @@ pub(super) fn pre_exec_reroute(
func: &MirFunction,
arg_vals: Option<&[VMValue]>,
) -> Option<Result<VMValue, VMError>> {
let args = match arg_vals { Some(a) => a, None => return None };
if args.is_empty() { return None; }
let parsed = match parse_method_signature(func.signature.name.as_str()) { Some(p) => p, None => return None };
let recv_cls = match extract_instance_box_class(&args[0]) { Some(c) => c, None => return None };
let args = match arg_vals {
Some(a) => a,
None => return None,
};
if args.is_empty() {
return None;
}
let parsed = match parse_method_signature(func.signature.name.as_str()) {
Some(p) => p,
None => return None,
};
let recv_cls = match extract_instance_box_class(&args[0]) {
Some(c) => c,
None => return None,
};
// Always consider special re-routes (e.g., toString→stringify) even when class matches
if let Some(r) = try_special_reroute(interp, &recv_cls, &parsed, arg_vals) { return Some(r); }
if recv_cls == parsed.class { return None; }
if let Some(r) = try_special_reroute(interp, &recv_cls, &parsed, arg_vals) {
return Some(r);
}
if recv_cls == parsed.class {
return None;
}
// Class mismatch: reroute to same method on the receiver's class
if let Some(r) = reroute_to_correct_method(interp, &recv_cls, &parsed, arg_vals) { return Some(r); }
if let Some(r) = reroute_to_correct_method(interp, &recv_cls, &parsed, arg_vals) {
return Some(r);
}
// Narrow special fallback (e.g., is_eof)
if let Some(r) = try_special_method(&recv_cls, &parsed, arg_vals) { return Some(r); }
if let Some(r) = try_special_method(&recv_cls, &parsed, arg_vals) {
return Some(r);
}
None
}

View File

@ -71,20 +71,32 @@ impl MirInterpreter {
}
/// Register static box declarations (called from vm.rs during setup)
pub fn register_static_box_decl(&mut self, name: String, decl: crate::core::model::BoxDeclaration) {
pub fn register_static_box_decl(
&mut self,
name: String,
decl: crate::core::model::BoxDeclaration,
) {
self.static_box_decls.insert(name, decl);
}
/// Ensure static box singleton instance exists, create if not
/// Returns mutable reference to the singleton instance
fn ensure_static_box_instance(&mut self, box_name: &str) -> Result<&mut crate::instance_v2::InstanceBox, VMError> {
fn ensure_static_box_instance(
&mut self,
box_name: &str,
) -> Result<&mut crate::instance_v2::InstanceBox, VMError> {
// Check if instance already exists
if !self.static_boxes.contains_key(box_name) {
// Get declaration
let decl = self.static_box_decls.get(box_name)
.ok_or_else(|| VMError::InvalidInstruction(
format!("static box declaration not found: {}", box_name)
))?
let decl = self
.static_box_decls
.get(box_name)
.ok_or_else(|| {
VMError::InvalidInstruction(format!(
"static box declaration not found: {}",
box_name
))
})?
.clone();
// Create instance from declaration
@ -97,15 +109,20 @@ impl MirInterpreter {
self.static_boxes.insert(box_name.to_string(), instance);
if std::env::var("NYASH_VM_STATIC_TRACE").ok().as_deref() == Some("1") {
eprintln!("[vm-static] created singleton instance for static box: {}", box_name);
eprintln!(
"[vm-static] created singleton instance for static box: {}",
box_name
);
}
}
// Return mutable reference
self.static_boxes.get_mut(box_name)
.ok_or_else(|| VMError::InvalidInstruction(
format!("static box instance not found after creation: {}", box_name)
self.static_boxes.get_mut(box_name).ok_or_else(|| {
VMError::InvalidInstruction(format!(
"static box instance not found after creation: {}",
box_name
))
})
}
/// Check if a function name represents a static box method
@ -169,7 +186,12 @@ impl MirInterpreter {
// Build helpful error message
let mut names: Vec<&String> = module.functions.keys().collect();
names.sort();
let avail = names.into_iter().take(12).cloned().collect::<Vec<_>>().join(", ");
let avail = names
.into_iter()
.take(12)
.cloned()
.collect::<Vec<_>>()
.join(", ");
let tried = candidates.join(", ");
let msg = format!(
"entry function not found. searched: [{}]. available: [{}]. hint: define 'static box Main {{ method main(args){{ ... }} }}' or set NYASH_ENTRY=Name",
@ -200,9 +222,13 @@ impl MirInterpreter {
argv_list = out;
}
} else if let Ok(s) = std::env::var("NYASH_SCRIPT_ARGS_JSON") {
if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) { argv_list = v; }
if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) {
argv_list = v;
}
} else if let Ok(s) = std::env::var("NYASH_ARGV") {
if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) { argv_list = v; }
if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) {
argv_list = v;
}
}
// Construct ArrayBox of StringBox
let array = crate::boxes::array::ArrayBox::new();

View File

@ -3,8 +3,8 @@
//! レジスタ値の読み込み+型変換チェーンを統一します。
use super::super::*;
use crate::mir::ValueId;
use crate::box_trait::NyashBox;
use crate::mir::ValueId;
impl MirInterpreter {
/// レジスタ値をBox<dyn NyashBox>として読み込む
@ -53,7 +53,13 @@ impl MirInterpreter {
VMValue::Bool(_) => "Bool",
VMValue::String(_) => "String",
VMValue::Void => "Void",
VMValue::BoxRef(b) => return Err(self.err_type_mismatch("load_as_int", "Integer", &b.type_name())),
VMValue::BoxRef(b) => {
return Err(self.err_type_mismatch(
"load_as_int",
"Integer",
&b.type_name(),
))
}
VMValue::Future(_) => "Future",
};
Err(self.err_type_mismatch("load_as_int", "Integer", type_name))
@ -83,7 +89,9 @@ impl MirInterpreter {
VMValue::Bool(_) => "Bool",
VMValue::String(_) => "String",
VMValue::Void => "Void",
VMValue::BoxRef(b) => return Err(self.err_type_mismatch("load_as_bool", "Bool", &b.type_name())),
VMValue::BoxRef(b) => {
return Err(self.err_type_mismatch("load_as_bool", "Bool", &b.type_name()))
}
VMValue::Future(_) => "Future",
};
Err(self.err_type_mismatch("load_as_bool", "Bool", type_name))

View File

@ -42,7 +42,10 @@ impl ErrorBuilder {
#[inline]
#[allow(dead_code)]
pub fn type_mismatch(method: &str, expected: &str, actual: &str) -> VMError {
VMError::InvalidInstruction(format!("{} expects {} type, got {}", method, expected, actual))
VMError::InvalidInstruction(format!(
"{} expects {} type, got {}",
method, expected, actual
))
}
/// Index out of bounds error
@ -60,7 +63,10 @@ impl ErrorBuilder {
#[inline]
#[allow(dead_code)]
pub fn out_of_bounds(method: &str, index: usize, len: usize) -> VMError {
VMError::InvalidInstruction(format!("{} index out of bounds: {} >= {}", method, index, len))
VMError::InvalidInstruction(format!(
"{} index out of bounds: {} >= {}",
method, index, len
))
}
/// Unsupported operation error

View File

@ -1,11 +1,11 @@
//! MIR Interpreter共通ユーティリティ
pub mod destination_helpers;
pub mod arg_validation;
pub mod receiver_helpers;
pub mod error_helpers;
pub mod conversion_helpers;
pub mod destination_helpers;
pub mod error_helpers;
pub mod naming;
pub mod receiver_helpers;
// Phase 21.2: adapter_dev removed - all adapter functions now in .hako implementation
// Selective re-export (only naming is widely used via utils::normalize_arity_suffix)

View File

@ -12,4 +12,3 @@ pub fn normalize_arity_suffix(name: &str) -> &str {
None => name,
}
}

View File

@ -23,9 +23,7 @@ impl MirInterpreter {
let receiver_value = self.reg_load(receiver)?;
match receiver_value {
VMValue::BoxRef(b) => Ok(b),
_ => Err(VMError::InvalidInstruction(
"receiver must be Box".into(),
)),
_ => Err(VMError::InvalidInstruction("receiver must be Box".into())),
}
}
}

View File

@ -7,5 +7,5 @@
// Re-export all arithmetic operations from the dedicated arithmetic module
pub use crate::boxes::arithmetic::{
AddBox, SubtractBox, MultiplyBox, DivideBox, ModuloBox, CompareBox,
AddBox, CompareBox, DivideBox, ModuloBox, MultiplyBox, SubtractBox,
};

View File

@ -5,8 +5,8 @@
* 🎯 Phase 2.4: Delete this file to remove builtin ArrayBox support
*/
use crate::box_trait::NyashBox;
use crate::box_factory::RuntimeError;
use crate::box_trait::NyashBox;
/// Create builtin ArrayBox instance
///
@ -31,4 +31,4 @@ mod tests {
let result = create(&[]).unwrap();
assert!(result.as_any().downcast_ref::<ArrayBox>().is_some());
}
}
}

View File

@ -5,8 +5,8 @@
* 🎯 Phase 2.3: Delete this file to remove builtin BoolBox support
*/
use crate::box_trait::{NyashBox, BoolBox};
use crate::box_factory::RuntimeError;
use crate::box_trait::{BoolBox, NyashBox};
/// Create builtin BoolBox instance
///
@ -35,4 +35,4 @@ mod tests {
let result = create(&[]).unwrap();
assert!(result.as_any().downcast_ref::<BoolBox>().is_some());
}
}
}

View File

@ -5,8 +5,8 @@
* 🎯 Phase 2.6: Delete this file to remove builtin ConsoleBox support (LAST)
*/
use crate::box_trait::NyashBox;
use crate::box_factory::RuntimeError;
use crate::box_trait::NyashBox;
/// Create builtin ConsoleBox instance
///
@ -32,4 +32,4 @@ mod tests {
let result = create(&[]).unwrap();
assert!(result.as_any().downcast_ref::<ConsoleBox>().is_some());
}
}
}

View File

@ -7,8 +7,8 @@
* 🎯 Core-ro mode: This is used directly
*/
use crate::box_trait::NyashBox;
use crate::box_factory::RuntimeError;
use crate::box_trait::NyashBox;
use crate::boxes::file::core_ro::CoreRoFileIo;
use crate::boxes::file::FileBox;
use std::sync::Arc;
@ -28,9 +28,7 @@ pub fn create(_args: &[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, RuntimeE
});
}
eprintln!(
"[FileBox] Using builtin core-ro fallback implementation"
);
eprintln!("[FileBox] Using builtin core-ro fallback implementation");
// Create FileBox with core-ro provider directly
// Don't rely on global provider_lock which may not be initialized

View File

@ -5,8 +5,8 @@
* 🎯 Phase 2.2: Delete this file to remove builtin IntegerBox support
*/
use crate::box_trait::{NyashBox, IntegerBox};
use crate::box_factory::RuntimeError;
use crate::box_trait::{IntegerBox, NyashBox};
/// Create builtin IntegerBox instance
///
@ -35,4 +35,4 @@ mod tests {
let result = create(&[]).unwrap();
assert!(result.as_any().downcast_ref::<IntegerBox>().is_some());
}
}
}

View File

@ -5,8 +5,8 @@
* 🎯 Phase 2.5: Delete this file to remove builtin MapBox support
*/
use crate::box_trait::NyashBox;
use crate::box_factory::RuntimeError;
use crate::box_trait::NyashBox;
/// Create builtin MapBox instance
///
@ -31,4 +31,4 @@ mod tests {
let result = create(&[]).unwrap();
assert!(result.as_any().downcast_ref::<MapBox>().is_some());
}
}
}

View File

@ -15,15 +15,15 @@
*/
// Phase 2.1-2.6: Delete these modules one by one
pub mod string_box; // DELETE: Phase 2.1 (plugin ready)
pub mod integer_box; // DELETE: Phase 2.2 (plugin ready)
pub mod bool_box; // DELETE: Phase 2.3 (plugin needed)
pub mod array_box; // DELETE: Phase 2.4 (plugin check)
pub mod map_box; // DELETE: Phase 2.5 (plugin check)
pub mod console_box; // DELETE: Phase 2.6 (LAST - critical for logging)
pub mod array_box; // DELETE: Phase 2.4 (plugin check)
pub mod bool_box; // DELETE: Phase 2.3 (plugin needed)
pub mod console_box;
pub mod integer_box; // DELETE: Phase 2.2 (plugin ready)
pub mod map_box; // DELETE: Phase 2.5 (plugin check)
pub mod string_box; // DELETE: Phase 2.1 (plugin ready) // DELETE: Phase 2.6 (LAST - critical for logging)
// Fallback support (Phase 15.5: Fallback Guarantee)
pub mod file_box; // FALLBACK: Core-ro FileBox for auto/core-ro modes
pub mod file_box; // FALLBACK: Core-ro FileBox for auto/core-ro modes
// Special consideration
pub mod null_box; // DISCUSS: Keep as primitive?
pub mod null_box; // DISCUSS: Keep as primitive?

View File

@ -5,8 +5,8 @@
* 📋 Discussion needed: Is null a language primitive or plugin concern?
*/
use crate::box_trait::NyashBox;
use crate::box_factory::RuntimeError;
use crate::box_trait::NyashBox;
/// Create builtin NullBox instance
///
@ -26,4 +26,4 @@ mod tests {
let result = create(&[]).unwrap();
assert!(result.as_any().downcast_ref::<NullBox>().is_some());
}
}
}

View File

@ -5,8 +5,8 @@
* 🎯 Phase 2.1: Delete this file to remove builtin StringBox support
*/
use crate::box_trait::{NyashBox, StringBox};
use crate::box_factory::RuntimeError;
use crate::box_trait::{NyashBox, StringBox};
/// Create builtin StringBox instance
///
@ -35,4 +35,4 @@ mod tests {
let result = create(&[]).unwrap();
assert!(result.as_any().downcast_ref::<StringBox>().is_some());
}
}
}

View File

@ -138,7 +138,10 @@ impl UnifiedBoxRegistry {
Some("strict_plugin_first") | _ => FactoryPolicy::StrictPluginFirst, // Phase 15.5: Plugin First DEFAULT!
};
eprintln!("[UnifiedBoxRegistry] 🎯 Factory Policy: {:?} (Phase 15.5: Everything is Plugin!)", policy);
eprintln!(
"[UnifiedBoxRegistry] 🎯 Factory Policy: {:?} (Phase 15.5: Everything is Plugin!)",
policy
);
Self::with_policy(policy)
}
@ -175,7 +178,7 @@ impl UnifiedBoxRegistry {
if std::env::var("NYASH_USE_PLUGIN_BUILTINS").is_ok() {
if let Ok(types) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES") {
if types.split(',').any(|t| t.trim() == name) {
return false; // 予約型として扱わない
return false; // 予約型として扱わない
}
}
}
@ -229,19 +232,19 @@ impl UnifiedBoxRegistry {
match self.policy {
FactoryPolicy::StrictPluginFirst => match factory_type {
FactoryType::Plugin => 0, // Highest priority
FactoryType::User => 1, // Medium priority
FactoryType::Builtin => 2, // Lowest priority
FactoryType::Plugin => 0, // Highest priority
FactoryType::User => 1, // Medium priority
FactoryType::Builtin => 2, // Lowest priority
},
FactoryPolicy::CompatPluginFirst => match factory_type {
FactoryType::Plugin => 0, // Highest priority
FactoryType::Builtin => 1, // Medium priority
FactoryType::User => 2, // Lowest priority
FactoryType::Plugin => 0, // Highest priority
FactoryType::Builtin => 1, // Medium priority
FactoryType::User => 2, // Lowest priority
},
FactoryPolicy::BuiltinFirst => match factory_type {
FactoryType::Builtin => 0, // Highest priority (current default)
FactoryType::User => 1, // Medium priority
FactoryType::Plugin => 2, // Lowest priority
FactoryType::Builtin => 0, // Highest priority (current default)
FactoryType::User => 1, // Medium priority
FactoryType::Plugin => 2, // Lowest priority
},
}
} else {
@ -271,7 +274,9 @@ impl UnifiedBoxRegistry {
// Prefer plugin-builtins when enabled and provider is available in v2 registry
// BUT: Skip if plugins are explicitly disabled
let plugins_disabled = std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() == Some("1");
if !plugins_disabled && std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1") {
if !plugins_disabled
&& std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().as_deref() == Some("1")
{
use crate::runtime::{get_global_registry, BoxProvider};
// Allowlist types for override: env NYASH_PLUGIN_OVERRIDE_TYPES="ArrayBox,MapBox" (default: none)
let allow: Vec<String> = if let Ok(list) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES")
@ -338,7 +343,8 @@ impl UnifiedBoxRegistry {
Err(e) => {
// FileBox special case: handle fallback based on mode
if name == "FileBox" {
if let Some(fallback_result) = self.try_filebox_fallback(name, args, &e) {
if let Some(fallback_result) = self.try_filebox_fallback(name, args, &e)
{
return fallback_result;
}
}
@ -386,14 +392,23 @@ impl UnifiedBoxRegistry {
match mode {
provider_registry::FileBoxMode::PluginOnly => {
// Fail-Fast: return the original error immediately
eprintln!("[FileBox] Plugin creation failed in plugin-only mode: {}", original_error);
eprintln!(
"[FileBox] Plugin creation failed in plugin-only mode: {}",
original_error
);
Some(Err(RuntimeError::InvalidOperation {
message: format!("FileBox plugin creation failed (plugin-only mode): {}", original_error),
message: format!(
"FileBox plugin creation failed (plugin-only mode): {}",
original_error
),
}))
}
provider_registry::FileBoxMode::Auto => {
// Auto mode: try fallback to builtin/core-ro
eprintln!("[FileBox] Plugin creation failed, falling back to builtin/core-ro: {}", original_error);
eprintln!(
"[FileBox] Plugin creation failed, falling back to builtin/core-ro: {}",
original_error
);
// Try builtin factory if available
for factory in &self.factories {

View File

@ -6,8 +6,8 @@
*/
use super::BoxFactory;
use crate::box_trait::NyashBox;
use super::RuntimeError;
use crate::box_trait::NyashBox;
use crate::runtime::get_global_registry;
/// Factory for plugin-based Box types
@ -29,11 +29,20 @@ impl BoxFactory for PluginBoxFactory {
) -> Result<Box<dyn NyashBox>, RuntimeError> {
// Check if plugins are disabled
let plugins_disabled = std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() == Some("1");
eprintln!("[PluginBoxFactory] NYASH_DISABLE_PLUGINS={} for {}",
std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref().unwrap_or("not set"), name);
eprintln!(
"[PluginBoxFactory] NYASH_DISABLE_PLUGINS={} for {}",
std::env::var("NYASH_DISABLE_PLUGINS")
.ok()
.as_deref()
.unwrap_or("not set"),
name
);
if plugins_disabled {
return Err(RuntimeError::InvalidOperation {
message: format!("Plugins disabled (NYASH_DISABLE_PLUGINS=1), cannot create {}", name),
message: format!(
"Plugins disabled (NYASH_DISABLE_PLUGINS=1), cannot create {}",
name
),
});
}

View File

@ -6,9 +6,9 @@
*/
use super::BoxFactory;
use super::{RuntimeError, SharedState};
use crate::box_trait::NyashBox;
use crate::instance_v2::InstanceBox;
use super::{RuntimeError, SharedState};
/// Factory for user-defined Box types
pub struct UserDefinedBoxFactory {

View File

@ -16,16 +16,14 @@
use crate::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox};
use crate::boxes::FloatBox;
use crate::operator_traits::{
DynamicAdd, DynamicDiv, DynamicMul, DynamicSub, OperatorError,
};
use crate::operator_traits::{DynamicAdd, DynamicDiv, DynamicMul, DynamicSub, OperatorError};
// Phase 1-2: Import macros, helpers, and static implementations from separate modules
mod macros;
mod helpers;
mod macros;
mod static_ops;
pub use helpers::{concat_result, can_repeat};
pub use helpers::{can_repeat, concat_result};
pub use macros::impl_static_numeric_ops;
// Phase 2: Static implementations are now in static_ops.rs
@ -392,16 +390,27 @@ impl OperatorResolver {
#[inline]
fn try_dyn_left_add(left: &dyn NyashBox, right: &dyn NyashBox) -> Option<Box<dyn NyashBox>> {
if let Some(int_box) = left.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
if let Some(result) = int_box.try_add(right) { return Some(result); }
if let Some(result) = int_box.try_add(right) {
return Some(result);
}
}
if let Some(str_box) = left.as_any().downcast_ref::<crate::box_trait::StringBox>() {
if let Some(result) = str_box.try_add(right) { return Some(result); }
if let Some(result) = str_box.try_add(right) {
return Some(result);
}
}
if let Some(float_box) = left.as_any().downcast_ref::<crate::boxes::math_box::FloatBox>() {
if let Some(result) = float_box.try_add(right) { return Some(result); }
if let Some(float_box) = left
.as_any()
.downcast_ref::<crate::boxes::math_box::FloatBox>()
{
if let Some(result) = float_box.try_add(right) {
return Some(result);
}
}
if let Some(bool_box) = left.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
if let Some(result) = bool_box.try_add(right) { return Some(result); }
if let Some(result) = bool_box.try_add(right) {
return Some(result);
}
}
None
}
@ -409,13 +418,22 @@ impl OperatorResolver {
#[inline]
fn try_dyn_left_sub(left: &dyn NyashBox, right: &dyn NyashBox) -> Option<Box<dyn NyashBox>> {
if let Some(int_box) = left.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
if let Some(result) = int_box.try_sub(right) { return Some(result); }
if let Some(result) = int_box.try_sub(right) {
return Some(result);
}
}
if let Some(float_box) = left.as_any().downcast_ref::<crate::boxes::math_box::FloatBox>() {
if let Some(result) = float_box.try_sub(right) { return Some(result); }
if let Some(float_box) = left
.as_any()
.downcast_ref::<crate::boxes::math_box::FloatBox>()
{
if let Some(result) = float_box.try_sub(right) {
return Some(result);
}
}
if let Some(bool_box) = left.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
if let Some(result) = bool_box.try_sub(right) { return Some(result); }
if let Some(result) = bool_box.try_sub(right) {
return Some(result);
}
}
None
}
@ -423,16 +441,27 @@ impl OperatorResolver {
#[inline]
fn try_dyn_left_mul(left: &dyn NyashBox, right: &dyn NyashBox) -> Option<Box<dyn NyashBox>> {
if let Some(int_box) = left.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
if let Some(result) = int_box.try_mul(right) { return Some(result); }
if let Some(result) = int_box.try_mul(right) {
return Some(result);
}
}
if let Some(str_box) = left.as_any().downcast_ref::<crate::box_trait::StringBox>() {
if let Some(result) = str_box.try_mul(right) { return Some(result); }
if let Some(result) = str_box.try_mul(right) {
return Some(result);
}
}
if let Some(float_box) = left.as_any().downcast_ref::<crate::boxes::math_box::FloatBox>() {
if let Some(result) = float_box.try_mul(right) { return Some(result); }
if let Some(float_box) = left
.as_any()
.downcast_ref::<crate::boxes::math_box::FloatBox>()
{
if let Some(result) = float_box.try_mul(right) {
return Some(result);
}
}
if let Some(bool_box) = left.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
if let Some(result) = bool_box.try_mul(right) { return Some(result); }
if let Some(result) = bool_box.try_mul(right) {
return Some(result);
}
}
None
}
@ -442,7 +471,10 @@ impl OperatorResolver {
if let Some(int_box) = left.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
return int_box.try_div(right);
}
if let Some(float_box) = left.as_any().downcast_ref::<crate::boxes::math_box::FloatBox>() {
if let Some(float_box) = left
.as_any()
.downcast_ref::<crate::boxes::math_box::FloatBox>()
{
return float_box.try_div(right);
}
if let Some(bool_box) = left.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
@ -458,7 +490,9 @@ impl OperatorResolver {
// Try to cast to concrete types first and use their DynamicAdd implementation
// This approach uses the concrete types rather than trait objects
if let Some(result) = Self::try_dyn_left_add(left, right) { return Ok(result); }
if let Some(result) = Self::try_dyn_left_add(left, right) {
return Ok(result);
}
Err(OperatorError::UnsupportedOperation {
operator: "+".to_string(),
@ -472,7 +506,9 @@ impl OperatorResolver {
left: &dyn NyashBox,
right: &dyn NyashBox,
) -> Result<Box<dyn NyashBox>, OperatorError> {
if let Some(result) = Self::try_dyn_left_sub(left, right) { return Ok(result); }
if let Some(result) = Self::try_dyn_left_sub(left, right) {
return Ok(result);
}
Err(OperatorError::UnsupportedOperation {
operator: "-".to_string(),
@ -486,7 +522,9 @@ impl OperatorResolver {
left: &dyn NyashBox,
right: &dyn NyashBox,
) -> Result<Box<dyn NyashBox>, OperatorError> {
if let Some(result) = Self::try_dyn_left_mul(left, right) { return Ok(result); }
if let Some(result) = Self::try_dyn_left_mul(left, right) {
return Ok(result);
}
Err(OperatorError::UnsupportedOperation {
operator: "*".to_string(),
@ -500,7 +538,9 @@ impl OperatorResolver {
left: &dyn NyashBox,
right: &dyn NyashBox,
) -> Result<Box<dyn NyashBox>, OperatorError> {
if let Some(result) = Self::try_dyn_left_div(left, right) { return Ok(result); }
if let Some(result) = Self::try_dyn_left_div(left, right) {
return Ok(result);
}
Err(OperatorError::UnsupportedOperation {
operator: "/".to_string(),

View File

@ -60,4 +60,4 @@ pub fn concat_result(left: &dyn NyashBox, right: &dyn NyashBox) -> Box<dyn Nyash
#[inline]
pub fn can_repeat(times: i64) -> bool {
(0..=10_000).contains(&times)
}
}

View File

@ -28,21 +28,21 @@ macro_rules! impl_static_numeric_ops {
impl crate::operator_traits::NyashAdd<$ty> for $ty {
type Output = $ty;
fn add(self, rhs: $ty) -> Self::Output {
< $ty >::new(self.value + rhs.value)
<$ty>::new(self.value + rhs.value)
}
}
impl crate::operator_traits::NyashSub<$ty> for $ty {
type Output = $ty;
fn sub(self, rhs: $ty) -> Self::Output {
< $ty >::new(self.value - rhs.value)
<$ty>::new(self.value - rhs.value)
}
}
impl crate::operator_traits::NyashMul<$ty> for $ty {
type Output = $ty;
fn mul(self, rhs: $ty) -> Self::Output {
< $ty >::new(self.value * rhs.value)
<$ty>::new(self.value * rhs.value)
}
}
@ -52,7 +52,7 @@ macro_rules! impl_static_numeric_ops {
if rhs.value == $zero {
Err(crate::operator_traits::OperatorError::DivisionByZero)
} else {
Ok(< $ty >::new(self.value / rhs.value))
Ok(<$ty>::new(self.value / rhs.value))
}
}
}
@ -60,4 +60,4 @@ macro_rules! impl_static_numeric_ops {
}
// Re-export the macro for external use
pub use impl_static_numeric_ops;
pub use impl_static_numeric_ops;

View File

@ -6,8 +6,8 @@
use crate::box_trait::{BoolBox, IntegerBox, StringBox};
use crate::boxes::FloatBox;
use crate::operator_traits::{NyashAdd, NyashMul};
use crate::impl_static_numeric_ops;
use crate::operator_traits::{NyashAdd, NyashMul};
// ===== Macro-generated static implementations =====

View File

@ -160,15 +160,7 @@ pub trait NyashBox: BoxCore + Debug {
// ===== Basic Box Types (Re-exported from basic module) =====
// Re-export all basic box types from the dedicated basic module
pub use crate::boxes::basic::{
BoolBox, ErrorBox, FileBox, IntegerBox, StringBox, VoidBox,
};
pub use crate::boxes::basic::{BoolBox, ErrorBox, FileBox, IntegerBox, StringBox, VoidBox};
// Old Box implementations have been moved to separate files
// ArrayBox is now defined in boxes::array module

View File

@ -137,4 +137,4 @@ impl Display for AddBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
}

View File

@ -76,4 +76,4 @@ impl CompareBox {
let right_str = right.to_string_box();
BoolBox::new(left_str.value >= right_str.value)
}
}
}

View File

@ -124,4 +124,4 @@ impl Display for DivideBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
}

View File

@ -13,19 +13,19 @@
// Individual arithmetic operation implementations
mod add_box;
mod subtract_box;
mod multiply_box;
mod compare_box;
mod divide_box;
mod modulo_box;
mod compare_box;
mod multiply_box;
mod subtract_box;
// Re-export all arithmetic box types
pub use add_box::AddBox;
pub use subtract_box::SubtractBox;
pub use multiply_box::MultiplyBox;
pub use compare_box::CompareBox;
pub use divide_box::DivideBox;
pub use modulo_box::ModuloBox;
pub use compare_box::CompareBox;
pub use multiply_box::MultiplyBox;
pub use subtract_box::SubtractBox;
// Re-export for convenience - common pattern in arithmetic operations
pub use crate::box_trait::{BoolBox, IntegerBox, NyashBox, StringBox};
@ -125,4 +125,4 @@ mod tests {
assert_eq!(CompareBox::greater(&left, &right).value, false);
assert_eq!(CompareBox::equals(&left, &right).value, false);
}
}
}

View File

@ -115,4 +115,4 @@ impl Display for MultiplyBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
}

View File

@ -116,4 +116,4 @@ impl Display for SubtractBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
}

View File

@ -42,11 +42,11 @@ impl ArrayBox {
None => {
let strict = std::env::var("HAKO_OOB_STRICT")
.ok()
.map(|v| matches!(v.as_str(), "1"|"true"|"on"))
.map(|v| matches!(v.as_str(), "1" | "true" | "on"))
.unwrap_or(false)
|| std::env::var("NYASH_OOB_STRICT")
.ok()
.map(|v| matches!(v.as_str(), "1"|"true"|"on"))
.map(|v| matches!(v.as_str(), "1" | "true" | "on"))
.unwrap_or(false);
if strict {
Box::new(StringBox::new("[array/empty/pop] empty array"))
@ -92,11 +92,11 @@ impl ArrayBox {
None => {
let strict = std::env::var("HAKO_OOB_STRICT")
.ok()
.map(|v| matches!(v.as_str(), "1"|"true"|"on"))
.map(|v| matches!(v.as_str(), "1" | "true" | "on"))
.unwrap_or(false)
|| std::env::var("NYASH_OOB_STRICT")
.ok()
.map(|v| matches!(v.as_str(), "1"|"true"|"on"))
.map(|v| matches!(v.as_str(), "1" | "true" | "on"))
.unwrap_or(false);
if strict {
// Mark OOB occurrence for runner policies (GateC strict fail, etc.)
@ -127,11 +127,11 @@ impl ArrayBox {
} else {
let strict = std::env::var("HAKO_OOB_STRICT")
.ok()
.map(|v| matches!(v.as_str(), "1"|"true"|"on"))
.map(|v| matches!(v.as_str(), "1" | "true" | "on"))
.unwrap_or(false)
|| std::env::var("NYASH_OOB_STRICT")
.ok()
.map(|v| matches!(v.as_str(), "1"|"true"|"on"))
.map(|v| matches!(v.as_str(), "1" | "true" | "on"))
.unwrap_or(false);
if strict {
crate::runtime::observe::mark_oob();

View File

@ -2,7 +2,7 @@
//!
//! Implements the core BoolBox type for true/false values.
use crate::box_trait::{NyashBox, BoxCore, BoxBase, StringBox};
use crate::box_trait::{BoxBase, BoxCore, NyashBox, StringBox};
use std::any::Any;
use std::fmt::{Debug, Display};
@ -83,4 +83,4 @@ impl Display for BoolBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
}

View File

@ -2,7 +2,7 @@
//!
//! Implements the ErrorBox type for representing error information.
use crate::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use std::any::Any;
use std::fmt::{Debug, Display};
@ -79,4 +79,4 @@ impl Display for ErrorBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
}

View File

@ -9,4 +9,4 @@
)]
// Re-export the new FileBox implementation for backward compatibility
pub use crate::boxes::file::FileBox;
pub use crate::boxes::file::FileBox;

View File

@ -2,7 +2,7 @@
//!
//! Implements the core IntegerBox type for 64-bit signed integers.
use crate::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use std::any::Any;
use std::fmt::{Debug, Display};
@ -79,4 +79,4 @@ impl Display for IntegerBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
}

View File

@ -4,17 +4,17 @@
//! fundamental data types in Nyash: String, Integer, Boolean, Void, File, and Error.
// Individual basic box implementations
mod string_box;
mod integer_box;
mod bool_box;
mod void_box;
mod file_box;
mod error_box;
mod file_box;
mod integer_box;
mod string_box;
mod void_box;
// Re-export all basic box types
pub use string_box::StringBox;
pub use integer_box::IntegerBox;
pub use bool_box::BoolBox;
pub use void_box::VoidBox;
pub use error_box::ErrorBox;
pub use file_box::FileBox;
pub use error_box::ErrorBox;
pub use integer_box::IntegerBox;
pub use string_box::StringBox;
pub use void_box::VoidBox;

View File

@ -2,7 +2,7 @@
//!
//! Implements the core StringBox type with all string manipulation methods.
use crate::box_trait::{NyashBox, BoxCore, BoxBase};
use crate::box_trait::{BoxBase, BoxCore, NyashBox};
use crate::boxes::ArrayBox;
use std::any::Any;
use std::fmt::{Debug, Display};

View File

@ -2,7 +2,7 @@
//!
//! Implements the core VoidBox type representing empty or null results.
use crate::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox};
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use std::any::Any;
use std::fmt::{Debug, Display};
@ -75,4 +75,4 @@ impl Display for VoidBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}
}

View File

@ -99,9 +99,9 @@
* - call stackは直近100件まで自動保持
*/
use crate::box_factory::RuntimeError;
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox, VoidBox};
use crate::instance_v2::InstanceBox;
use crate::box_factory::RuntimeError;
use chrono::Local;
use std::any::Any;
use std::collections::HashMap;

View File

@ -33,8 +33,8 @@
* - `run()`はブロッキング動作(アプリ終了まで制御を返さない)
*/
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use crate::box_factory::RuntimeError;
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use eframe::{self, egui, epaint::Vec2};
use std::any::Any;
use std::sync::{Arc, RwLock};

View File

@ -1,8 +1,8 @@
//! Thin FileBox shim that delegates to a selected provider.
//! Not wired into the registry yet (safe placeholder).
use super::provider::{FileCaps, FileIo, FileResult};
use std::sync::Arc;
use super::provider::{FileIo, FileCaps, FileResult};
#[allow(dead_code)]
pub struct FileBoxShim {
@ -16,9 +16,16 @@ impl FileBoxShim {
let caps = provider.caps();
Self { provider, caps }
}
pub fn open(&self, path: &str) -> FileResult<()> { self.provider.open(path) }
pub fn read(&self) -> FileResult<String> { self.provider.read() }
pub fn close(&self) -> FileResult<()> { self.provider.close() }
pub fn caps(&self) -> FileCaps { self.caps }
pub fn open(&self, path: &str) -> FileResult<()> {
self.provider.open(path)
}
pub fn read(&self) -> FileResult<String> {
self.provider.read()
}
pub fn close(&self) -> FileResult<()> {
self.provider.close()
}
pub fn caps(&self) -> FileCaps {
self.caps
}
}

View File

@ -3,10 +3,12 @@
//! Provides ProviderFactory implementation for the builtin FileBox (core-ro).
//! This is auto-registered when feature "builtin-filebox" is enabled.
use std::sync::Arc;
use crate::boxes::file::provider::FileIo;
use crate::boxes::file::core_ro::CoreRoFileIo;
use crate::runner::modes::common_util::provider_registry::{ProviderFactory, register_provider_factory};
use crate::boxes::file::provider::FileIo;
use crate::runner::modes::common_util::provider_registry::{
register_provider_factory, ProviderFactory,
};
use std::sync::Arc;
/// Builtin FileBox factory (static registration)
pub struct BuiltinFileBoxFactory;

View File

@ -3,10 +3,10 @@
// 参考: 既存Boxの設計思想
// SSOT provider design (ring0/1) — modules are currently placeholders
pub mod provider; // trait FileIo / FileCaps / FileError
pub mod core_ro; // Core readonly provider
pub mod box_shim; // Thin delegating shim
pub mod builtin_factory; // Builtin FileBox ProviderFactory
pub mod box_shim; // Thin delegating shim
pub mod builtin_factory;
pub mod core_ro; // Core readonly provider
pub mod provider; // trait FileIo / FileCaps / FileError // Builtin FileBox ProviderFactory
use crate::box_trait::{BoolBox, BoxBase, BoxCore, NyashBox, StringBox};
use crate::runtime::provider_lock;
@ -64,7 +64,8 @@ impl FileBox {
.ok_or("FileBox provider not initialized")?
.clone();
provider.open(path)
provider
.open(path)
.map_err(|e| format!("Failed to open: {}", e))?;
Ok(FileBox {
@ -76,8 +77,7 @@ impl FileBox {
pub fn read_to_string(&self) -> Result<String, String> {
if let Some(ref provider) = self.provider {
provider.read()
.map_err(|e| format!("Read failed: {}", e))
provider.read().map_err(|e| format!("Read failed: {}", e))
} else {
Err("No provider available".to_string())
}
@ -85,7 +85,8 @@ impl FileBox {
pub fn write_all(&self, _buf: &[u8]) -> Result<(), String> {
// Fail-Fast by capability: consult provider caps
let caps = self.provider
let caps = self
.provider
.as_ref()
.map(|p| p.caps())
.or_else(|| provider_lock::get_filebox_caps())
@ -107,15 +108,20 @@ impl FileBox {
/// ファイルに内容を書き込む
pub fn write(&self, _content: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
let caps = self.provider
let caps = self
.provider
.as_ref()
.map(|p| p.caps())
.or_else(|| provider_lock::get_filebox_caps())
.unwrap_or_else(|| provider::FileCaps::read_only());
if !caps.write {
return Box::new(StringBox::new("Error: write unsupported by provider (read-only)"));
return Box::new(StringBox::new(
"Error: write unsupported by provider (read-only)",
));
}
Box::new(StringBox::new("Error: write supported but not implemented in this build"))
Box::new(StringBox::new(
"Error: write supported but not implemented in this build",
))
}
/// ファイルが存在するかチェック
@ -126,28 +132,38 @@ impl FileBox {
/// ファイルを削除
pub fn delete(&self) -> Box<dyn NyashBox> {
let caps = self.provider
let caps = self
.provider
.as_ref()
.map(|p| p.caps())
.or_else(|| provider_lock::get_filebox_caps())
.unwrap_or_else(|| provider::FileCaps::read_only());
if !caps.write {
return Box::new(StringBox::new("Error: delete unsupported by provider (read-only)"));
return Box::new(StringBox::new(
"Error: delete unsupported by provider (read-only)",
));
}
Box::new(StringBox::new("Error: delete supported but not implemented in this build"))
Box::new(StringBox::new(
"Error: delete supported but not implemented in this build",
))
}
/// ファイルをコピー
pub fn copy(&self, _dest: &str) -> Box<dyn NyashBox> {
let caps = self.provider
let caps = self
.provider
.as_ref()
.map(|p| p.caps())
.or_else(|| provider_lock::get_filebox_caps())
.unwrap_or_else(|| provider::FileCaps::read_only());
if !caps.write {
return Box::new(StringBox::new("Error: copy unsupported by provider (read-only)"));
return Box::new(StringBox::new(
"Error: copy unsupported by provider (read-only)",
));
}
Box::new(StringBox::new("Error: copy supported but not implemented in this build"))
Box::new(StringBox::new(
"Error: copy supported but not implemented in this build",
))
}
}

View File

@ -12,7 +12,12 @@ pub struct FileCaps {
}
impl FileCaps {
pub const fn read_only() -> Self { Self { read: true, write: false } }
pub const fn read_only() -> Self {
Self {
read: true,
write: false,
}
}
}
/// Unified error type (thin placeholder for now)
@ -40,9 +45,10 @@ pub trait FileIo: Send + Sync {
pub fn normalize_newlines(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for b in s.as_bytes() {
if *b == b'\r' { continue; }
if *b == b'\r' {
continue;
}
out.push(*b as char);
}
out
}

View File

@ -279,7 +279,11 @@ impl HTTPResponseBox {
}
/// ステータスコード・メッセージ設定
pub fn set_status(&self, code: Box<dyn NyashBox>, message: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
pub fn set_status(
&self,
code: Box<dyn NyashBox>,
message: Box<dyn NyashBox>,
) -> Box<dyn NyashBox> {
let code_val = code.to_string_box().value.parse::<i32>().unwrap_or(200);
let message_val = message.to_string_box().value;
*self.status_code.lock().unwrap() = code_val;
@ -288,7 +292,11 @@ impl HTTPResponseBox {
}
/// ヘッダー設定
pub fn set_header(&self, name: Box<dyn NyashBox>, value: Box<dyn NyashBox>) -> Box<dyn NyashBox> {
pub fn set_header(
&self,
name: Box<dyn NyashBox>,
value: Box<dyn NyashBox>,
) -> Box<dyn NyashBox> {
let name_str = name.to_string_box().value;
let value_str = value.to_string_box().value;
self.headers.lock().unwrap().insert(name_str, value_str);
@ -326,7 +334,10 @@ impl HTTPResponseBox {
let version = self.http_version.lock().unwrap().clone();
let status_code = *self.status_code.lock().unwrap();
let status_message = self.status_message.lock().unwrap().clone();
response.push_str(&format!("{} {} {}\r\n", version, status_code, status_message));
response.push_str(&format!(
"{} {} {}\r\n",
version, status_code, status_message
));
// Headers
for (name, value) in self.headers.lock().unwrap().iter() {
@ -335,7 +346,8 @@ impl HTTPResponseBox {
// Content-Length if not already set
let body_len = { self.body.lock().unwrap().len() };
let need_len = { !self.headers.lock().unwrap().contains_key("content-length") && body_len > 0 };
let need_len =
{ !self.headers.lock().unwrap().contains_key("content-length") && body_len > 0 };
if need_len {
response.push_str(&format!("Content-Length: {}\r\n", body_len));
}
@ -367,10 +379,11 @@ impl HTTPResponseBox {
let response = HTTPResponseBox::new();
*response.status_code.lock().unwrap() = 200;
*response.status_message.lock().unwrap() = "OK".to_string();
response.headers.lock().unwrap().insert(
"Content-Type".to_string(),
"application/json".to_string(),
);
response
.headers
.lock()
.unwrap()
.insert("Content-Type".to_string(), "application/json".to_string());
*response.body.lock().unwrap() = content.to_string_box().value;
response
}
@ -384,7 +397,8 @@ impl HTTPResponseBox {
"Content-Type".to_string(),
"text/html; charset=utf-8".to_string(),
);
*response.body.lock().unwrap() = "<html><body><h1>404 - Not Found</h1></body></html>".to_string();
*response.body.lock().unwrap() =
"<html><body><h1>404 - Not Found</h1></body></html>".to_string();
response
}
}

View File

@ -161,7 +161,10 @@ impl MapBox {
}
value.clone_box()
}
None => Box::new(StringBox::new(&format!("[map/missing] Key not found: {}", key_str))),
None => Box::new(StringBox::new(&format!(
"[map/missing] Key not found: {}",
key_str
))),
}
}
@ -202,7 +205,10 @@ impl MapBox {
.unwrap()
.values()
.map(|v| {
if v.as_any().downcast_ref::<crate::instance_v2::InstanceBox>().is_some() {
if v.as_any()
.downcast_ref::<crate::instance_v2::InstanceBox>()
.is_some()
{
v.share_box()
} else {
v.clone_box()

View File

@ -16,28 +16,48 @@ pub struct MissingBox {
impl MissingBox {
pub fn new() -> Self {
Self { base: BoxBase::new() }
Self {
base: BoxBase::new(),
}
}
pub fn is_missing(&self) -> bool { true }
pub fn is_missing(&self) -> bool {
true
}
}
impl BoxCore for MissingBox {
fn box_id(&self) -> u64 { self.base.id }
fn parent_type_id(&self) -> Option<std::any::TypeId> { self.base.parent_type_id }
fn box_id(&self) -> u64 {
self.base.id
}
fn parent_type_id(&self) -> Option<std::any::TypeId> {
self.base.parent_type_id
}
fn fmt_box(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
// 開発時の可視性向上のための文字列表現。prod では基本的に表面化させない想定。
write!(f, "(missing)")
}
fn as_any(&self) -> &dyn Any { self }
fn as_any_mut(&mut self) -> &mut dyn Any { self }
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl NyashBox for MissingBox {
fn type_name(&self) -> &'static str { "MissingBox" }
fn to_string_box(&self) -> StringBox { StringBox::new("(missing)") }
fn clone_box(&self) -> Box<dyn NyashBox> { Box::new(self.clone()) }
fn share_box(&self) -> Box<dyn NyashBox> { self.clone_box() }
fn type_name(&self) -> &'static str {
"MissingBox"
}
fn to_string_box(&self) -> StringBox {
StringBox::new("(missing)")
}
fn clone_box(&self) -> Box<dyn NyashBox> {
Box::new(self.clone())
}
fn share_box(&self) -> Box<dyn NyashBox> {
self.clone_box()
}
fn equals(&self, other: &dyn NyashBox) -> BoolBox {
// 欠損どうしは論理同値とみなすが、通常の等価比較は境界で禁止される想定。
BoolBox::new(other.as_any().downcast_ref::<MissingBox>().is_some())
@ -45,6 +65,7 @@ impl NyashBox for MissingBox {
}
impl Display for MissingBox {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.fmt_box(f) }
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_box(f)
}
}

View File

@ -145,8 +145,8 @@ pub use egui_box::EguiBox;
#[cfg(target_arch = "wasm32")]
pub use web::{WebCanvasBox, WebConsoleBox, WebDisplayBox};
pub mod null_box;
pub mod missing_box;
pub mod null_box;
// High-priority Box types
pub mod array;
@ -168,8 +168,8 @@ pub mod intent_box;
pub mod p2p_box;
// null関数も再エクスポート
pub use null_box::{null, NullBox};
pub use missing_box::MissingBox;
pub use null_box::{null, NullBox};
// High-priority Box types re-export
pub use array::ArrayBox;

View File

@ -1,7 +1,7 @@
use super::utils::parse_debug_fuel;
use super::CliConfig;
use clap::{Arg, ArgMatches, Command};
use serde_json;
use super::CliConfig;
use super::utils::parse_debug_fuel;
pub fn parse() -> CliConfig {
let argv: Vec<String> = std::env::args().collect();
@ -13,10 +13,7 @@ pub fn parse() -> CliConfig {
}
// Provide HEX-escaped JSON as an alternate robust path for multiline/special bytes
// Each arg is encoded as lowercase hex of its UTF-8 bytes
let hex_args: Vec<String> = script_args
.iter()
.map(|s| hex_encode_utf8(s))
.collect();
let hex_args: Vec<String> = script_args.iter().map(|s| hex_encode_utf8(s)).collect();
if let Ok(hex_json) = serde_json::to_string(&hex_args) {
std::env::set_var("NYASH_SCRIPT_ARGS_HEX_JSON", hex_json);
}
@ -118,8 +115,12 @@ fn hex_encode_utf8(s: &str) -> String {
}
pub fn from_matches(matches: &ArgMatches) -> CliConfig {
if matches.get_flag("stage3") { std::env::set_var("NYASH_NY_COMPILER_STAGE3", "1"); }
if let Some(a) = matches.get_one::<String>("ny-compiler-args") { std::env::set_var("NYASH_NY_COMPILER_CHILD_ARGS", a); }
if matches.get_flag("stage3") {
std::env::set_var("NYASH_NY_COMPILER_STAGE3", "1");
}
if let Some(a) = matches.get_one::<String>("ny-compiler-args") {
std::env::set_var("NYASH_NY_COMPILER_CHILD_ARGS", a);
}
let cfg = CliConfig {
file: matches.get_one::<String>("file").cloned(),
debug_fuel: parse_debug_fuel(matches.get_one::<String>("debug-fuel").unwrap()),
@ -134,7 +135,11 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig {
compile_native: matches.get_flag("compile-native") || matches.get_flag("aot"),
output_file: matches.get_one::<String>("output").cloned(),
benchmark: matches.get_flag("benchmark"),
iterations: matches.get_one::<String>("iterations").unwrap().parse().unwrap_or(10),
iterations: matches
.get_one::<String>("iterations")
.unwrap()
.parse()
.unwrap_or(10),
vm_stats: matches.get_flag("vm-stats"),
vm_stats_json: matches.get_flag("vm-stats-json"),
jit_exec: matches.get_flag("jit-exec"),
@ -145,7 +150,9 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig {
jit_events_compile: matches.get_flag("jit-events-compile"),
jit_events_runtime: matches.get_flag("jit-events-runtime"),
jit_events_path: matches.get_one::<String>("jit-events-path").cloned(),
jit_threshold: matches.get_one::<String>("jit-threshold").and_then(|s| s.parse::<u32>().ok()),
jit_threshold: matches
.get_one::<String>("jit-threshold")
.and_then(|s| s.parse::<u32>().ok()),
jit_phi_min: matches.get_flag("jit-phi-min"),
jit_hostcall: matches.get_flag("jit-hostcall"),
jit_handle_debug: matches.get_flag("jit-handle-debug"),
@ -158,7 +165,10 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig {
run_task: matches.get_one::<String>("run-task").cloned(),
load_ny_plugins: matches.get_flag("load-ny-plugins"),
gc_mode: matches.get_one::<String>("gc").cloned(),
parser_ny: matches.get_one::<String>("parser").map(|s| s == "ny").unwrap_or(false),
parser_ny: matches
.get_one::<String>("parser")
.map(|s| s == "ny")
.unwrap_or(false),
ny_parser_pipe: matches.get_flag("ny-parser-pipe"),
json_file: matches.get_one::<String>("json-file").cloned(),
mir_json_file: matches.get_one::<String>("mir-json-file").cloned(),
@ -168,7 +178,10 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig {
build_aot: matches.get_one::<String>("build-aot").cloned(),
build_profile: matches.get_one::<String>("build-profile").cloned(),
build_target: matches.get_one::<String>("build-target").cloned(),
cli_usings: matches.get_many::<String>("using").map(|v| v.cloned().collect()).unwrap_or_else(|| Vec::new()),
cli_usings: matches
.get_many::<String>("using")
.map(|v| v.cloned().collect())
.unwrap_or_else(|| Vec::new()),
emit_mir_json: matches.get_one::<String>("emit-mir-json").cloned(),
program_json_to_mir: matches.get_one::<String>("program-json-to-mir").cloned(),
emit_exe: matches.get_one::<String>("emit-exe").cloned(),
@ -179,42 +192,94 @@ pub fn from_matches(matches: &ArgMatches) -> CliConfig {
macro_ctx_json: matches.get_one::<String>("macro-ctx-json").cloned(),
};
if cfg.cli_verbose { std::env::set_var("NYASH_CLI_VERBOSE", "1"); }
if cfg.vm_stats { std::env::set_var("NYASH_VM_STATS", "1"); }
if cfg.vm_stats_json { std::env::set_var("NYASH_VM_STATS_JSON", "1"); }
if cfg.jit_exec { std::env::set_var("NYASH_JIT_EXEC", "1"); }
if cfg.jit_stats { std::env::set_var("NYASH_JIT_STATS", "1"); }
if cfg.jit_stats_json { std::env::set_var("NYASH_JIT_STATS_JSON", "1"); }
if cfg.jit_dump { std::env::set_var("NYASH_JIT_DUMP", "1"); }
if cfg.jit_events { std::env::set_var("NYASH_JIT_EVENTS", "1"); }
if cfg.jit_events_compile { std::env::set_var("NYASH_JIT_EVENTS_COMPILE", "1"); }
if cfg.jit_events_runtime { std::env::set_var("NYASH_JIT_EVENTS_RUNTIME", "1"); }
if let Some(p) = &cfg.jit_events_path { std::env::set_var("NYASH_JIT_EVENTS_PATH", p); }
if let Some(t) = cfg.jit_threshold { std::env::set_var("NYASH_JIT_THRESHOLD", t.to_string()); }
if cfg.jit_phi_min { std::env::set_var("NYASH_JIT_PHI_MIN", "1"); }
if cfg.jit_hostcall { std::env::set_var("NYASH_JIT_HOSTCALL", "1"); }
if cfg.jit_handle_debug { std::env::set_var("NYASH_JIT_HANDLE_DEBUG", "1"); }
if cfg.jit_native_f64 { std::env::set_var("NYASH_JIT_NATIVE_F64", "1"); }
if cfg.jit_native_bool { std::env::set_var("NYASH_JIT_NATIVE_BOOL", "1"); }
if cfg.jit_only { std::env::set_var("NYASH_JIT_ONLY", "1"); }
if cfg.jit_direct { std::env::set_var("NYASH_JIT_DIRECT", "1"); }
if let Some(gc) = &cfg.gc_mode { std::env::set_var("NYASH_GC_MODE", gc); }
if cfg.cli_verbose {
std::env::set_var("NYASH_CLI_VERBOSE", "1");
}
if cfg.vm_stats {
std::env::set_var("NYASH_VM_STATS", "1");
}
if cfg.vm_stats_json {
std::env::set_var("NYASH_VM_STATS_JSON", "1");
}
if cfg.jit_exec {
std::env::set_var("NYASH_JIT_EXEC", "1");
}
if cfg.jit_stats {
std::env::set_var("NYASH_JIT_STATS", "1");
}
if cfg.jit_stats_json {
std::env::set_var("NYASH_JIT_STATS_JSON", "1");
}
if cfg.jit_dump {
std::env::set_var("NYASH_JIT_DUMP", "1");
}
if cfg.jit_events {
std::env::set_var("NYASH_JIT_EVENTS", "1");
}
if cfg.jit_events_compile {
std::env::set_var("NYASH_JIT_EVENTS_COMPILE", "1");
}
if cfg.jit_events_runtime {
std::env::set_var("NYASH_JIT_EVENTS_RUNTIME", "1");
}
if let Some(p) = &cfg.jit_events_path {
std::env::set_var("NYASH_JIT_EVENTS_PATH", p);
}
if let Some(t) = cfg.jit_threshold {
std::env::set_var("NYASH_JIT_THRESHOLD", t.to_string());
}
if cfg.jit_phi_min {
std::env::set_var("NYASH_JIT_PHI_MIN", "1");
}
if cfg.jit_hostcall {
std::env::set_var("NYASH_JIT_HOSTCALL", "1");
}
if cfg.jit_handle_debug {
std::env::set_var("NYASH_JIT_HANDLE_DEBUG", "1");
}
if cfg.jit_native_f64 {
std::env::set_var("NYASH_JIT_NATIVE_F64", "1");
}
if cfg.jit_native_bool {
std::env::set_var("NYASH_JIT_NATIVE_BOOL", "1");
}
if cfg.jit_only {
std::env::set_var("NYASH_JIT_ONLY", "1");
}
if cfg.jit_direct {
std::env::set_var("NYASH_JIT_DIRECT", "1");
}
if let Some(gc) = &cfg.gc_mode {
std::env::set_var("NYASH_GC_MODE", gc);
}
if matches.get_flag("run-tests") {
std::env::set_var("NYASH_RUN_TESTS", "1");
if let Some(filter) = matches.get_one::<String>("test-filter") { std::env::set_var("NYASH_TEST_FILTER", filter); }
if let Some(filter) = matches.get_one::<String>("test-filter") {
std::env::set_var("NYASH_TEST_FILTER", filter);
}
if let Some(entry) = matches.get_one::<String>("test-entry") {
let v = entry.as_str();
if v == "wrap" || v == "override" { std::env::set_var("NYASH_TEST_ENTRY", v); }
if v == "wrap" || v == "override" {
std::env::set_var("NYASH_TEST_ENTRY", v);
}
}
if let Some(ret) = matches.get_one::<String>("test-return") {
let v = ret.as_str();
if v == "tests" || v == "original" { std::env::set_var("NYASH_TEST_RETURN", v); }
if v == "tests" || v == "original" {
std::env::set_var("NYASH_TEST_RETURN", v);
}
}
}
if matches.get_flag("macro-preexpand") { std::env::set_var("NYASH_MACRO_SELFHOST_PRE_EXPAND", "1"); }
if matches.get_flag("macro-preexpand-auto") { std::env::set_var("NYASH_MACRO_SELFHOST_PRE_EXPAND", "auto"); }
if matches.get_flag("macro-top-level-allow") { std::env::set_var("NYASH_MACRO_TOPLEVEL_ALLOW", "1"); }
if matches.get_flag("macro-preexpand") {
std::env::set_var("NYASH_MACRO_SELFHOST_PRE_EXPAND", "1");
}
if matches.get_flag("macro-preexpand-auto") {
std::env::set_var("NYASH_MACRO_SELFHOST_PRE_EXPAND", "auto");
}
if matches.get_flag("macro-top-level-allow") {
std::env::set_var("NYASH_MACRO_TOPLEVEL_ALLOW", "1");
}
if let Some(p) = matches.get_one::<String>("macro-profile") {
match p.as_str() {
"dev" | "ci-fast" | "strict" => {

View File

@ -6,7 +6,6 @@ mod args;
mod groups;
mod utils;
/// Command-line configuration structure
#[derive(Debug, Clone)]
pub struct CliConfig {
@ -69,14 +68,22 @@ pub struct CliConfig {
pub macro_ctx_json: Option<String>,
}
pub use groups::{BackendConfig, BuildConfig, CliGroups, DebugConfig, EmitConfig, InputConfig, JitConfig, ParserPipeConfig};
pub use groups::{
BackendConfig, BuildConfig, CliGroups, DebugConfig, EmitConfig, InputConfig, JitConfig,
ParserPipeConfig,
};
impl CliConfig {
pub fn parse() -> Self { args::parse() }
pub fn parse() -> Self {
args::parse()
}
pub fn as_groups(&self) -> CliGroups {
CliGroups {
input: InputConfig { file: self.file.clone(), cli_usings: self.cli_usings.clone() },
input: InputConfig {
file: self.file.clone(),
cli_usings: self.cli_usings.clone(),
},
debug: DebugConfig {
debug_fuel: self.debug_fuel,
dump_ast: self.dump_ast,

View File

@ -6,4 +6,3 @@ pub fn parse_debug_fuel(value: &str) -> Option<usize> {
value.parse::<usize>().ok()
}
}

View File

@ -121,7 +121,9 @@ pub fn await_max_ms() -> u64 {
/// Enable MIR PHI non-generation for Bridge compatibility mode only.
/// フェーズM.2: MirBuilder/LoopBuilderでPHI統一済み、Bridge層の互換性制御のみ
/// Default: PHI-ON (Phase 15 direction), override with NYASH_MIR_NO_PHI=1
pub fn mir_no_phi() -> bool { env_bool("NYASH_MIR_NO_PHI") }
pub fn mir_no_phi() -> bool {
env_bool("NYASH_MIR_NO_PHI")
}
/// Allow verifier to skip SSA/dominance/merge checks for PHI-less MIR.
pub fn verify_allow_no_phi() -> bool {
@ -131,11 +133,15 @@ pub fn verify_allow_no_phi() -> bool {
/// Enable strict edge-copy policy verification in PHI-off mode.
/// When enabled, merge blocks must receive merged values via predecessor copies only,
/// and the merge block itself must not introduce a self-copy to the merged destination.
pub fn verify_edge_copy_strict() -> bool { env_bool("NYASH_VERIFY_EDGE_COPY_STRICT") }
pub fn verify_edge_copy_strict() -> bool {
env_bool("NYASH_VERIFY_EDGE_COPY_STRICT")
}
/// Enforce purity of return blocks: no side-effecting instructions allowed before Return
/// Default: OFF. Enable with NYASH_VERIFY_RET_PURITY=1 in dev/profiling sessions.
pub fn verify_ret_purity() -> bool { env_bool("NYASH_VERIFY_RET_PURITY") }
pub fn verify_ret_purity() -> bool {
env_bool("NYASH_VERIFY_RET_PURITY")
}
// ---- LLVM harness toggle (llvmlite) ----
pub fn llvm_use_harness() -> bool {
@ -172,7 +178,9 @@ pub fn env_bool_default(key: &str, default: bool) -> bool {
/// Global fail-fast policy for runtime fallbacks.
/// Default: ON (true) to prohibit silent/different-route fallbacks in Rust layer.
/// Set NYASH_FAIL_FAST=0 to temporarily allow legacy fallbacks during bring-up.
pub fn fail_fast() -> bool { env_bool_default("NYASH_FAIL_FAST", true) }
pub fn fail_fast() -> bool {
env_bool_default("NYASH_FAIL_FAST", true)
}
// VM legacy by-name call fallback was removed (Phase 2 complete).
@ -204,7 +212,9 @@ pub fn plugin_only() -> bool {
/// Core-13 "pure" mode: after normalization, only the 13 canonical ops are allowed.
/// If enabled, the optimizer will try lightweight rewrites for Load/Store/NewBox/Unary,
/// and the final verifier will reject any remaining non-Core-13 ops.
pub fn mir_core13_pure() -> bool { env_bool("NYASH_MIR_CORE13_PURE") }
pub fn mir_core13_pure() -> bool {
env_bool("NYASH_MIR_CORE13_PURE")
}
/// Enable heuristic pre-pin of comparison operands in if/loop headers.
/// Default: OFF (0). Set NYASH_MIR_PREPIN=1 to enable.
@ -235,14 +245,26 @@ pub fn opt_diag_fail() -> bool {
// ---- Legacy compatibility (dev-only) ----
/// Enable legacy InstanceBox fields (SharedNyashBox map) for compatibility.
/// Default: OFF. Set NYASH_LEGACY_FIELDS_ENABLE=1 to materialize and use legacy fields.
pub fn legacy_fields_enable() -> bool { env_bool("NYASH_LEGACY_FIELDS_ENABLE") }
pub fn legacy_fields_enable() -> bool {
env_bool("NYASH_LEGACY_FIELDS_ENABLE")
}
// ---- GC/Runtime tracing (execution-affecting visibility) ----
pub fn gc_trace() -> bool { env_bool("NYASH_GC_TRACE") }
pub fn gc_barrier_trace() -> bool { env_bool("NYASH_GC_BARRIER_TRACE") }
pub fn runtime_checkpoint_trace() -> bool { env_bool("NYASH_RUNTIME_CHECKPOINT_TRACE") }
pub fn vm_pic_stats() -> bool { env_bool("NYASH_VM_PIC_STATS") }
pub fn vm_vt_trace() -> bool { env_bool("NYASH_VM_VT_TRACE") }
pub fn gc_trace() -> bool {
env_bool("NYASH_GC_TRACE")
}
pub fn gc_barrier_trace() -> bool {
env_bool("NYASH_GC_BARRIER_TRACE")
}
pub fn runtime_checkpoint_trace() -> bool {
env_bool("NYASH_RUNTIME_CHECKPOINT_TRACE")
}
pub fn vm_pic_stats() -> bool {
env_bool("NYASH_VM_PIC_STATS")
}
pub fn vm_vt_trace() -> bool {
env_bool("NYASH_VM_VT_TRACE")
}
pub fn vm_pic_trace() -> bool {
std::env::var("NYASH_VM_PIC_TRACE").ok().as_deref() == Some("1")
}
@ -349,7 +371,11 @@ pub fn extern_trace() -> bool {
// ---- Operator Boxes adopt defaults ----
/// CompareOperator.apply adopt: default ON (prod/devともに採用)
pub fn operator_box_compare_adopt() -> bool {
match std::env::var("NYASH_OPERATOR_BOX_COMPARE_ADOPT").ok().as_deref().map(|v| v.to_ascii_lowercase()) {
match std::env::var("NYASH_OPERATOR_BOX_COMPARE_ADOPT")
.ok()
.as_deref()
.map(|v| v.to_ascii_lowercase())
{
Some(ref s) if s == "0" || s == "false" || s == "off" => false,
Some(ref s) if s == "1" || s == "true" || s == "on" => true,
_ => true, // default ON
@ -357,7 +383,11 @@ pub fn operator_box_compare_adopt() -> bool {
}
/// AddOperator.apply adopt: default OFF順次昇格のため
pub fn operator_box_add_adopt() -> bool {
match std::env::var("NYASH_OPERATOR_BOX_ADD_ADOPT").ok().as_deref().map(|v| v.to_ascii_lowercase()) {
match std::env::var("NYASH_OPERATOR_BOX_ADD_ADOPT")
.ok()
.as_deref()
.map(|v| v.to_ascii_lowercase())
{
Some(ref s) if s == "0" || s == "false" || s == "off" => false,
_ => true, // default ON (promoted after validation)
}
@ -415,9 +445,15 @@ pub fn enable_using() -> bool {
pub fn using_profile() -> String {
std::env::var("NYASH_USING_PROFILE").unwrap_or_else(|_| "dev".to_string())
}
pub fn using_is_prod() -> bool { using_profile().eq_ignore_ascii_case("prod") }
pub fn using_is_ci() -> bool { using_profile().eq_ignore_ascii_case("ci") }
pub fn using_is_dev() -> bool { using_profile().eq_ignore_ascii_case("dev") }
pub fn using_is_prod() -> bool {
using_profile().eq_ignore_ascii_case("prod")
}
pub fn using_is_ci() -> bool {
using_profile().eq_ignore_ascii_case("ci")
}
pub fn using_is_dev() -> bool {
using_profile().eq_ignore_ascii_case("dev")
}
/// Allow `using "path"` statements in source (dev-only by default).
pub fn allow_using_file() -> bool {
// SSOT 徹底: 全プロファイルで既定禁止nyash.toml を唯一の真実に)
@ -432,7 +468,11 @@ pub fn allow_using_file() -> bool {
/// 1) Explicit env `NYASH_USING_AST` = 1/true/on → enabled, = 0/false/off → disabled
/// 2) Default by profile: dev/ci → ON, prod → OFF
pub fn using_ast_enabled() -> bool {
match std::env::var("NYASH_USING_AST").ok().as_deref().map(|v| v.to_ascii_lowercase()) {
match std::env::var("NYASH_USING_AST")
.ok()
.as_deref()
.map(|v| v.to_ascii_lowercase())
{
Some(ref s) if s == "1" || s == "true" || s == "on" => true,
Some(ref s) if s == "0" || s == "false" || s == "off" => false,
_ => !using_is_prod(), // dev/ci → true, prod → false
@ -443,7 +483,11 @@ pub fn using_ast_enabled() -> bool {
/// - dev/ci: default true (allow, with WARN)
/// Override with NYASH_VM_USER_INSTANCE_BOXCALL={0|1}
pub fn vm_allow_user_instance_boxcall() -> bool {
match std::env::var("NYASH_VM_USER_INSTANCE_BOXCALL").ok().as_deref().map(|v| v.to_ascii_lowercase()) {
match std::env::var("NYASH_VM_USER_INSTANCE_BOXCALL")
.ok()
.as_deref()
.map(|v| v.to_ascii_lowercase())
{
Some(ref s) if s == "0" || s == "false" || s == "off" => false,
Some(ref s) if s == "1" || s == "true" || s == "on" => true,
_ => !using_is_prod(),
@ -630,7 +674,10 @@ fn warn_alias_once(alias: &str, primary: &str) {
let set = WARNED_ALIASES.get_or_init(|| Mutex::new(HashSet::new()));
if let Ok(mut s) = set.lock() {
if !s.contains(alias) {
eprintln!("[deprecate/env] '{}' is deprecated; use '{}'", alias, primary);
eprintln!(
"[deprecate/env] '{}' is deprecated; use '{}'",
alias, primary
);
s.insert(alias.to_string());
}
}
@ -652,7 +699,9 @@ pub fn llvm_opt_level() -> String {
/// GateC(Core) route request (primary: NYASH_GATE_C_CORE; alias: HAKO_GATE_C_CORE)
pub fn gate_c_core() -> bool {
if env_bool("NYASH_GATE_C_CORE") { return true; }
if env_bool("NYASH_GATE_C_CORE") {
return true;
}
if env_bool("HAKO_GATE_C_CORE") {
warn_alias_once("HAKO_GATE_C_CORE", "NYASH_GATE_C_CORE");
return true;

View File

@ -14,11 +14,18 @@ pub enum ProviderPolicy {
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FileBoxMode { Auto, CoreRo, PluginOnly }
pub enum FileBoxMode {
Auto,
CoreRo,
PluginOnly,
}
/// Read global provider policy (affects Auto mode only)
pub fn provider_policy_from_env() -> ProviderPolicy {
match std::env::var("HAKO_PROVIDER_POLICY").unwrap_or_else(|_| "strict-plugin-first".to_string()).as_str() {
match std::env::var("HAKO_PROVIDER_POLICY")
.unwrap_or_else(|_| "strict-plugin-first".to_string())
.as_str()
{
"safe-core-first" => ProviderPolicy::SafeCoreFirst,
"static-preferred" => ProviderPolicy::StaticPreferred,
_ => ProviderPolicy::StrictPluginFirst,
@ -27,13 +34,18 @@ pub fn provider_policy_from_env() -> ProviderPolicy {
/// Read FileBox mode from environment variables
pub fn filebox_mode_from_env() -> FileBoxMode {
match std::env::var("NYASH_FILEBOX_MODE").unwrap_or_else(|_| "auto".to_string()).as_str() {
match std::env::var("NYASH_FILEBOX_MODE")
.unwrap_or_else(|_| "auto".to_string())
.as_str()
{
"core-ro" => FileBoxMode::CoreRo,
"plugin-only" => FileBoxMode::PluginOnly,
_ => {
if std::env::var("NYASH_DISABLE_PLUGINS").as_deref() == Ok("1") {
FileBoxMode::CoreRo
} else { FileBoxMode::Auto }
} else {
FileBoxMode::Auto
}
}
}
}
@ -44,4 +56,3 @@ pub fn filebox_mode_from_env() -> FileBoxMode {
pub fn allow_filebox_fallback_override(quiet_pipe: bool) -> bool {
quiet_pipe || crate::config::env::env_bool("NYASH_FILEBOX_ALLOW_FALLBACK")
}

View File

@ -9,7 +9,13 @@ static EMIT_COUNTER: AtomicU64 = AtomicU64::new(0);
/// - NYASH_DEBUG_ENABLE=1 master gate
/// - NYASH_DEBUG_KINDS=resolve,ssa allowed cats (comma-separated)
/// - NYASH_DEBUG_SINK=path file to append JSONL events
pub fn emit(cat: &str, kind: &str, fn_name: Option<&str>, region_id: Option<&str>, meta: serde_json::Value) {
pub fn emit(
cat: &str,
kind: &str,
fn_name: Option<&str>,
region_id: Option<&str>,
meta: serde_json::Value,
) {
if std::env::var("NYASH_DEBUG_ENABLE").ok().as_deref() != Some("1") {
return;
}
@ -25,7 +31,9 @@ pub fn emit(cat: &str, kind: &str, fn_name: Option<&str>, region_id: Option<&str
.unwrap_or(1);
if sample_every > 1 {
let n = EMIT_COUNTER.fetch_add(1, Ordering::Relaxed) + 1;
if n % sample_every != 0 { return; }
if n % sample_every != 0 {
return;
}
}
let sink = match std::env::var("NYASH_DEBUG_SINK") {
Ok(s) if !s.is_empty() => s,
@ -41,7 +49,11 @@ pub fn emit(cat: &str, kind: &str, fn_name: Option<&str>, region_id: Option<&str
"kind": kind,
"meta": meta,
});
if let Ok(mut f) = std::fs::OpenOptions::new().create(true).append(true).open(&sink) {
if let Ok(mut f) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&sink)
{
let _ = writeln!(f, "{}", obj.to_string());
}
}

View File

@ -1,2 +1,2 @@
pub mod log;
pub mod hub;
pub mod log;

View File

@ -36,37 +36,15 @@ 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"];

View File

@ -1,8 +1,8 @@
use std::ffi::{CStr, CString};
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::ffi::{CString, CStr};
pub struct Opts {
pub out: Option<PathBuf>,
@ -13,9 +13,13 @@ pub struct Opts {
fn resolve_ny_llvmc() -> PathBuf {
if let Ok(s) = std::env::var("NYASH_NY_LLVM_COMPILER") {
if !s.is_empty() { return PathBuf::from(s); }
if !s.is_empty() {
return PathBuf::from(s);
}
}
if let Ok(p) = which::which("ny-llvmc") {
return p;
}
if let Ok(p) = which::which("ny-llvmc") { return p; }
PathBuf::from("target/release/ny-llvmc")
}
@ -25,7 +29,10 @@ pub fn mir_json_to_object(mir_json: &str, opts: Opts) -> Result<PathBuf, String>
// Optional provider selection (C-API) — guarded by env flags
// NYASH_LLVM_USE_CAPI=1 and HAKO_V1_EXTERN_PROVIDER_C_ABI=1
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() == Some("1")
&& std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() == Some("1")
&& std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
.ok()
.as_deref()
== Some("1")
{
// Basic shape check first
if !mir_json.contains("\"functions\"") || !mir_json.contains("\"blocks\"") {
@ -37,11 +44,19 @@ pub fn mir_json_to_object(mir_json: &str, opts: Opts) -> Result<PathBuf, String>
let tmp_dir = std::env::temp_dir();
let in_path = tmp_dir.join("hako_llvm_in.json");
{
let mut f = fs::File::create(&in_path).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
f.write_all(mir_json.as_bytes()).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
let mut f = fs::File::create(&in_path)
.map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
f.write_all(mir_json.as_bytes())
.map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
}
let out_path = if let Some(p) = opts.out.clone() {
p
} else {
tmp_dir.join("hako_llvm_out.o")
};
if let Some(parent) = out_path.parent() {
let _ = fs::create_dir_all(parent);
}
let out_path = if let Some(p) = opts.out.clone() { p } else { tmp_dir.join("hako_llvm_out.o") };
if let Some(parent) = out_path.parent() { let _ = fs::create_dir_all(parent); }
match compile_via_capi(&in_path, &out_path) {
Ok(()) => return Ok(out_path),
Err(e) => {
@ -74,26 +89,41 @@ pub fn mir_json_to_object(mir_json: &str, opts: Opts) -> Result<PathBuf, String>
let tmp_dir = std::env::temp_dir();
let in_path = tmp_dir.join("hako_llvm_in.json");
{
let mut f = fs::File::create(&in_path).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
f.write_all(mir_json.as_bytes()).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
let mut f =
fs::File::create(&in_path).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
f.write_all(mir_json.as_bytes())
.map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
}
// Output path
let out_path = if let Some(p) = opts.out.clone() { p } else { tmp_dir.join("hako_llvm_out.o") };
if let Some(parent) = out_path.parent() { let _ = fs::create_dir_all(parent); }
let out_path = if let Some(p) = opts.out.clone() {
p
} else {
tmp_dir.join("hako_llvm_out.o")
};
if let Some(parent) = out_path.parent() {
let _ = fs::create_dir_all(parent);
}
// Build command: ny-llvmc --in <json> --emit obj --out <out>
let mut cmd = Command::new(&ny_llvmc);
cmd.arg("--in").arg(&in_path)
.arg("--emit").arg("obj")
.arg("--out").arg(&out_path);
if let Some(nyrt) = opts.nyrt.as_ref() { cmd.arg("--nyrt").arg(nyrt); }
cmd.arg("--in")
.arg(&in_path)
.arg("--emit")
.arg("obj")
.arg("--out")
.arg(&out_path);
if let Some(nyrt) = opts.nyrt.as_ref() {
cmd.arg("--nyrt").arg(nyrt);
}
if let Some(level) = opts.opt_level.as_ref() {
cmd.env("HAKO_LLVM_OPT_LEVEL", level);
cmd.env("NYASH_LLVM_OPT_LEVEL", level);
}
let status = cmd.status().map_err(|e| format!("[llvmemit/spawn/error] {}", e))?;
let status = cmd
.status()
.map_err(|e| format!("[llvmemit/spawn/error] {}", e))?;
if !status.success() {
let code = status.code().unwrap_or(1);
let tag = format!("[llvmemit/ny-llvmc/failed status={}]", code);
@ -121,19 +151,28 @@ fn compile_via_capi(json_in: &Path, obj_out: &Path) -> Result<(), String> {
unsafe {
// Resolve library path
let mut candidates: Vec<PathBuf> = Vec::new();
if let Ok(p) = std::env::var("HAKO_AOT_FFI_LIB") { if !p.is_empty() { candidates.push(PathBuf::from(p)); } }
if let Ok(p) = std::env::var("HAKO_AOT_FFI_LIB") {
if !p.is_empty() {
candidates.push(PathBuf::from(p));
}
}
candidates.push(PathBuf::from("target/release/libhako_llvmc_ffi.so"));
candidates.push(PathBuf::from("lib/libhako_llvmc_ffi.so"));
let lib_path = candidates.into_iter().find(|p| p.exists())
let lib_path = candidates
.into_iter()
.find(|p| p.exists())
.ok_or_else(|| "FFI library not found (set HAKO_AOT_FFI_LIB)".to_string())?;
let lib = Library::new(lib_path).map_err(|e| format!("dlopen failed: {}", e))?;
// Symbol: int hako_llvmc_compile_json(const char*, const char*, char**)
type CompileFn = unsafe extern "C" fn(*const c_char, *const c_char, *mut *mut c_char) -> c_int;
type CompileFn =
unsafe extern "C" fn(*const c_char, *const c_char, *mut *mut c_char) -> c_int;
let func: libloading::Symbol<CompileFn> = lib
.get(b"hako_llvmc_compile_json\0")
.map_err(|e| format!("dlsym failed: {}", e))?;
let cin = CString::new(json_in.to_string_lossy().as_bytes()).map_err(|_| "invalid json path".to_string())?;
let cout = CString::new(obj_out.to_string_lossy().as_bytes()).map_err(|_| "invalid out path".to_string())?;
let cin = CString::new(json_in.to_string_lossy().as_bytes())
.map_err(|_| "invalid json path".to_string())?;
let cout = CString::new(obj_out.to_string_lossy().as_bytes())
.map_err(|_| "invalid out path".to_string())?;
let mut err_ptr: *mut c_char = std::ptr::null_mut();
// Avoid recursive FFI-in-FFI: force inner AOT to use CLI path
let prev = std::env::var("HAKO_AOT_USE_FFI").ok();
@ -156,17 +195,31 @@ fn compile_via_capi(json_in: &Path, obj_out: &Path) -> Result<(), String> {
);
}
let rc = func(cin.as_ptr(), cout.as_ptr(), &mut err_ptr as *mut *mut c_char);
if let Some(v) = prev { std::env::set_var("HAKO_AOT_USE_FFI", v); } else { std::env::remove_var("HAKO_AOT_USE_FFI"); }
let rc = func(
cin.as_ptr(),
cout.as_ptr(),
&mut err_ptr as *mut *mut c_char,
);
if let Some(v) = prev {
std::env::set_var("HAKO_AOT_USE_FFI", v);
} else {
std::env::remove_var("HAKO_AOT_USE_FFI");
}
if rc != 0 {
let msg = if !err_ptr.is_null() { CStr::from_ptr(err_ptr).to_string_lossy().to_string() } else { "compile failed".to_string() };
let msg = if !err_ptr.is_null() {
CStr::from_ptr(err_ptr).to_string_lossy().to_string()
} else {
"compile failed".to_string()
};
// Free error string (allocated by C side)
if !err_ptr.is_null() {
free(err_ptr as *mut c_void);
}
return Err(msg);
}
if !obj_out.exists() { return Err("object not produced".into()); }
if !obj_out.exists() {
return Err("object not produced".into());
}
Ok(())
}
}
@ -177,13 +230,19 @@ fn compile_via_capi(_json_in: &Path, _obj_out: &Path) -> Result<(), String> {
}
/// Link an object to an executable via C-API FFI bundle.
pub fn link_object_capi(obj_in: &Path, exe_out: &Path, extra_ldflags: Option<&str>) -> Result<(), String> {
pub fn link_object_capi(
obj_in: &Path,
exe_out: &Path,
extra_ldflags: Option<&str>,
) -> Result<(), String> {
// Compute effective ldflags
let mut eff: Option<String> = extra_ldflags.map(|s| s.to_string());
let empty = eff.as_deref().map(|s| s.trim().is_empty()).unwrap_or(true);
if empty {
if let Ok(s) = std::env::var("HAKO_AOT_LDFLAGS") {
if !s.trim().is_empty() { eff = Some(s); }
if !s.trim().is_empty() {
eff = Some(s);
}
}
}
if eff.is_none() {
@ -224,35 +283,68 @@ fn link_via_capi(obj_in: &Path, exe_out: &Path, extra_ldflags: Option<&str>) ->
unsafe {
let mut candidates: Vec<PathBuf> = Vec::new();
if let Ok(p) = std::env::var("HAKO_AOT_FFI_LIB") { if !p.is_empty() { candidates.push(PathBuf::from(p)); } }
if let Ok(p) = std::env::var("HAKO_AOT_FFI_LIB") {
if !p.is_empty() {
candidates.push(PathBuf::from(p));
}
}
candidates.push(PathBuf::from("target/release/libhako_llvmc_ffi.so"));
candidates.push(PathBuf::from("lib/libhako_llvmc_ffi.so"));
let lib_path = candidates.into_iter().find(|p| p.exists())
let lib_path = candidates
.into_iter()
.find(|p| p.exists())
.ok_or_else(|| "FFI library not found (set HAKO_AOT_FFI_LIB)".to_string())?;
let lib = Library::new(lib_path).map_err(|e| format!("dlopen failed: {}", e))?;
// int hako_llvmc_link_obj(const char*, const char*, const char*, char**)
type LinkFn = unsafe extern "C" fn(*const c_char, *const c_char, *const c_char, *mut *mut c_char) -> c_int;
type LinkFn = unsafe extern "C" fn(
*const c_char,
*const c_char,
*const c_char,
*mut *mut c_char,
) -> c_int;
let func: libloading::Symbol<LinkFn> = lib
.get(b"hako_llvmc_link_obj\0")
.map_err(|e| format!("dlsym failed: {}", e))?;
let cobj = CString::new(obj_in.to_string_lossy().as_bytes()).map_err(|_| "invalid obj path".to_string())?;
let cexe = CString::new(exe_out.to_string_lossy().as_bytes()).map_err(|_| "invalid exe path".to_string())?;
let cobj = CString::new(obj_in.to_string_lossy().as_bytes())
.map_err(|_| "invalid obj path".to_string())?;
let cexe = CString::new(exe_out.to_string_lossy().as_bytes())
.map_err(|_| "invalid exe path".to_string())?;
let ldflags_owned;
let cflags_ptr = if let Some(s) = extra_ldflags { ldflags_owned = CString::new(s).map_err(|_| "invalid ldflags".to_string())?; ldflags_owned.as_ptr() } else { std::ptr::null() };
let cflags_ptr = if let Some(s) = extra_ldflags {
ldflags_owned = CString::new(s).map_err(|_| "invalid ldflags".to_string())?;
ldflags_owned.as_ptr()
} else {
std::ptr::null()
};
let mut err_ptr: *mut c_char = std::ptr::null_mut();
// Avoid recursive FFI-in-FFI
let prev = std::env::var("HAKO_AOT_USE_FFI").ok();
std::env::set_var("HAKO_AOT_USE_FFI", "0");
let rc = func(cobj.as_ptr(), cexe.as_ptr(), cflags_ptr, &mut err_ptr as *mut *mut c_char);
if let Some(v) = prev { std::env::set_var("HAKO_AOT_USE_FFI", v); } else { std::env::remove_var("HAKO_AOT_USE_FFI"); }
let rc = func(
cobj.as_ptr(),
cexe.as_ptr(),
cflags_ptr,
&mut err_ptr as *mut *mut c_char,
);
if let Some(v) = prev {
std::env::set_var("HAKO_AOT_USE_FFI", v);
} else {
std::env::remove_var("HAKO_AOT_USE_FFI");
}
if rc != 0 {
let msg = if !err_ptr.is_null() { CStr::from_ptr(err_ptr).to_string_lossy().to_string() } else { "link failed".to_string() };
let msg = if !err_ptr.is_null() {
CStr::from_ptr(err_ptr).to_string_lossy().to_string()
} else {
"link failed".to_string()
};
if !err_ptr.is_null() {
free(err_ptr as *mut c_void);
}
return Err(msg);
}
if !exe_out.exists() { return Err("exe not produced".into()); }
if !exe_out.exists() {
return Err("exe not produced".into());
}
Ok(())
}
}
@ -263,21 +355,31 @@ fn link_via_capi(_obj_in: &Path, _exe_out: &Path, _extra: Option<&str>) -> Resul
}
fn resolve_python3() -> Option<PathBuf> {
if let Ok(p) = which::which("python3") { return Some(p); }
if let Ok(p) = which::which("python") { return Some(p); }
if let Ok(p) = which::which("python3") {
return Some(p);
}
if let Ok(p) = which::which("python") {
return Some(p);
}
None
}
fn resolve_llvmlite_harness() -> Option<PathBuf> {
if let Ok(root) = std::env::var("NYASH_ROOT") {
let p = PathBuf::from(root).join("tools/llvmlite_harness.py");
if p.exists() { return Some(p); }
if p.exists() {
return Some(p);
}
}
let p = PathBuf::from("tools/llvmlite_harness.py");
if p.exists() { return Some(p); }
if p.exists() {
return Some(p);
}
// Also try repo-relative (target may run elsewhere)
let p2 = PathBuf::from("../tools/llvmlite_harness.py");
if p2.exists() { return Some(p2); }
if p2.exists() {
return Some(p2);
}
None
}
@ -303,17 +405,27 @@ fn mir_json_to_object_llvmlite(mir_json: &str, opts: &Opts) -> Result<PathBuf, S
let tmp_dir = std::env::temp_dir();
let in_path = tmp_dir.join("hako_llvm_in.json");
{
let mut f = fs::File::create(&in_path).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
f.write_all(mir_json.as_bytes()).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
let mut f =
fs::File::create(&in_path).map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
f.write_all(mir_json.as_bytes())
.map_err(|e| format!("[llvmemit/tmp/write-failed] {}", e))?;
}
let out_path = if let Some(p) = opts.out.clone() {
p
} else {
tmp_dir.join("hako_llvm_out.o")
};
if let Some(parent) = out_path.parent() {
let _ = fs::create_dir_all(parent);
}
let out_path = if let Some(p) = opts.out.clone() { p } else { tmp_dir.join("hako_llvm_out.o") };
if let Some(parent) = out_path.parent() { let _ = fs::create_dir_all(parent); }
// Run: python3 tools/llvmlite_harness.py --in <json> --out <out>
let status = Command::new(&py)
.arg(&harness)
.arg("--in").arg(&in_path)
.arg("--out").arg(&out_path)
.arg("--in")
.arg(&in_path)
.arg("--out")
.arg(&out_path)
.status()
.map_err(|e| format!("[llvmemit/llvmlite/spawn/error] {}", e))?;
if !status.success() {

View File

@ -11,7 +11,10 @@ pub fn program_json_to_mir_json(program_json: &str) -> Result<String, String> {
}
/// Convert Program(JSON v0) to MIR(JSON v0) with using imports support.
pub fn program_json_to_mir_json_with_imports(program_json: &str, imports: HashMap<String, String>) -> Result<String, String> {
pub fn program_json_to_mir_json_with_imports(
program_json: &str,
imports: HashMap<String, String>,
) -> Result<String, String> {
// Basic header check
if !program_json.contains("\"version\"") || !program_json.contains("\"kind\"") {
let tag = "[mirbuilder/input/invalid] missing version/kind keys";
@ -20,14 +23,15 @@ pub fn program_json_to_mir_json_with_imports(program_json: &str, imports: HashMa
}
// Parse Program(JSON v0) into a MIR Module with imports
let module = match runner::json_v0_bridge::parse_json_v0_to_module_with_imports(program_json, imports) {
Ok(m) => m,
Err(e) => {
let tag = format!("[mirbuilder/parse/error] {}", e);
eprintln!("{}", tag);
return Err(tag);
}
};
let module =
match runner::json_v0_bridge::parse_json_v0_to_module_with_imports(program_json, imports) {
Ok(m) => m,
Err(e) => {
let tag = format!("[mirbuilder/parse/error] {}", e);
eprintln!("{}", tag);
return Err(tag);
}
};
// Emit MIR(JSON) to a temporary file (reuse existing emitter), then read back
let tmp_dir = std::env::temp_dir();
@ -57,8 +61,12 @@ pub fn program_json_to_mir_json_with_imports(program_json: &str, imports: HashMa
if let Some(funcs) = m.get("functions").cloned() {
let v1 = serde_json::json!({"schema_version":"1.0","functions": funcs});
serde_json::to_string(&v1).unwrap_or(s0)
} else { s0 }
} else { s0 }
} else {
s0
}
} else {
s0
}
}
_ => s0,
};
@ -128,7 +136,10 @@ mod tests {
let mir_json = result.unwrap();
// MIR JSON should contain functions
assert!(mir_json.contains("functions"), "MIR JSON should contain functions");
assert!(
mir_json.contains("functions"),
"MIR JSON should contain functions"
);
eprintln!("[test] MIR JSON generated successfully with MatI64 imports");
}
}

Some files were not shown because too many files have changed in this diff Show More