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:
41
AGENTS.md
41
AGENTS.md
@ -146,38 +146,38 @@ PR テンプレ(追加項目)
|
||||
- [ ] 「スモークを通すためだけのハードコード」を入れていません(根治で解決)
|
||||
- [ ] 受け入れ基準に記載の検証を実施しました(dev OFF/ログ確認)
|
||||
|
||||
### 5.2 Rust Freeze Policy(Self‑Host First)
|
||||
### 5.2 Rust Minimal Policy(Self‑Host First, but not Frozen)
|
||||
|
||||
目的: 脱Rustで開発効率を最大化する。Rust層は“最小シーム+バグ修正”のみに留め、分析/ルール/可視化は .hako 側で実装する。
|
||||
目的: 脱Rustを志向しつつも、Stage‑1 / Self‑Host ラインの整備やツールの使いやすさ向上のために、**Rust層で必要な構造的変更やブリッジ強化は積極的に許可する**。分析/ルール/可視化は引き続き .hako 側が主戦場。
|
||||
|
||||
- 原則
|
||||
- Rustは「SSOTランナー導線(resolve→parse→merge)」と「VM/Interpreterの安定化(バグ修正、挙動不変)」のみ。
|
||||
- 新規機能・ルール・可視化・チェックは .hako で実装(自己ホスト)。
|
||||
- 変更は既定OFFのトグルで可逆・小差分・戻し手順あり(AGENTS.md 5.1に準拠)。
|
||||
- Rustは「SSOTランナー導線(resolve→parse→merge)」と「VM/Interpreterの安定化」を軸にしつつ、Stage‑1 CLI / selfhost ブリッジ / エラーメッセージ改善など**開発導線の改善**も扱ってよい。
|
||||
- 新規の言語ルール・静的解析ロジック・ビジネスロジックは、基本的に .hako 側で実装(自己ホスト)。
|
||||
- 変更はできるだけ小さく・可逆に保ちつつ、必要に応じてトグルや dev 用フラグでガード(AGENTS.md 5.1 に準拠)。
|
||||
|
||||
- 許可(最小のRust変更)
|
||||
- ランナー導線の保守(using text‑merge for .hako の維持)。
|
||||
- バグ修正(既定挙動不変、必要ならトグル既定OFF)。
|
||||
- Analyzerシームの提供(抽出のみ):
|
||||
- 例: `--hako-analyze <paths>` → AST/Analysis JSON を stdout/--out に出力。
|
||||
- 判定ロジックは持たない(抽出専用)。
|
||||
- 薄いCLI糖衣:`--hako-check`(内部で `--hako-analyze`→.hakoアナライザをVM経由で実行)。
|
||||
- 許可(Rustでやってよいことの例)
|
||||
- ランナー導線の保守・改善(Stage‑B/Stage‑1/Stage0 の配線、using text‑merge for .hako の維持・整理)。
|
||||
- Stage‑1 / Stage‑B / selfhost 向けのブリッジ強化(子プロセス起動、環境変数の集約、エラー表示の改善)。
|
||||
- バグ修正(既定挙動を壊さない範囲。必要なら既定OFFトグル)。
|
||||
- Analyzerシームの提供(AST/Analysis JSON の抽出専用。判定ロジックは持たない)。
|
||||
- 薄いCLI糖衣:`--hako-check` など、.hako 側のアナライザを呼び出すための CLI エントリ。
|
||||
|
||||
- 禁止/抑制
|
||||
- ルール実装(Lint/命名/依存/特定Box名分岐)をRustに持ち込むこと。
|
||||
- 広域リファクタ・既定挙動変更(凍結)。
|
||||
- 禁止/抑制(依然として .hako 側でやる領域)
|
||||
- Lint / 命名規則 / 依存関係チェック / 特定 Box 名への分岐など、**ルール実装の本体**。
|
||||
- LoopSSA / 数値カーネル / 高レベルの最適化ロジックを Rust に新規実装すること(Self‑Host で十分に表現できる範囲)。
|
||||
- 広域リファクタや既定挙動を大きく変える変更(必要なら Phase/Proposal に切り出してから)。
|
||||
|
||||
- .hako 側の責務(Self‑Host)
|
||||
- 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 側の解析・Stage‑1 ラインが十分に動き始めたので、Rust層は「完全凍結」ではなく、**Self‑Host を支えるための最小+必要な整備を行う層**という扱いに緩和する。日常の機能拡張や言語仕様変更はこれまで通り .hako 側で行い、Rust 変更は「導線の改善」「ブリッジ」「可観測性の向上」にフォーカスする。
|
||||
|
||||
### 6. Fail-Fast with Structure
|
||||
|
||||
@ -379,6 +379,7 @@ Notes
|
||||
- Phase‑17(LoopForm Self‑Hosting & 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
|
||||
|
||||
|
||||
175
CURRENT_TASK.md
175
CURRENT_TASK.md
@ -17,11 +17,37 @@
|
||||
- .hako 側:
|
||||
- Stage‑B コンパイラ本体 / LoopSSA v2 / BreakFinderBox / PhiInjectorBox はまだ部分実装。
|
||||
- JSON v0 / selfhost ルートは Rust 側の LoopForm v2 規約に追いつかせる必要がある。
|
||||
- Stage‑B / FuncScanner ライン:
|
||||
- Phase 25.3 をクローズし、`stageb_fib_program_defs_canary_vm.sh` が緑(`defs` に `TestBox.fib/Main.main`、fib.body.body[*] に `Loop`)。
|
||||
- Stage‑B は block パーサ優先 + defs を Block 包みで構造化。次手: Stage‑1 UsingResolver ループの Region+next_i 揃え / Stage‑1 CLI program-json selfhost 準備。
|
||||
|
||||
---
|
||||
|
||||
## 1. 最近完了した重要タスク
|
||||
|
||||
### 1-0. Phase 25.3 — FuncScanner / Stage‑B defs 安定化(完了)
|
||||
|
||||
**目的**
|
||||
- Stage‑B / 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 / Stage‑B 経由の fib defs ラインは LoopForm v2 + LoopSnapshotMergeBox 上で構造的に安定したとみなし、Phase 25.3 はクローズ。
|
||||
- 次フェーズの入口が整理できたので、Stage‑1 UsingResolver ループ(Region+next_i 形)と Stage‑1 CLI program-json/selfhost 導線に着手可能。
|
||||
|
||||
### 1-1. Phase 25.1m — Static Method / LoopForm v2 continue + PHI Fix(完了)
|
||||
|
||||
**目的**
|
||||
@ -138,6 +164,26 @@
|
||||
|
||||
---
|
||||
|
||||
### 1-4. Phase 25.1A‑3 — Stage‑1 CLI bridge(stub 実装)
|
||||
|
||||
- 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`: Stage‑1 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.1A‑4 — Using SSOT 薄設計+BuildBox include 除去
|
||||
|
||||
- SSOT: `lang/src/using/resolve_ssot_box.hako` に README 相当のコメントと I/F(resolve_modules/resolve_prefix)を追加。現状は no-op だが「using をここで扱う」窓口を固定。
|
||||
- Stage1UsingResolver: `resolve_for_program_json` を追加し、SSOT を呼ぶ導線だけ確保(現状は透過返し)。
|
||||
- BuildBox: 先頭の `include` を削除し、`using lang.compiler.entry.bundle_resolver as BundleResolver` に置換(Stage‑B パーサが 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. Stage‑B 本体の型エラー(`String > Integer(13)`)
|
||||
@ -231,26 +277,50 @@
|
||||
## 3. 次にやること(候補タスク)
|
||||
|
||||
ここから先は「どのフェーズを進めるか」をそのときの優先度で選ぶ感じだよ。
|
||||
Rust 側は LoopForm v2 / Stage‑B fib / Stage‑1 UsingResolver 構造テストまで一通り整ったので、当面は Stage‑1 CLI / selfhost ラインの小さな箱から進める。
|
||||
|
||||
1. **Stage‑B / 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 を避ける。
|
||||
- Stage‑B → Stage‑1 パーサ経路は、`ParserBox.parse_program2` のトップレベルループではなく、`parse_block2` で `Main.main` の本文をブロックとしてパースし、
|
||||
Program(JSON v0) を自前で組み立てる構造に変更(Stage‑B 固まり問題を回避)。
|
||||
### A. Stage‑1 CLI / Stage0 ブリッジ(Phase 25.1 — いまここ)
|
||||
|
||||
2. **Stage‑B 再入ガード `env.set/2` の整理**
|
||||
- 再入ガードを Box 内 state で持つ方向か、Rust 側に dev 専用 extern を追加するかを決める。
|
||||
- どちらにしても「本番経路(prod ランナー)には影響しない」ようフラグでガードする。
|
||||
- A‑1: Stage‑1 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 引数も Stage‑1 側に転送。
|
||||
- 再入防止として子プロセスには `NYASH_STAGE1_CLI_CHILD=1` を付与(子側からは Rust ブリッジを素通り)。
|
||||
- A‑2: Stage‑1 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`)を追記。
|
||||
- A‑3: 次ステップ(未着手)
|
||||
- Stage‑1 CLI skeleton に Stage‑B/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 に輸入。
|
||||
- Stage‑B Test2 / selfhost CLI の JSON→MIR 経路で MirVerifier 緑を目標にする。
|
||||
### B. Stage‑1 UsingResolver / LoopForm v2 ライン(Phase 25.1e 系フォロー)
|
||||
|
||||
4. **Selfhost / CLI 周辺のテスト整理**
|
||||
- 代表的な selfhost / Stage‑B / Stage‑1 CLI ケースに対して、
|
||||
「Phase 25.1m でどこまで緑になったか」「どこから先が 25.1k 以降の仕事か」を tests / tools 側で見える化する。
|
||||
- B‑1: 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`(Stage‑B body_src を受けて prefix を返す)、`_collect_using_entries`(JSON スキャンして entries を集める)、`_build_module_map`(modules_list を map 化)を明記済み。
|
||||
- `HAKO_STAGEB_APPLY_USINGS=0` の時は prefix を空 string にしつつ、depth ガードだけは走らせる仕様もコメントで固定。
|
||||
- B‑2: UsingResolver 構造テスト(ループ形/PHI)の拡充(DONE)
|
||||
- `src/tests/mir_stage1_using_resolver_verify.rs` に Region+next_i ループと early-exit JSON スキャンパターンの軽量テストを追加。
|
||||
- これらは cargo test 経路で常時緑を維持し、v2 quick スモークへの昇格は「実行時間とノイズを見ながら後続フェーズで検討」という扱いにする(docs にメモ済み)。
|
||||
- B‑3: 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. Stage‑B / LoopSSA / Selfhost まわり(中期タスク)
|
||||
|
||||
- C‑1: Stage‑B 再入ガード `env.set/2` の整理(据え置き)
|
||||
- dev 専用 extern を Rust 側に追加するか、Stage‑B 箱側で state に寄せるかを決める必要があるが、現在はループ/PHI ラインを優先し保留。
|
||||
- このタスクに着手するときは「prod/CI 経路から完全に切り離した dev ガード」として設計する。
|
||||
- C‑2: .hako LoopSSA v2 実装(Rust LoopForm v2 への追従)
|
||||
- Rust LoopForm v2 の Carrier/Pinned/BodyLocalInOut モデルを `.hako` の LoopSSA に輸入し、Stage‑B Test2 / selfhost CLI でも MirVerifier 緑を目指す中〜長期タスク。
|
||||
- 具体的な対象: `lang/src/compiler/builder/ssa/loopssa.hako` と Stage‑B/BreakFinder 周辺の region 化済みループ。
|
||||
- C‑3: Selfhost / CLI 周辺のテスト整理
|
||||
- 代表的な selfhost / Stage‑B / Stage‑1 CLI ケースを tests/tools 側でタグ付け(quick/integration/selfhost)、Phase 25.1 の「どこまでが Rust 側」「どこからが Stage1 側」を見える化する。
|
||||
|
||||
---
|
||||
|
||||
@ -268,44 +338,51 @@
|
||||
次にどの箱から攻めるか決めたら、ここに箇条書きで足していこうね。
|
||||
|
||||
|
||||
## 2-? Stage‑B FuncScanner / LoopSnapshotMergeBox / MIR ロガー(in progress)
|
||||
## 3. これからやるタスクのラフ一覧(25.1 / Stage‑1 系)
|
||||
|
||||
- StageBFuncScannerBox を `compiler_stageb.hako` 内に追加し、StageBDriverBox.main からは FuncScannerBox ではなくこちらを呼ぶ構成にしている。
|
||||
- VM 側の Undefined value(BreakFinderBox._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=10(0+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 を比較しつつ、
|
||||
Stage‑B body 抽出 → StageBFuncScannerBox.scan_all_boxes → Program(JSON v0).defs 注入の導線自体を見直してほしい(LoopForm/PHI/スナップショット側は LoopSnapshotMergeBox で安定化済み)。
|
||||
- Phase 25.3 では、FuncScanner / Stage‑B ラインの観測専用として `.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 helper(FuncScannerBox._parse_params / _strip_comments など)呼び出しとして扱う。
|
||||
- これにより static box 文脈で「実体のない me を receiver にした Method call」が生成される経路が閉じられ、FuncScannerBox._scan_methods/4 系の UndefinedValue は Rust 側で構造的に防止されている。
|
||||
ここから先は「まず設計と箱分割を書いてから実装」という方針で進めるタスク群だよ。
|
||||
|
||||
### A. Rust 層解析(LoopForm v2 / JSON v0 / Stage‑1 観測)
|
||||
|
||||
## 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` を「Stage‑1 から見た導線」として読み直し、どのレイヤで 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: Stage‑1 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. Stage‑1 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. Stage‑1 CLI / program-json / mir-json Self‑Host 準備
|
||||
|
||||
- C-1: Stage‑1 CLI インターフェースの設計メモ
|
||||
- `.hako → Program(JSON)` / `Program(JSON) → MIR(JSON)` / `MIR(JSON) → 実行` を Stage‑1 側からどう呼び出すか(関数名・引数レベル)を docs に先に書く。
|
||||
- C-2: Stage‑B → Stage‑1 データフロー図
|
||||
- `compiler_stageb.hako` → Program(JSON v0) → Stage‑1 UsingResolver → MirBuilder までのパイプラインを Phase‑25.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` など)で決定性を確認するかを Phase‑25.x の設計メモとして書いておく。
|
||||
- D-2: dev 専用 / flaky テストの扱い方針
|
||||
- どのテストが dev ignore(手動実行用)で、いつ/何を満たしたら常時有効に戻すかを、このファイルと関連 README に明確に残す。
|
||||
|
||||
このセクションは「すぐ実装する TODO ではなく、25.1〜25.x ラインで踏むべき設計タスク一覧」として使う予定だよ。
|
||||
(静的 me-call 修正の参考メモはここに残しつつ、別セクションは整理済み)
|
||||
|
||||
## 3. Phase 25.1q — LoopForm Front Unification(DONE / follow-up 別タスクへ)
|
||||
|
||||
|
||||
@ -43,9 +43,9 @@ Status: design+partial implementation(Stage1 ビルド導線の初期版まで
|
||||
- Rust VM/LLVM のコア(MIR インタプリタ/コード生成)の提供。
|
||||
- Stage1 で AOT されたコア関数(後述)を呼び出すランチャ。
|
||||
|
||||
**禁止/抑制:**
|
||||
- パーサ高レイヤ/Stage‑B/MirBuilder/AotPrep/numeric core のロジックを Rust 側に新規追加しない。
|
||||
- 新しい Box 実装やランタイム機能を Rust に持ち込まない(Phase 25 Rust Freeze を継続)。
|
||||
**禁止/抑制(緩和版):**
|
||||
- パーサ高レイヤ/Stage‑B/MirBuilder/AotPrep/numeric core の**意味論そのもの**を Rust 側に新規実装しない(Self‑Host の責務)。
|
||||
- 新しい Box 実装や高レベルランタイム機能を Rust に持ち込むのは原則避ける(ただし Stage‑1 ブリッジやエラーログ改善など、導線・可観測性に必要な最小限の変更は許可)。
|
||||
|
||||
### Stage1 — Hakorune Selfhost Binary
|
||||
|
||||
@ -157,6 +157,11 @@ Status: design+partial implementation(Stage1 ビルド導線の初期版まで
|
||||
- [ ] Stage0→Stage1→Stage1' のビルドシーケンスを文章で定義(どの組み合わせで自己一致チェックを行うか)。
|
||||
- [ ] 「普段使うのは Stage1」「問題発生時に Stage0 から再生成」という運用パターンを docs に記載。
|
||||
|
||||
### E. Stage‑1 UsingResolver / LoopForm v2 対応(設計)
|
||||
|
||||
- [x] 設計ドラフトを追加(`stage1-usingresolver-loopform.md`)。Region+next_i 形ループと Carrier/Pinned 対応の指針を明文化。
|
||||
- [x] Rust 側の観測結果を反映し、具体的なリライト手順とテスト項目を更新する(JSON→LoopForm v2 導線/Stage‑B→Stage‑1 データフローのテキスト図を追記)。
|
||||
|
||||
## 実装チェックリスト(25.1 実行順案)
|
||||
|
||||
### 1. バイナリ命名と役割の明確化
|
||||
|
||||
@ -112,6 +112,7 @@ source -> | Stage-B (block) | --> | Stage-1 UsingRes | --> | MirBuilder (.ha
|
||||
### ループ洗い出しメモ(entry / pipeline_v2)
|
||||
- entry UsingResolver(lang/src/compiler/entry/using_resolver_box.hako)
|
||||
- Region+next_i 化済み: entries イテレーション / JSON スキャン / modules_list 分割
|
||||
- 追加テスト: modules_list 分割ループ(start/next_start)をそのまま MIR 化できることを `mir_stage1_using_resolver_module_map_regionized_verifies` で固定。
|
||||
- 残り: なし(現状の 3 ループは all Region 形)
|
||||
- pipeline_v2 UsingResolver(lang/src/compiler/pipeline_v2/using_resolver_box.hako)
|
||||
- 役割: modules_json 上で alias を解決する stateful helper(テキスト収集は entry 側)。
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
# Phase 25.3 — FuncScanner / Stage‑B defs 安定化
|
||||
|
||||
Status: 完了(Stage‑B fib defs canary 緑/2025-11 時点)
|
||||
|
||||
## スコープ / ゴール
|
||||
|
||||
- 対象レイヤ
|
||||
@ -22,6 +24,30 @@
|
||||
- Loop/PHI の意味論は **LoopFormBuilder + LoopSnapshotMergeBox** に完全委譲したまま、
|
||||
- FuncScanner / Stage‑B は「テキストスキャン+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` ノードが含まれる状態を固定。
|
||||
- Stage‑B 本線の整理:
|
||||
- `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\" }]` を追加する形で注入する。
|
||||
- Stage‑B / FuncScanner 経由の `defs[*].body` は必ず `{\"type\":\"Block\",\"body\":[Stmt...]}` でラップし、AOT/VM 側からは「通常の Block ノード」として読めるようにする。
|
||||
- フロント/バックエンドの責務分離:
|
||||
- Stage‑B / FuncScanner: `.hako` テキスト → Program(JSON v0) まで(ループ/PHI の意味論は持たない)。
|
||||
- Rust 側 LoopForm v2 / JSON v0 Bridge: Program(JSON v0) → MIR/AOT まで(Loop/PHI/SSA の SSOT)。
|
||||
- Stage‑B トグル整理(抜粋):
|
||||
- `HAKO_STAGEB_FUNC_SCAN=1`(既定): defs を常に埋める。`0` で Stage‑B からの 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/パーサ調査時だけ opt‑in で使う想定。
|
||||
|
||||
## すでに前提としている状態
|
||||
|
||||
- LoopForm v2 / PHI / snapshot:
|
||||
@ -118,27 +144,28 @@ FuncScanner / Stage‑B のデバッグ時には、`scan_all_boxes` のループ
|
||||
- ここでの主なバグは「SSA ではなく、Stage‑B が fib ソースから defs を組み立てきれていないこと」なので、
|
||||
ループ構造や LoopForm には手を入れず、Stage‑B 側のテキスト処理と 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 / Stage‑B defs もこの土台の上に載せた」
|
||||
というつながりを 1〜2 行で追記する。
|
||||
- `CURRENT_TASK.md` には:
|
||||
- Phase 25.3 のタスク完了状況(FuncScanner fib / Stage‑B fib canary 緑)と、
|
||||
- その後に繋がる Stage‑1 UsingResolver / Stage‑1 CLI self‑host ラインへのブリッジ
|
||||
を短く整理しておく。
|
||||
今回の結果:
|
||||
- `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 / Stage‑B 経由の fib defs ラインは LoopForm v2 + LoopSnapshotMergeBox の上で構造的に安定したとみなせる。
|
||||
|
||||
ドキュメント側:
|
||||
- Phase 25.1q / 25.2 の README には、
|
||||
- 「loop/PHI/スナップショットの SSOT は LoopForm v2 + LoopSnapshotMergeBox」
|
||||
- 「Phase 25.3 で FuncScanner / Stage‑B defs もこの土台の上に載せた」
|
||||
という関係を 1〜2 行で追記済み。
|
||||
- `CURRENT_TASK.md` では Phase 25.3 を「Stage‑B fib defs canary 緑」まで完了したフェーズとして整理し、
|
||||
次フェーズを Stage‑1 UsingResolver ループ整理 / Stage‑1 CLI program-json selfhost 導線に設定した。
|
||||
|
||||
### 5. mir_funcscanner_skip_ws 系テストの扱い(flaky → dev 専用)
|
||||
|
||||
|
||||
@ -69,7 +69,7 @@ Related docs:
|
||||
- `IntArrayCore`(数値一次元配列コア)
|
||||
- `MatI64`(行列箱・i64版)
|
||||
などを、「Rust プラグイン実装」ではなく **Hakorune 実装+ごく薄い intrinsic** に置き換えるための設計ロードマップを固める。
|
||||
- 新しい箱・数値カーネル・標準ランタイム機能は **原則 .hako で実装する** 方針を明文化し、「Rust Freeze Policy(Self‑Host First)」を Phase 25 で具体化する。
|
||||
- 新しい箱・数値カーネル・標準ランタイム機能は **原則 .hako で実装する** 方針を明文化し、「Rust Minimal Policy(Self‑Host 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 に準拠)。
|
||||
|
||||
### Ring1(Hakorune / Nyash ― System サブセット)
|
||||
|
||||
@ -112,11 +112,11 @@ Related docs:
|
||||
|
||||
Phase 25 は「設計とロードマップの確定」が主目的。実装・移行作業自体は後続フェーズ(22.x/26.x など)で分割実施する。
|
||||
|
||||
### 1) Rust Freeze の明文化とチェックリスト
|
||||
### 1) Rust Minimal Policy の明文化とチェックリスト
|
||||
|
||||
- 既存の「Rust Freeze Policy(Self‑Host First)」を、ランタイム/箱/数値系に特化して再整理:
|
||||
- 既存の「Rust Freeze Policy(Self‑Host First)」を、「Self‑Host を支える最小+必要な整備は許可する」Rust Minimal Policy として再整理:
|
||||
- 新規 Box / ランタイム機能は Rust ではなく .hako で実装する。
|
||||
- Rust 変更は「最小の intrinsic 追加」か「バグ修正」に限定。
|
||||
- Rust 変更は「最小の intrinsic 追加」「Stage‑1/Stage‑B ブリッジの改善」「エラーログ・可観測性の向上」か「バグ修正」に限定。
|
||||
- PR / フェーズ用チェックリスト案を作成:
|
||||
- [ ] この変更は Ring0 の責務か?(VM/allocator/LLVM/OS FFI のみ)
|
||||
- [ ] 新しい Box/アルゴリズムを Rust に追加していないか?
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Stage1 Hakorune CLI Design(Proposal)
|
||||
|
||||
Status: design-only(Phase 25.1 時点では仕様策定と導線の整理まで)
|
||||
Status: design-only + Stage0 stub 実装済み(Phase 25.1 時点では仕様策定と導線の整理まで。selfhost EXE 本体は未実装)
|
||||
|
||||
## ゴール
|
||||
|
||||
@ -15,6 +15,8 @@ Status: design-only(Phase 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`)はまだ設計段階。
|
||||
- Stage1(Hakorune 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` が環境セットとバイナリ検出を担当)。
|
||||
|
||||
#### デバッグ Tips(Phase 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
|
||||
|
||||
@ -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 / Stage‑B では、skip_whitespace や scan_all_boxes のループ頭・出口に挿入して、ValueId と値の流れを追跡する用途で使用している。
|
||||
|
||||
## 🔌 **プラグインテスター(BID-FFI診断ツール)**
|
||||
```bash
|
||||
|
||||
@ -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 Stage‑1 JSON v0 (Program)
|
||||
// Bridge(JSON v0) が Program v0 を受け取り MIR に lowering するため、ここでは AST(JSON v0) を出力する。
|
||||
// 既定で MIR 直出力は行わない(重い経路を避け、一行出力を保証)。
|
||||
// Stage‑B/selfhost: body_src は Main.main 内のブロック本文なので、ParserBox の block パーサ
|
||||
// を直接使って Program(JSON) を構成する(トップレベル parser_loop は使わない)。
|
||||
local block_res = p.parse_block2(body_src, 0)
|
||||
// Stage‑B/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 + "]"
|
||||
|
||||
@ -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 Stage‑B.
|
||||
// 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 Stage‑1 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 は空のまま返す(Stage‑B 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
|
||||
|
||||
@ -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 } }
|
||||
|
||||
@ -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 boundary(Stage‑B 安定性のためここで実装)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 Stage‑B / 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)
|
||||
// Stage‑B 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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 ""
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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" {
|
||||
|
||||
@ -9,6 +9,18 @@
|
||||
// - ctx.using_paths: Array<String>(将来のヒント/本箱では純粋合成のみ)
|
||||
// - ctx.cwd: String(相対の基準)
|
||||
|
||||
// Quick README (Phase 25.1 using設計)
|
||||
// - 役割: using 解決の唯一の窓口(SSOT)。parser/Stage‑B/Stage‑1 は 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_json(nyash.toml 相当)と using_entries_json(UsingCollector 出力)を突き合わせ、
|
||||
// 「解決済み 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 Stage‑1 entry.
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
113
src/ast/nodes.rs
113
src/ast/nodes.rs
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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(());
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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" => {
|
||||
|
||||
@ -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 UTF‑8, 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),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -12,4 +12,3 @@ pub fn normalize_arity_suffix(name: &str) -> &str {
|
||||
None => name,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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
|
||||
///
|
||||
|
||||
@ -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
|
||||
///
|
||||
|
||||
@ -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
|
||||
///
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
///
|
||||
|
||||
@ -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
|
||||
///
|
||||
|
||||
@ -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?
|
||||
|
||||
@ -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
|
||||
///
|
||||
|
||||
@ -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
|
||||
///
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 =====
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -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 (Gate‑C 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();
|
||||
|
||||
@ -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};
|
||||
|
||||
|
||||
@ -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};
|
||||
|
||||
|
||||
@ -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};
|
||||
|
||||
|
||||
@ -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 file_box::FileBox;
|
||||
pub use error_box::ErrorBox;
|
||||
pub use file_box::FileBox;
|
||||
pub use integer_box::IntegerBox;
|
||||
pub use string_box::StringBox;
|
||||
pub use void_box::VoidBox;
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -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};
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -3,10 +3,10 @@
|
||||
// 参考: 既存Boxの設計思想
|
||||
|
||||
// SSOT provider design (ring‑0/1) — modules are currently placeholders
|
||||
pub mod provider; // trait FileIo / FileCaps / FileError
|
||||
pub mod core_ro; // Core read‑only 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 read‑only 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",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
141
src/cli/args.rs
141
src/cli/args.rs
@ -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" => {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -6,4 +6,3 @@ pub fn parse_debug_fuel(value: &str) -> Option<usize> {
|
||||
value.parse::<usize>().ok()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
/// Gate‑C(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;
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
pub mod log;
|
||||
pub mod hub;
|
||||
pub mod log;
|
||||
|
||||
@ -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",
|
||||
];
|
||||
pub static SYNTAX_ALLOWED_BINOPS: &[&str] = &[
|
||||
"add",
|
||||
"sub",
|
||||
"mul",
|
||||
"div",
|
||||
"and",
|
||||
"or",
|
||||
"eq",
|
||||
"ne",
|
||||
"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"];
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
pub mod mir_builder;
|
||||
pub mod llvm_codegen;
|
||||
|
||||
pub mod mir_builder;
|
||||
|
||||
@ -50,7 +50,7 @@ impl Clone for InstanceBox {
|
||||
class_name: self.class_name.clone(),
|
||||
fields_ng: Arc::clone(&self.fields_ng), // Shared reference
|
||||
methods: Arc::clone(&self.methods),
|
||||
inner_content: None, // inner_content cannot be cloned (Box<dyn>)
|
||||
inner_content: None, // inner_content cannot be cloned (Box<dyn>)
|
||||
base: BoxBase::new(), // Fresh base for clone
|
||||
finalized: Arc::clone(&self.finalized),
|
||||
fields: self.fields.as_ref().map(Arc::clone),
|
||||
@ -108,7 +108,11 @@ impl InstanceBox {
|
||||
base: BoxBase::new(),
|
||||
finalized: Arc::new(Mutex::new(false)),
|
||||
// レガシー互換フィールド(既定OFF)
|
||||
fields: if legacy_enabled { Some(Arc::new(Mutex::new(legacy_field_map))) } else { None },
|
||||
fields: if legacy_enabled {
|
||||
Some(Arc::new(Mutex::new(legacy_field_map)))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
init_field_order: fields,
|
||||
weak_fields_union: std::collections::HashSet::new(),
|
||||
in_finalization: Arc::new(Mutex::new(false)),
|
||||
@ -240,10 +244,7 @@ impl InstanceBox {
|
||||
}
|
||||
|
||||
/// レガシー互換:weak field取得
|
||||
pub fn get_weak_field(
|
||||
&self,
|
||||
field_name: &str,
|
||||
) -> Option<NyashValue> {
|
||||
pub fn get_weak_field(&self, field_name: &str) -> Option<NyashValue> {
|
||||
self.get_field_ng(field_name)
|
||||
}
|
||||
|
||||
|
||||
16
src/lib.rs
16
src/lib.rs
@ -65,7 +65,7 @@ pub mod runtime;
|
||||
// Unified Grammar scaffolding
|
||||
pub mod grammar;
|
||||
pub mod syntax; // syntax sugar config and helpers
|
||||
// Execution runner (CLI coordinator)
|
||||
// Execution runner (CLI coordinator)
|
||||
pub mod runner;
|
||||
pub mod runner_hv1_inline_guard {}
|
||||
pub mod using; // using resolver scaffolding (Phase 15)
|
||||
@ -76,7 +76,9 @@ pub mod host_providers;
|
||||
pub mod providers;
|
||||
|
||||
// C‑ABI PoC shim (20.36/20.37)
|
||||
pub mod abi { pub mod nyrt_shim; }
|
||||
pub mod abi {
|
||||
pub mod nyrt_shim;
|
||||
}
|
||||
|
||||
// Expose the macro engine module under a raw identifier; the source lives under `src/macro/`.
|
||||
#[path = "macro/mod.rs"]
|
||||
@ -91,12 +93,8 @@ pub mod tests;
|
||||
// Re-export main types for easy access
|
||||
pub use ast::{ASTNode, BinaryOperator, LiteralValue};
|
||||
pub use box_arithmetic::{AddBox, CompareBox, DivideBox, ModuloBox, MultiplyBox, SubtractBox};
|
||||
pub use box_trait::{BoolBox, IntegerBox, NyashBox, StringBox, VoidBox};
|
||||
pub use environment::{Environment, PythonCompatEnvironment};
|
||||
pub use box_factory::RuntimeError;
|
||||
pub use parser::{NyashParser, ParseError};
|
||||
pub use tokenizer::{NyashTokenizer, Token, TokenType};
|
||||
pub use type_box::{MethodSignature, TypeBox, TypeRegistry}; // 🌟 TypeBox exports
|
||||
pub use box_trait::{BoolBox, IntegerBox, NyashBox, StringBox, VoidBox};
|
||||
pub use boxes::console_box::ConsoleBox;
|
||||
pub use boxes::debug_box::DebugBox;
|
||||
pub use boxes::map_box::MapBox;
|
||||
@ -106,8 +104,12 @@ pub use boxes::random_box::RandomBox;
|
||||
pub use boxes::sound_box::SoundBox;
|
||||
pub use boxes::time_box::{DateTimeBox, TimeBox, TimerBox};
|
||||
pub use channel_box::{ChannelBox, MessageBox};
|
||||
pub use environment::{Environment, PythonCompatEnvironment};
|
||||
pub use instance_v2::InstanceBox; // 🎯 新実装テスト(nyash_rustパス使用)
|
||||
pub use method_box::{BoxType, EphemeralInstance, FunctionDefinition, MethodBox};
|
||||
pub use parser::{NyashParser, ParseError};
|
||||
pub use tokenizer::{NyashTokenizer, Token, TokenType};
|
||||
pub use type_box::{MethodSignature, TypeBox, TypeRegistry}; // 🌟 TypeBox exports
|
||||
|
||||
pub use value::NyashValue;
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use nyash_rust::ast::{ASTNode, BinaryOperator, LiteralValue, Span, UnaryOperator};
|
||||
use serde_json::{json, Value};
|
||||
use nyash_rust::ast::{ASTNode, LiteralValue, BinaryOperator, UnaryOperator, Span};
|
||||
|
||||
pub fn ast_to_json(ast: &ASTNode) -> Value {
|
||||
match ast.clone() {
|
||||
@ -7,7 +7,9 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
|
||||
"kind": "Program",
|
||||
"statements": statements.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>()
|
||||
}),
|
||||
ASTNode::Loop { condition, body, .. } => json!({
|
||||
ASTNode::Loop {
|
||||
condition, body, ..
|
||||
} => json!({
|
||||
"kind": "Loop",
|
||||
"condition": ast_to_json(&condition),
|
||||
"body": body.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>()
|
||||
@ -27,18 +29,32 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
|
||||
"target": ast_to_json(&target),
|
||||
"value": ast_to_json(&value),
|
||||
}),
|
||||
ASTNode::Local { variables, initial_values, .. } => json!({
|
||||
ASTNode::Local {
|
||||
variables,
|
||||
initial_values,
|
||||
..
|
||||
} => json!({
|
||||
"kind": "Local",
|
||||
"variables": variables,
|
||||
"inits": initial_values.into_iter().map(|opt| opt.map(|v| ast_to_json(&v))).collect::<Vec<_>>()
|
||||
}),
|
||||
ASTNode::If { condition, then_body, else_body, .. } => json!({
|
||||
ASTNode::If {
|
||||
condition,
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => json!({
|
||||
"kind": "If",
|
||||
"condition": ast_to_json(&condition),
|
||||
"then": then_body.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>(),
|
||||
"else": else_body.map(|v| v.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>()),
|
||||
}),
|
||||
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => json!({
|
||||
ASTNode::TryCatch {
|
||||
try_body,
|
||||
catch_clauses,
|
||||
finally_body,
|
||||
..
|
||||
} => json!({
|
||||
"kind": "TryCatch",
|
||||
"try": try_body.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>(),
|
||||
"catch": catch_clauses.into_iter().map(|cc| json!({
|
||||
@ -48,7 +64,14 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
|
||||
})).collect::<Vec<_>>(),
|
||||
"cleanup": finally_body.map(|v| v.into_iter().map(|s| ast_to_json(&s)).collect::<Vec<_>>())
|
||||
}),
|
||||
ASTNode::FunctionDeclaration { name, params, body, is_static, is_override, .. } => json!({
|
||||
ASTNode::FunctionDeclaration {
|
||||
name,
|
||||
params,
|
||||
body,
|
||||
is_static,
|
||||
is_override,
|
||||
..
|
||||
} => json!({
|
||||
"kind": "FunctionDeclaration",
|
||||
"name": name,
|
||||
"params": params,
|
||||
@ -58,24 +81,38 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
|
||||
}),
|
||||
ASTNode::Variable { name, .. } => json!({"kind":"Variable","name":name}),
|
||||
ASTNode::Literal { value, .. } => json!({"kind":"Literal","value": lit_to_json(&value)}),
|
||||
ASTNode::BinaryOp { operator, left, right, .. } => json!({
|
||||
ASTNode::BinaryOp {
|
||||
operator,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} => json!({
|
||||
"kind":"BinaryOp",
|
||||
"op": bin_to_str(&operator),
|
||||
"left": ast_to_json(&left),
|
||||
"right": ast_to_json(&right),
|
||||
}),
|
||||
ASTNode::UnaryOp { operator, operand, .. } => json!({
|
||||
ASTNode::UnaryOp {
|
||||
operator, operand, ..
|
||||
} => json!({
|
||||
"kind":"UnaryOp",
|
||||
"op": un_to_str(&operator),
|
||||
"operand": ast_to_json(&operand),
|
||||
}),
|
||||
ASTNode::MethodCall { object, method, arguments, .. } => json!({
|
||||
ASTNode::MethodCall {
|
||||
object,
|
||||
method,
|
||||
arguments,
|
||||
..
|
||||
} => json!({
|
||||
"kind":"MethodCall",
|
||||
"object": ast_to_json(&object),
|
||||
"method": method,
|
||||
"arguments": arguments.into_iter().map(|a| ast_to_json(&a)).collect::<Vec<_>>()
|
||||
}),
|
||||
ASTNode::FunctionCall { name, arguments, .. } => json!({
|
||||
ASTNode::FunctionCall {
|
||||
name, arguments, ..
|
||||
} => json!({
|
||||
"kind":"FunctionCall",
|
||||
"name": name,
|
||||
"arguments": arguments.into_iter().map(|a| ast_to_json(&a)).collect::<Vec<_>>()
|
||||
@ -88,7 +125,12 @@ pub fn ast_to_json(ast: &ASTNode) -> Value {
|
||||
"kind":"Map",
|
||||
"entries": entries.into_iter().map(|(k,v)| json!({"k":k,"v":ast_to_json(&v)})).collect::<Vec<_>>()
|
||||
}),
|
||||
ASTNode::MatchExpr { scrutinee, arms, else_expr, .. } => json!({
|
||||
ASTNode::MatchExpr {
|
||||
scrutinee,
|
||||
arms,
|
||||
else_expr,
|
||||
..
|
||||
} => json!({
|
||||
"kind":"MatchExpr",
|
||||
"scrutinee": ast_to_json(&scrutinee),
|
||||
"arms": arms.into_iter().map(|(lit, body)| json!({
|
||||
@ -108,45 +150,163 @@ pub fn json_to_ast(v: &Value) -> Option<ASTNode> {
|
||||
let k = v.get("kind")?.as_str()?;
|
||||
Some(match k {
|
||||
"Program" => {
|
||||
let stmts = v.get("statements")?.as_array()?.iter().filter_map(json_to_ast).collect::<Vec<_>>();
|
||||
ASTNode::Program { statements: stmts, span: Span::unknown() }
|
||||
let stmts = v
|
||||
.get("statements")?
|
||||
.as_array()?
|
||||
.iter()
|
||||
.filter_map(json_to_ast)
|
||||
.collect::<Vec<_>>();
|
||||
ASTNode::Program {
|
||||
statements: stmts,
|
||||
span: Span::unknown(),
|
||||
}
|
||||
}
|
||||
"Loop" => ASTNode::Loop {
|
||||
condition: Box::new(json_to_ast(v.get("condition")?)?),
|
||||
body: v.get("body")?.as_array()?.iter().filter_map(json_to_ast).collect::<Vec<_>>(),
|
||||
body: v
|
||||
.get("body")?
|
||||
.as_array()?
|
||||
.iter()
|
||||
.filter_map(json_to_ast)
|
||||
.collect::<Vec<_>>(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
"Print" => ASTNode::Print { expression: Box::new(json_to_ast(v.get("expression")?)?), span: Span::unknown() },
|
||||
"Return" => ASTNode::Return { value: v.get("value").and_then(json_to_ast).map(Box::new), span: Span::unknown() },
|
||||
"Break" => ASTNode::Break { span: Span::unknown() },
|
||||
"Continue" => ASTNode::Continue { span: Span::unknown() },
|
||||
"Assignment" => ASTNode::Assignment { target: Box::new(json_to_ast(v.get("target")?)?), value: Box::new(json_to_ast(v.get("value")?)?), span: Span::unknown() },
|
||||
"Local" => {
|
||||
let vars = v.get("variables")?.as_array()?.iter().filter_map(|s| s.as_str().map(|x| x.to_string())).collect();
|
||||
let inits = v.get("inits")?.as_array()?.iter().map(|initv| {
|
||||
if initv.is_null() { None } else { json_to_ast(initv).map(Box::new) }
|
||||
}).collect();
|
||||
ASTNode::Local { variables: vars, initial_values: inits, span: Span::unknown() }
|
||||
"Print" => ASTNode::Print {
|
||||
expression: Box::new(json_to_ast(v.get("expression")?)?),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
"Return" => ASTNode::Return {
|
||||
value: v.get("value").and_then(json_to_ast).map(Box::new),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
"Break" => ASTNode::Break {
|
||||
span: Span::unknown(),
|
||||
},
|
||||
"Continue" => ASTNode::Continue {
|
||||
span: Span::unknown(),
|
||||
},
|
||||
"Assignment" => ASTNode::Assignment {
|
||||
target: Box::new(json_to_ast(v.get("target")?)?),
|
||||
value: Box::new(json_to_ast(v.get("value")?)?),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
"Local" => {
|
||||
let vars = v
|
||||
.get("variables")?
|
||||
.as_array()?
|
||||
.iter()
|
||||
.filter_map(|s| s.as_str().map(|x| x.to_string()))
|
||||
.collect();
|
||||
let inits = v
|
||||
.get("inits")?
|
||||
.as_array()?
|
||||
.iter()
|
||||
.map(|initv| {
|
||||
if initv.is_null() {
|
||||
None
|
||||
} else {
|
||||
json_to_ast(initv).map(Box::new)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
ASTNode::Local {
|
||||
variables: vars,
|
||||
initial_values: inits,
|
||||
span: Span::unknown(),
|
||||
}
|
||||
}
|
||||
"If" => ASTNode::If {
|
||||
condition: Box::new(json_to_ast(v.get("condition")?)?),
|
||||
then_body: v
|
||||
.get("then")?
|
||||
.as_array()?
|
||||
.iter()
|
||||
.filter_map(json_to_ast)
|
||||
.collect::<Vec<_>>(),
|
||||
else_body: v.get("else").and_then(|a| {
|
||||
a.as_array()
|
||||
.map(|arr| arr.iter().filter_map(json_to_ast).collect::<Vec<_>>())
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
"If" => ASTNode::If { condition: Box::new(json_to_ast(v.get("condition")?)?), then_body: v.get("then")?.as_array()?.iter().filter_map(json_to_ast).collect::<Vec<_>>(), else_body: v.get("else").and_then(|a| a.as_array().map(|arr| arr.iter().filter_map(json_to_ast).collect::<Vec<_>>())), span: Span::unknown() },
|
||||
"FunctionDeclaration" => ASTNode::FunctionDeclaration {
|
||||
name: v.get("name")?.as_str()?.to_string(),
|
||||
params: v.get("params")?.as_array()?.iter().filter_map(|s| s.as_str().map(|x| x.to_string())).collect(),
|
||||
body: v.get("body")?.as_array()?.iter().filter_map(json_to_ast).collect(),
|
||||
params: v
|
||||
.get("params")?
|
||||
.as_array()?
|
||||
.iter()
|
||||
.filter_map(|s| s.as_str().map(|x| x.to_string()))
|
||||
.collect(),
|
||||
body: v
|
||||
.get("body")?
|
||||
.as_array()?
|
||||
.iter()
|
||||
.filter_map(json_to_ast)
|
||||
.collect(),
|
||||
is_static: v.get("static").and_then(|b| b.as_bool()).unwrap_or(false),
|
||||
is_override: v.get("override").and_then(|b| b.as_bool()).unwrap_or(false),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
"Variable" => ASTNode::Variable { name: v.get("name")?.as_str()?.to_string(), span: Span::unknown() },
|
||||
"Literal" => ASTNode::Literal { value: json_to_lit(v.get("value")?)?, span: Span::unknown() },
|
||||
"BinaryOp" => ASTNode::BinaryOp { operator: str_to_bin(v.get("op")?.as_str()?)?, left: Box::new(json_to_ast(v.get("left")?)?), right: Box::new(json_to_ast(v.get("right")?)?), span: Span::unknown() },
|
||||
"UnaryOp" => ASTNode::UnaryOp { operator: str_to_un(v.get("op")?.as_str()?)?, operand: Box::new(json_to_ast(v.get("operand")?)?), span: Span::unknown() },
|
||||
"MethodCall" => ASTNode::MethodCall { object: Box::new(json_to_ast(v.get("object")?)?), method: v.get("method")?.as_str()?.to_string(), arguments: v.get("arguments")?.as_array()?.iter().filter_map(json_to_ast).collect(), span: Span::unknown() },
|
||||
"FunctionCall" => ASTNode::FunctionCall { name: v.get("name")?.as_str()?.to_string(), arguments: v.get("arguments")?.as_array()?.iter().filter_map(json_to_ast).collect(), span: Span::unknown() },
|
||||
"Array" => ASTNode::ArrayLiteral { elements: v.get("elements")?.as_array()?.iter().filter_map(json_to_ast).collect(), span: Span::unknown() },
|
||||
"Map" => ASTNode::MapLiteral { entries: v.get("entries")?.as_array()?.iter().filter_map(|e| {
|
||||
Some((e.get("k")?.as_str()?.to_string(), json_to_ast(e.get("v")?)?))
|
||||
}).collect(), span: Span::unknown() },
|
||||
"Variable" => ASTNode::Variable {
|
||||
name: v.get("name")?.as_str()?.to_string(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
"Literal" => ASTNode::Literal {
|
||||
value: json_to_lit(v.get("value")?)?,
|
||||
span: Span::unknown(),
|
||||
},
|
||||
"BinaryOp" => ASTNode::BinaryOp {
|
||||
operator: str_to_bin(v.get("op")?.as_str()?)?,
|
||||
left: Box::new(json_to_ast(v.get("left")?)?),
|
||||
right: Box::new(json_to_ast(v.get("right")?)?),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
"UnaryOp" => ASTNode::UnaryOp {
|
||||
operator: str_to_un(v.get("op")?.as_str()?)?,
|
||||
operand: Box::new(json_to_ast(v.get("operand")?)?),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
"MethodCall" => ASTNode::MethodCall {
|
||||
object: Box::new(json_to_ast(v.get("object")?)?),
|
||||
method: v.get("method")?.as_str()?.to_string(),
|
||||
arguments: v
|
||||
.get("arguments")?
|
||||
.as_array()?
|
||||
.iter()
|
||||
.filter_map(json_to_ast)
|
||||
.collect(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
"FunctionCall" => ASTNode::FunctionCall {
|
||||
name: v.get("name")?.as_str()?.to_string(),
|
||||
arguments: v
|
||||
.get("arguments")?
|
||||
.as_array()?
|
||||
.iter()
|
||||
.filter_map(json_to_ast)
|
||||
.collect(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
"Array" => ASTNode::ArrayLiteral {
|
||||
elements: v
|
||||
.get("elements")?
|
||||
.as_array()?
|
||||
.iter()
|
||||
.filter_map(json_to_ast)
|
||||
.collect(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
"Map" => ASTNode::MapLiteral {
|
||||
entries: v
|
||||
.get("entries")?
|
||||
.as_array()?
|
||||
.iter()
|
||||
.filter_map(|e| {
|
||||
Some((e.get("k")?.as_str()?.to_string(), json_to_ast(e.get("v")?)?))
|
||||
})
|
||||
.collect(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
"MatchExpr" => {
|
||||
let scr = json_to_ast(v.get("scrutinee")?)?;
|
||||
let arms_json = v.get("arms")?.as_array()?.iter();
|
||||
@ -166,18 +326,47 @@ pub fn json_to_ast(v: &Value) -> Option<ASTNode> {
|
||||
}
|
||||
}
|
||||
"TryCatch" => {
|
||||
let try_b = v.get("try")?.as_array()?.iter().filter_map(json_to_ast).collect::<Vec<_>>();
|
||||
let try_b = v
|
||||
.get("try")?
|
||||
.as_array()?
|
||||
.iter()
|
||||
.filter_map(json_to_ast)
|
||||
.collect::<Vec<_>>();
|
||||
let mut catches = Vec::new();
|
||||
if let Some(arr) = v.get("catch").and_then(|x| x.as_array()) {
|
||||
for c in arr.iter() {
|
||||
let exc_t = match c.get("type") { Some(t) if !t.is_null() => t.as_str().map(|s| s.to_string()), _ => None };
|
||||
let var = match c.get("var") { Some(vv) if !vv.is_null() => vv.as_str().map(|s| s.to_string()), _ => None };
|
||||
let body = c.get("body")?.as_array()?.iter().filter_map(json_to_ast).collect::<Vec<_>>();
|
||||
catches.push(nyash_rust::ast::CatchClause { exception_type: exc_t, variable_name: var, body, span: Span::unknown() });
|
||||
let exc_t = match c.get("type") {
|
||||
Some(t) if !t.is_null() => t.as_str().map(|s| s.to_string()),
|
||||
_ => None,
|
||||
};
|
||||
let var = match c.get("var") {
|
||||
Some(vv) if !vv.is_null() => vv.as_str().map(|s| s.to_string()),
|
||||
_ => None,
|
||||
};
|
||||
let body = c
|
||||
.get("body")?
|
||||
.as_array()?
|
||||
.iter()
|
||||
.filter_map(json_to_ast)
|
||||
.collect::<Vec<_>>();
|
||||
catches.push(nyash_rust::ast::CatchClause {
|
||||
exception_type: exc_t,
|
||||
variable_name: var,
|
||||
body,
|
||||
span: Span::unknown(),
|
||||
});
|
||||
}
|
||||
}
|
||||
let cleanup = v.get("cleanup").and_then(|cl| cl.as_array().map(|arr| arr.iter().filter_map(json_to_ast).collect::<Vec<_>>()));
|
||||
ASTNode::TryCatch { try_body: try_b, catch_clauses: catches, finally_body: cleanup, span: Span::unknown() }
|
||||
let cleanup = v.get("cleanup").and_then(|cl| {
|
||||
cl.as_array()
|
||||
.map(|arr| arr.iter().filter_map(json_to_ast).collect::<Vec<_>>())
|
||||
});
|
||||
ASTNode::TryCatch {
|
||||
try_body: try_b,
|
||||
catch_clauses: catches,
|
||||
finally_body: cleanup,
|
||||
span: Span::unknown(),
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
})
|
||||
|
||||
@ -32,14 +32,18 @@ impl MacroCtx {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gensym(&self, prefix: &str) -> String { gensym(prefix) }
|
||||
pub fn gensym(&self, prefix: &str) -> String {
|
||||
gensym(prefix)
|
||||
}
|
||||
|
||||
pub fn report(&self, level: &str, message: &str) {
|
||||
eprintln!("[macro][{}] {}", level, message);
|
||||
}
|
||||
|
||||
pub fn get_env(&self, key: &str) -> Option<String> {
|
||||
if !self.caps.env { return None; }
|
||||
if !self.caps.env {
|
||||
return None;
|
||||
}
|
||||
std::env::var(key).ok()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use nyash_rust::ast::Span;
|
||||
use nyash_rust::{ASTNode, ast::LiteralValue, ast::BinaryOperator};
|
||||
use nyash_rust::{ast::BinaryOperator, ast::LiteralValue, ASTNode};
|
||||
use std::time::Instant;
|
||||
|
||||
/// HIR Patch description (MVP placeholder)
|
||||
@ -16,10 +16,20 @@ pub struct MacroEngine {
|
||||
|
||||
impl MacroEngine {
|
||||
pub fn new() -> Self {
|
||||
let max_passes = std::env::var("NYASH_MACRO_MAX_PASSES").ok().and_then(|v| v.parse().ok()).unwrap_or(32);
|
||||
let cycle_window = std::env::var("NYASH_MACRO_CYCLE_WINDOW").ok().and_then(|v| v.parse().ok()).unwrap_or(8);
|
||||
let max_passes = std::env::var("NYASH_MACRO_MAX_PASSES")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(32);
|
||||
let cycle_window = std::env::var("NYASH_MACRO_CYCLE_WINDOW")
|
||||
.ok()
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(8);
|
||||
let trace = std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1");
|
||||
Self { max_passes, cycle_window, trace }
|
||||
Self {
|
||||
max_passes,
|
||||
cycle_window,
|
||||
trace,
|
||||
}
|
||||
}
|
||||
|
||||
/// Expand all macros with depth/cycle guards and return patched AST.
|
||||
@ -29,25 +39,48 @@ impl MacroEngine {
|
||||
let mut history: std::collections::VecDeque<ASTNode> = std::collections::VecDeque::new();
|
||||
for pass in 0..self.max_passes {
|
||||
let t0 = Instant::now();
|
||||
let before_len = crate::r#macro::ast_json::ast_to_json(&cur).to_string().len();
|
||||
let before_len = crate::r#macro::ast_json::ast_to_json(&cur)
|
||||
.to_string()
|
||||
.len();
|
||||
let next0 = self.expand_node(&cur);
|
||||
// Apply user MacroBoxes once per pass (if enabled)
|
||||
let next = crate::r#macro::macro_box::expand_all_once(&next0);
|
||||
let after_len = crate::r#macro::ast_json::ast_to_json(&next).to_string().len();
|
||||
let after_len = crate::r#macro::ast_json::ast_to_json(&next)
|
||||
.to_string()
|
||||
.len();
|
||||
let dt = t0.elapsed();
|
||||
if self.trace { eprintln!("[macro][engine] pass={} changed={} bytes:{}=>{} dt={:?}", pass, (next != cur), before_len, after_len, dt); }
|
||||
if self.trace {
|
||||
eprintln!(
|
||||
"[macro][engine] pass={} changed={} bytes:{}=>{} dt={:?}",
|
||||
pass,
|
||||
(next != cur),
|
||||
before_len,
|
||||
after_len,
|
||||
dt
|
||||
);
|
||||
}
|
||||
jsonl_trace(pass, before_len, after_len, next != cur, dt);
|
||||
if next == cur { return (cur, patches); }
|
||||
if next == cur {
|
||||
return (cur, patches);
|
||||
}
|
||||
// cycle detection in small window
|
||||
if history.iter().any(|h| *h == next) {
|
||||
eprintln!("[macro][engine] cycle detected at pass {} — stopping expansion", pass);
|
||||
eprintln!(
|
||||
"[macro][engine] cycle detected at pass {} — stopping expansion",
|
||||
pass
|
||||
);
|
||||
return (cur, patches);
|
||||
}
|
||||
history.push_back(cur);
|
||||
if history.len() > self.cycle_window { let _ = history.pop_front(); }
|
||||
if history.len() > self.cycle_window {
|
||||
let _ = history.pop_front();
|
||||
}
|
||||
cur = next;
|
||||
}
|
||||
eprintln!("[macro][engine] max passes ({}) exceeded — stopping expansion", self.max_passes);
|
||||
eprintln!(
|
||||
"[macro][engine] max passes ({}) exceeded — stopping expansion",
|
||||
self.max_passes
|
||||
);
|
||||
(cur, patches)
|
||||
}
|
||||
|
||||
@ -57,23 +90,55 @@ impl MacroEngine {
|
||||
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[macro][visit] Program: statements={}", statements.len());
|
||||
}
|
||||
let new_stmts = statements.into_iter().map(|n| {
|
||||
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[macro][visit] child kind...",);
|
||||
}
|
||||
self.expand_node(&n)
|
||||
}).collect();
|
||||
ASTNode::Program { statements: new_stmts, span }
|
||||
let new_stmts = statements
|
||||
.into_iter()
|
||||
.map(|n| {
|
||||
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[macro][visit] child kind...",);
|
||||
}
|
||||
self.expand_node(&n)
|
||||
})
|
||||
.collect();
|
||||
ASTNode::Program {
|
||||
statements: new_stmts,
|
||||
span,
|
||||
}
|
||||
}
|
||||
ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, mut methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, is_static, static_init, span } => {
|
||||
ASTNode::BoxDeclaration {
|
||||
name,
|
||||
fields,
|
||||
public_fields,
|
||||
private_fields,
|
||||
mut methods,
|
||||
constructors,
|
||||
init_fields,
|
||||
weak_fields,
|
||||
is_interface,
|
||||
extends,
|
||||
implements,
|
||||
type_parameters,
|
||||
is_static,
|
||||
static_init,
|
||||
span,
|
||||
} => {
|
||||
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[macro][visit] BoxDeclaration: {} (fields={})", name, fields.len());
|
||||
eprintln!(
|
||||
"[macro][visit] BoxDeclaration: {} (fields={})",
|
||||
name,
|
||||
fields.len()
|
||||
);
|
||||
}
|
||||
// Derive set: default Equals+ToString when macro is enabled
|
||||
let derive_all = std::env::var("NYASH_MACRO_DERIVE_ALL").ok().as_deref() == Some("1");
|
||||
let derive_set = std::env::var("NYASH_MACRO_DERIVE").ok().unwrap_or_else(|| "Equals,ToString".to_string());
|
||||
let derive_all =
|
||||
std::env::var("NYASH_MACRO_DERIVE_ALL").ok().as_deref() == Some("1");
|
||||
let derive_set = std::env::var("NYASH_MACRO_DERIVE")
|
||||
.ok()
|
||||
.unwrap_or_else(|| "Equals,ToString".to_string());
|
||||
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[macro][derive] box={} derive_all={} set={}", name, derive_all, derive_set);
|
||||
eprintln!(
|
||||
"[macro][derive] box={} derive_all={} set={}",
|
||||
name, derive_all, derive_set
|
||||
);
|
||||
}
|
||||
let want_equals = derive_all || derive_set.contains("Equals");
|
||||
let want_tostring = derive_all || derive_set.contains("ToString");
|
||||
@ -81,19 +146,43 @@ impl MacroEngine {
|
||||
let field_view: &Vec<String> = &public_fields;
|
||||
if want_equals && !methods.contains_key("equals") {
|
||||
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[macro][derive] equals for {} (public fields: {})", name, field_view.len());
|
||||
eprintln!(
|
||||
"[macro][derive] equals for {} (public fields: {})",
|
||||
name,
|
||||
field_view.len()
|
||||
);
|
||||
}
|
||||
let m = build_equals_method(&name, field_view);
|
||||
methods.insert("equals".to_string(), m);
|
||||
}
|
||||
if want_tostring && !methods.contains_key("toString") {
|
||||
if std::env::var("NYASH_MACRO_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[macro][derive] toString for {} (public fields: {})", name, field_view.len());
|
||||
eprintln!(
|
||||
"[macro][derive] toString for {} (public fields: {})",
|
||||
name,
|
||||
field_view.len()
|
||||
);
|
||||
}
|
||||
let m = build_tostring_method(&name, field_view);
|
||||
methods.insert("toString".to_string(), m);
|
||||
}
|
||||
ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, is_static, static_init, span }
|
||||
ASTNode::BoxDeclaration {
|
||||
name,
|
||||
fields,
|
||||
public_fields,
|
||||
private_fields,
|
||||
methods,
|
||||
constructors,
|
||||
init_fields,
|
||||
weak_fields,
|
||||
is_interface,
|
||||
extends,
|
||||
implements,
|
||||
type_parameters,
|
||||
is_static,
|
||||
static_init,
|
||||
span,
|
||||
}
|
||||
}
|
||||
other => other,
|
||||
}
|
||||
@ -102,7 +191,9 @@ impl MacroEngine {
|
||||
|
||||
fn jsonl_trace(pass: usize, before: usize, after: usize, changed: bool, dt: std::time::Duration) {
|
||||
if let Ok(path) = std::env::var("NYASH_MACRO_TRACE_JSONL") {
|
||||
if path.is_empty() { return; }
|
||||
if path.is_empty() {
|
||||
return;
|
||||
}
|
||||
let rec = serde_json::json!({
|
||||
"event": "macro_pass",
|
||||
"pass": pass,
|
||||
@ -110,17 +201,24 @@ fn jsonl_trace(pass: usize, before: usize, after: usize, changed: bool, dt: std:
|
||||
"before_bytes": before,
|
||||
"after_bytes": after,
|
||||
"dt_us": dt.as_micros() as u64,
|
||||
}).to_string();
|
||||
let _ = std::fs::OpenOptions::new().create(true).append(true).open(path).and_then(|mut f| {
|
||||
use std::io::Write;
|
||||
writeln!(f, "{}", rec)
|
||||
});
|
||||
})
|
||||
.to_string();
|
||||
let _ = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(path)
|
||||
.and_then(|mut f| {
|
||||
use std::io::Write;
|
||||
writeln!(f, "{}", rec)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn me_field(name: &str) -> ASTNode {
|
||||
ASTNode::FieldAccess {
|
||||
object: Box::new(ASTNode::Me { span: Span::unknown() }),
|
||||
object: Box::new(ASTNode::Me {
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
field: name.to_string(),
|
||||
span: Span::unknown(),
|
||||
}
|
||||
@ -128,30 +226,56 @@ fn me_field(name: &str) -> ASTNode {
|
||||
|
||||
fn var_field(var: &str, field: &str) -> ASTNode {
|
||||
ASTNode::FieldAccess {
|
||||
object: Box::new(ASTNode::Variable { name: var.to_string(), span: Span::unknown() }),
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: var.to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
field: field.to_string(),
|
||||
span: Span::unknown(),
|
||||
}
|
||||
}
|
||||
|
||||
fn bin_add(lhs: ASTNode, rhs: ASTNode) -> ASTNode {
|
||||
ASTNode::BinaryOp { operator: BinaryOperator::Add, left: Box::new(lhs), right: Box::new(rhs), span: Span::unknown() }
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(lhs),
|
||||
right: Box::new(rhs),
|
||||
span: Span::unknown(),
|
||||
}
|
||||
}
|
||||
|
||||
fn bin_and(lhs: ASTNode, rhs: ASTNode) -> ASTNode {
|
||||
ASTNode::BinaryOp { operator: BinaryOperator::And, left: Box::new(lhs), right: Box::new(rhs), span: Span::unknown() }
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::And,
|
||||
left: Box::new(lhs),
|
||||
right: Box::new(rhs),
|
||||
span: Span::unknown(),
|
||||
}
|
||||
}
|
||||
|
||||
fn bin_eq(lhs: ASTNode, rhs: ASTNode) -> ASTNode {
|
||||
ASTNode::BinaryOp { operator: BinaryOperator::Equal, left: Box::new(lhs), right: Box::new(rhs), span: Span::unknown() }
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Equal,
|
||||
left: Box::new(lhs),
|
||||
right: Box::new(rhs),
|
||||
span: Span::unknown(),
|
||||
}
|
||||
}
|
||||
|
||||
fn lit_str(s: &str) -> ASTNode { ASTNode::Literal { value: LiteralValue::String(s.to_string()), span: Span::unknown() } }
|
||||
fn lit_str(s: &str) -> ASTNode {
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::String(s.to_string()),
|
||||
span: Span::unknown(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_equals_method(_box_name: &str, fields: &Vec<String>) -> ASTNode {
|
||||
// equals(other) { return me.f1 == other.f1 && ...; }
|
||||
let cond = if fields.is_empty() {
|
||||
ASTNode::Literal { value: LiteralValue::Bool(true), span: Span::unknown() }
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Bool(true),
|
||||
span: Span::unknown(),
|
||||
}
|
||||
} else {
|
||||
let mut it = fields.iter();
|
||||
let first = it.next().unwrap();
|
||||
@ -166,7 +290,10 @@ fn build_equals_method(_box_name: &str, fields: &Vec<String>) -> ASTNode {
|
||||
ASTNode::FunctionDeclaration {
|
||||
name: "equals".to_string(),
|
||||
params: vec![param_name.clone()],
|
||||
body: vec![ASTNode::Return { value: Some(Box::new(cond)), span: Span::unknown() }],
|
||||
body: vec![ASTNode::Return {
|
||||
value: Some(Box::new(cond)),
|
||||
span: Span::unknown(),
|
||||
}],
|
||||
is_static: false,
|
||||
is_override: false,
|
||||
span: Span::unknown(),
|
||||
@ -178,7 +305,9 @@ fn build_tostring_method(box_name: &str, fields: &Vec<String>) -> ASTNode {
|
||||
let mut expr = lit_str(&format!("{}(", box_name));
|
||||
let mut first = true;
|
||||
for f in fields {
|
||||
if !first { expr = bin_add(expr, lit_str(",")); }
|
||||
if !first {
|
||||
expr = bin_add(expr, lit_str(","));
|
||||
}
|
||||
first = false;
|
||||
expr = bin_add(expr, me_field(f));
|
||||
}
|
||||
@ -186,7 +315,10 @@ fn build_tostring_method(box_name: &str, fields: &Vec<String>) -> ASTNode {
|
||||
ASTNode::FunctionDeclaration {
|
||||
name: "toString".to_string(),
|
||||
params: vec![],
|
||||
body: vec![ASTNode::Return { value: Some(Box::new(expr)), span: Span::unknown() }],
|
||||
body: vec![ASTNode::Return {
|
||||
value: Some(Box::new(expr)),
|
||||
span: Span::unknown(),
|
||||
}],
|
||||
is_static: false,
|
||||
is_override: false,
|
||||
span: Span::unknown(),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use nyash_rust::ASTNode;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
/// MacroBox API — user-extensible macro expansion units (experimental)
|
||||
///
|
||||
@ -33,13 +33,17 @@ pub fn register(m: &'static dyn MacroBox) {
|
||||
/// Legacy env `NYASH_MACRO_BOX=1` still forces ON, but by default we
|
||||
/// synchronize with the macro system gate so user macros run when macros are enabled.
|
||||
pub fn enabled() -> bool {
|
||||
if std::env::var("NYASH_MACRO_BOX").ok().as_deref() == Some("1") { return true; }
|
||||
if std::env::var("NYASH_MACRO_BOX").ok().as_deref() == Some("1") {
|
||||
return true;
|
||||
}
|
||||
super::enabled()
|
||||
}
|
||||
|
||||
/// Expand AST by applying all registered MacroBoxes in order once.
|
||||
pub fn expand_all_once(ast: &ASTNode) -> ASTNode {
|
||||
if !enabled() { return ast.clone(); }
|
||||
if !enabled() {
|
||||
return ast.clone();
|
||||
}
|
||||
let reg = registry();
|
||||
let guard = reg.lock().expect("macro registry poisoned");
|
||||
let mut cur = ast.clone();
|
||||
@ -55,39 +59,127 @@ pub fn expand_all_once(ast: &ASTNode) -> ASTNode {
|
||||
pub struct UppercasePrintMacro;
|
||||
|
||||
impl MacroBox for UppercasePrintMacro {
|
||||
fn name(&self) -> &'static str { "UppercasePrintMacro" }
|
||||
fn name(&self) -> &'static str {
|
||||
"UppercasePrintMacro"
|
||||
}
|
||||
fn expand(&self, ast: &ASTNode) -> ASTNode {
|
||||
use nyash_rust::ast::{ASTNode as A, LiteralValue, Span};
|
||||
fn go(n: &A) -> A {
|
||||
match n.clone() {
|
||||
A::Program { statements, span } => A::Program { statements: statements.into_iter().map(|c| go(&c)).collect(), span },
|
||||
A::Program { statements, span } => A::Program {
|
||||
statements: statements.into_iter().map(|c| go(&c)).collect(),
|
||||
span,
|
||||
},
|
||||
A::Print { expression, span } => {
|
||||
match &*expression {
|
||||
A::Literal { value: LiteralValue::String(s), .. } => {
|
||||
A::Literal {
|
||||
value: LiteralValue::String(s),
|
||||
..
|
||||
} => {
|
||||
// Demo: if string starts with "UPPER:", uppercase the rest.
|
||||
if let Some(rest) = s.strip_prefix("UPPER:") {
|
||||
let up = rest.to_uppercase();
|
||||
A::Print { expression: Box::new(A::Literal { value: LiteralValue::String(up), span: Span::unknown() }), span }
|
||||
} else { A::Print { expression: Box::new(go(&*expression)), span } }
|
||||
A::Print {
|
||||
expression: Box::new(A::Literal {
|
||||
value: LiteralValue::String(up),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span,
|
||||
}
|
||||
} else {
|
||||
A::Print {
|
||||
expression: Box::new(go(&*expression)),
|
||||
span,
|
||||
}
|
||||
}
|
||||
}
|
||||
other => A::Print { expression: Box::new(go(other)), span }
|
||||
other => A::Print {
|
||||
expression: Box::new(go(other)),
|
||||
span,
|
||||
},
|
||||
}
|
||||
}
|
||||
A::Assignment { target, value, span } => A::Assignment { target: Box::new(go(&*target)), value: Box::new(go(&*value)), span },
|
||||
A::If { condition, then_body, else_body, span } => A::If {
|
||||
A::Assignment {
|
||||
target,
|
||||
value,
|
||||
span,
|
||||
} => A::Assignment {
|
||||
target: Box::new(go(&*target)),
|
||||
value: Box::new(go(&*value)),
|
||||
span,
|
||||
},
|
||||
A::If {
|
||||
condition,
|
||||
then_body,
|
||||
else_body,
|
||||
span,
|
||||
} => A::If {
|
||||
condition: Box::new(go(&*condition)),
|
||||
then_body: then_body.into_iter().map(|c| go(&c)).collect(),
|
||||
else_body: else_body.map(|v| v.into_iter().map(|c| go(&c)).collect()),
|
||||
span,
|
||||
},
|
||||
A::Return { value, span } => A::Return { value: value.as_ref().map(|v| Box::new(go(v))), span },
|
||||
A::FieldAccess { object, field, span } => A::FieldAccess { object: Box::new(go(&*object)), field, span },
|
||||
A::MethodCall { object, method, arguments, span } => A::MethodCall { object: Box::new(go(&*object)), method, arguments: arguments.into_iter().map(|c| go(&c)).collect(), span },
|
||||
A::FunctionCall { name, arguments, span } => A::FunctionCall { name, arguments: arguments.into_iter().map(|c| go(&c)).collect(), span },
|
||||
A::BinaryOp { operator, left, right, span } => A::BinaryOp { operator, left: Box::new(go(&*left)), right: Box::new(go(&*right)), span },
|
||||
A::UnaryOp { operator, operand, span } => A::UnaryOp { operator, operand: Box::new(go(&*operand)), span },
|
||||
A::ArrayLiteral { elements, span } => A::ArrayLiteral { elements: elements.into_iter().map(|c| go(&c)).collect(), span },
|
||||
A::MapLiteral { entries, span } => A::MapLiteral { entries: entries.into_iter().map(|(k,v)| (k, go(&v))).collect(), span },
|
||||
A::Return { value, span } => A::Return {
|
||||
value: value.as_ref().map(|v| Box::new(go(v))),
|
||||
span,
|
||||
},
|
||||
A::FieldAccess {
|
||||
object,
|
||||
field,
|
||||
span,
|
||||
} => A::FieldAccess {
|
||||
object: Box::new(go(&*object)),
|
||||
field,
|
||||
span,
|
||||
},
|
||||
A::MethodCall {
|
||||
object,
|
||||
method,
|
||||
arguments,
|
||||
span,
|
||||
} => A::MethodCall {
|
||||
object: Box::new(go(&*object)),
|
||||
method,
|
||||
arguments: arguments.into_iter().map(|c| go(&c)).collect(),
|
||||
span,
|
||||
},
|
||||
A::FunctionCall {
|
||||
name,
|
||||
arguments,
|
||||
span,
|
||||
} => A::FunctionCall {
|
||||
name,
|
||||
arguments: arguments.into_iter().map(|c| go(&c)).collect(),
|
||||
span,
|
||||
},
|
||||
A::BinaryOp {
|
||||
operator,
|
||||
left,
|
||||
right,
|
||||
span,
|
||||
} => A::BinaryOp {
|
||||
operator,
|
||||
left: Box::new(go(&*left)),
|
||||
right: Box::new(go(&*right)),
|
||||
span,
|
||||
},
|
||||
A::UnaryOp {
|
||||
operator,
|
||||
operand,
|
||||
span,
|
||||
} => A::UnaryOp {
|
||||
operator,
|
||||
operand: Box::new(go(&*operand)),
|
||||
span,
|
||||
},
|
||||
A::ArrayLiteral { elements, span } => A::ArrayLiteral {
|
||||
elements: elements.into_iter().map(|c| go(&c)).collect(),
|
||||
span,
|
||||
},
|
||||
A::MapLiteral { entries, span } => A::MapLiteral {
|
||||
entries: entries.into_iter().map(|(k, v)| (k, go(&v))).collect(),
|
||||
span,
|
||||
},
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
@ -101,13 +193,17 @@ static INIT_FLAG: OnceLock<()> = OnceLock::new();
|
||||
pub fn init_builtin() {
|
||||
INIT_FLAG.get_or_init(|| {
|
||||
// Explicit example toggle
|
||||
if std::env::var("NYASH_MACRO_BOX_EXAMPLE").ok().as_deref() == Some("1") { register(&UppercasePrintMacro); }
|
||||
if std::env::var("NYASH_MACRO_BOX_EXAMPLE").ok().as_deref() == Some("1") {
|
||||
register(&UppercasePrintMacro);
|
||||
}
|
||||
// Comma-separated names: NYASH_MACRO_BOX_ENABLE="UppercasePrintMacro,Other"
|
||||
if let Ok(list) = std::env::var("NYASH_MACRO_BOX_ENABLE") {
|
||||
for name in list.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) {
|
||||
match name {
|
||||
"UppercasePrintMacro" => register(&UppercasePrintMacro),
|
||||
_ => { eprintln!("[macro][box] unknown MacroBox '{}', ignoring", name); }
|
||||
_ => {
|
||||
eprintln!("[macro][box] unknown MacroBox '{}', ignoring", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,7 +28,9 @@ pub fn init_from_env() {
|
||||
if std::env::var("NYASH_MACRO_BOX_CHILD_RUNNER").ok().is_some() {
|
||||
eprintln!("[macro][compat] NYASH_MACRO_BOX_CHILD_RUNNER is deprecated; runner mode is managed automatically");
|
||||
}
|
||||
let Some(paths) = paths else { return; };
|
||||
let Some(paths) = paths else {
|
||||
return;
|
||||
};
|
||||
for p in paths.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) {
|
||||
if let Err(e) = try_load_one(p) {
|
||||
// Quiet by default; print only when tracing is enabled to reduce noise in normal runs
|
||||
@ -47,35 +49,72 @@ fn try_load_one(path: &str) -> Result<(), String> {
|
||||
let prev_sugar = std::env::var("NYASH_SYNTAX_SUGAR_LEVEL").ok();
|
||||
std::env::set_var("NYASH_SYNTAX_SUGAR_LEVEL", "basic");
|
||||
let ast_res = nyash_rust::parser::NyashParser::parse_from_string(&src);
|
||||
if let Some(v) = prev_sugar { std::env::set_var("NYASH_SYNTAX_SUGAR_LEVEL", v); } else { std::env::remove_var("NYASH_SYNTAX_SUGAR_LEVEL"); }
|
||||
if let Some(v) = prev_sugar {
|
||||
std::env::set_var("NYASH_SYNTAX_SUGAR_LEVEL", v);
|
||||
} else {
|
||||
std::env::remove_var("NYASH_SYNTAX_SUGAR_LEVEL");
|
||||
}
|
||||
let ast = ast_res.map_err(|e| format!("parse error: {:?}", e))?;
|
||||
// Find a BoxDeclaration with static function expand(...)
|
||||
if let ASTNode::Program { statements, .. } = ast {
|
||||
// Capabilities: conservative scan before registration
|
||||
if let Err(msg) = caps_allow_macro_source(&ASTNode::Program { statements: statements.clone(), span: nyash_rust::ast::Span::unknown() }) {
|
||||
if let Err(msg) = caps_allow_macro_source(&ASTNode::Program {
|
||||
statements: statements.clone(),
|
||||
span: nyash_rust::ast::Span::unknown(),
|
||||
}) {
|
||||
eprintln!("[macro][box_ny][caps] {} (in '{}')", msg, path);
|
||||
if strict_enabled() { return Err(msg); }
|
||||
if strict_enabled() {
|
||||
return Err(msg);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
for st in &statements {
|
||||
if let ASTNode::BoxDeclaration { name: box_name, methods, .. } = st {
|
||||
if let Some(ASTNode::FunctionDeclaration { name: mname, body: exp_body, params, .. }) = methods.get("expand") {
|
||||
if let ASTNode::BoxDeclaration {
|
||||
name: box_name,
|
||||
methods,
|
||||
..
|
||||
} = st
|
||||
{
|
||||
if let Some(ASTNode::FunctionDeclaration {
|
||||
name: mname,
|
||||
body: exp_body,
|
||||
params,
|
||||
..
|
||||
}) = methods.get("expand")
|
||||
{
|
||||
if mname == "expand" {
|
||||
let reg_name = derive_box_name(&box_name, methods.get("name"));
|
||||
// Prefer Nyash runner route by default (self-hosting). Child-proxy only when explicitly enabled.
|
||||
let use_child = std::env::var("NYASH_MACRO_BOX_CHILD").ok().map(|v| v != "0" && v != "false" && v != "off").unwrap_or(true);
|
||||
let use_child = std::env::var("NYASH_MACRO_BOX_CHILD")
|
||||
.ok()
|
||||
.map(|v| v != "0" && v != "false" && v != "off")
|
||||
.unwrap_or(true);
|
||||
if use_child {
|
||||
let nm = reg_name;
|
||||
let file_static: &'static str = Box::leak(path.to_string().into_boxed_str());
|
||||
crate::r#macro::macro_box::register(Box::leak(Box::new(NyChildMacroBox { nm, file: file_static })));
|
||||
eprintln!("[macro][box_ny] registered child-proxy MacroBox '{}' for {}", nm, path);
|
||||
let file_static: &'static str =
|
||||
Box::leak(path.to_string().into_boxed_str());
|
||||
crate::r#macro::macro_box::register(Box::leak(Box::new(
|
||||
NyChildMacroBox {
|
||||
nm,
|
||||
file: file_static,
|
||||
},
|
||||
)));
|
||||
eprintln!(
|
||||
"[macro][box_ny] registered child-proxy MacroBox '{}' for {}",
|
||||
nm, path
|
||||
);
|
||||
} else {
|
||||
// Heuristic mapping by name first, otherwise inspect body pattern.
|
||||
let mut mapped = false;
|
||||
match reg_name {
|
||||
"UppercasePrintMacro" => {
|
||||
crate::r#macro::macro_box::register(&crate::r#macro::macro_box::UppercasePrintMacro);
|
||||
eprintln!("[macro][box_ny] registered built-in '{}' from {}", reg_name, path);
|
||||
crate::r#macro::macro_box::register(
|
||||
&crate::r#macro::macro_box::UppercasePrintMacro,
|
||||
);
|
||||
eprintln!(
|
||||
"[macro][box_ny] registered built-in '{}' from {}",
|
||||
reg_name, path
|
||||
);
|
||||
mapped = true;
|
||||
}
|
||||
_ => {}
|
||||
@ -83,14 +122,20 @@ fn try_load_one(path: &str) -> Result<(), String> {
|
||||
if !mapped {
|
||||
if expand_is_identity(exp_body, params) {
|
||||
let nm = reg_name;
|
||||
crate::r#macro::macro_box::register(Box::leak(Box::new(NyIdentityMacroBox { nm })));
|
||||
crate::r#macro::macro_box::register(Box::leak(Box::new(
|
||||
NyIdentityMacroBox { nm },
|
||||
)));
|
||||
eprintln!("[macro][box_ny] registered Ny MacroBox '{}' (identity by body) from {}", nm, path);
|
||||
} else if expand_indicates_uppercase(exp_body, params) {
|
||||
crate::r#macro::macro_box::register(&crate::r#macro::macro_box::UppercasePrintMacro);
|
||||
crate::r#macro::macro_box::register(
|
||||
&crate::r#macro::macro_box::UppercasePrintMacro,
|
||||
);
|
||||
eprintln!("[macro][box_ny] registered built-in 'UppercasePrintMacro' by body pattern from {}", path);
|
||||
} else {
|
||||
let nm = reg_name;
|
||||
crate::r#macro::macro_box::register(Box::leak(Box::new(NyIdentityMacroBox { nm })));
|
||||
crate::r#macro::macro_box::register(Box::leak(Box::new(
|
||||
NyIdentityMacroBox { nm },
|
||||
)));
|
||||
eprintln!("[macro][box_ny] registered Ny MacroBox '{}' (identity: unknown body) from {}", nm, path);
|
||||
}
|
||||
}
|
||||
@ -102,19 +147,38 @@ fn try_load_one(path: &str) -> Result<(), String> {
|
||||
}
|
||||
// Fallback: accept top-level `static function MacroBoxSpec.expand(json)` without a BoxDeclaration
|
||||
// Default OFF for safety; can be enabled via CLI/env
|
||||
let allow_top = std::env::var("NYASH_MACRO_TOPLEVEL_ALLOW").ok().map(|v| v != "0" && v != "false" && v != "off").unwrap_or(false);
|
||||
let allow_top = std::env::var("NYASH_MACRO_TOPLEVEL_ALLOW")
|
||||
.ok()
|
||||
.map(|v| v != "0" && v != "false" && v != "off")
|
||||
.unwrap_or(false);
|
||||
for st in &statements {
|
||||
if let ASTNode::FunctionDeclaration { is_static: true, name, .. } = st {
|
||||
if let ASTNode::FunctionDeclaration {
|
||||
is_static: true,
|
||||
name,
|
||||
..
|
||||
} = st
|
||||
{
|
||||
if let Some((box_name, method)) = name.split_once('.') {
|
||||
if method == "expand" {
|
||||
let nm: &'static str = Box::leak(box_name.to_string().into_boxed_str());
|
||||
let file_static: &'static str = Box::leak(path.to_string().into_boxed_str());
|
||||
let use_child = std::env::var("NYASH_MACRO_BOX_CHILD").ok().map(|v| v != "0" && v != "false" && v != "off").unwrap_or(true);
|
||||
let file_static: &'static str =
|
||||
Box::leak(path.to_string().into_boxed_str());
|
||||
let use_child = std::env::var("NYASH_MACRO_BOX_CHILD")
|
||||
.ok()
|
||||
.map(|v| v != "0" && v != "false" && v != "off")
|
||||
.unwrap_or(true);
|
||||
if use_child && allow_top {
|
||||
crate::r#macro::macro_box::register(Box::leak(Box::new(NyChildMacroBox { nm, file: file_static })));
|
||||
crate::r#macro::macro_box::register(Box::leak(Box::new(
|
||||
NyChildMacroBox {
|
||||
nm,
|
||||
file: file_static,
|
||||
},
|
||||
)));
|
||||
eprintln!("[macro][box_ny] registered child-proxy MacroBox '{}' (top-level static) for {}", nm, path);
|
||||
} else {
|
||||
crate::r#macro::macro_box::register(Box::leak(Box::new(NyIdentityMacroBox { nm })));
|
||||
crate::r#macro::macro_box::register(Box::leak(Box::new(
|
||||
NyIdentityMacroBox { nm },
|
||||
)));
|
||||
eprintln!("[macro][box_ny] registered identity MacroBox '{}' (top-level static) for {}", nm, path);
|
||||
}
|
||||
return Ok(());
|
||||
@ -131,7 +195,11 @@ fn derive_box_name(default: &str, name_fn: Option<&ASTNode>) -> &'static str {
|
||||
if let Some(ASTNode::FunctionDeclaration { body, .. }) = name_fn {
|
||||
if body.len() == 1 {
|
||||
if let ASTNode::Return { value: Some(v), .. } = &body[0] {
|
||||
if let ASTNode::Literal { value: nyash_rust::ast::LiteralValue::String(s), .. } = &**v {
|
||||
if let ASTNode::Literal {
|
||||
value: nyash_rust::ast::LiteralValue::String(s),
|
||||
..
|
||||
} = &**v
|
||||
{
|
||||
let owned = s.clone();
|
||||
return Box::leak(owned.into_boxed_str());
|
||||
}
|
||||
@ -141,21 +209,33 @@ fn derive_box_name(default: &str, name_fn: Option<&ASTNode>) -> &'static str {
|
||||
Box::leak(default.to_string().into_boxed_str())
|
||||
}
|
||||
|
||||
pub(crate) struct NyIdentityMacroBox { nm: &'static str }
|
||||
pub(crate) struct NyIdentityMacroBox {
|
||||
nm: &'static str,
|
||||
}
|
||||
|
||||
impl super::macro_box::MacroBox for NyIdentityMacroBox {
|
||||
fn name(&self) -> &'static str { self.nm }
|
||||
fn name(&self) -> &'static str {
|
||||
self.nm
|
||||
}
|
||||
fn expand(&self, ast: &ASTNode) -> ASTNode {
|
||||
if std::env::var("NYASH_MACRO_BOX_NY_IDENTITY_ROUNDTRIP").ok().as_deref() == Some("1") {
|
||||
if std::env::var("NYASH_MACRO_BOX_NY_IDENTITY_ROUNDTRIP")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
let j = crate::r#macro::ast_json::ast_to_json(ast);
|
||||
if let Some(a2) = crate::r#macro::ast_json::json_to_ast(&j) { return a2; }
|
||||
if let Some(a2) = crate::r#macro::ast_json::json_to_ast(&j) {
|
||||
return a2;
|
||||
}
|
||||
}
|
||||
ast.clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_is_identity(body: &Vec<ASTNode>, params: &Vec<String>) -> bool {
|
||||
if body.len() != 1 { return false; }
|
||||
if body.len() != 1 {
|
||||
return false;
|
||||
}
|
||||
if let ASTNode::Return { value: Some(v), .. } = &body[0] {
|
||||
if let ASTNode::Variable { name, .. } = &**v {
|
||||
return params.get(0).map(|p| p == name).unwrap_or(false);
|
||||
@ -165,11 +245,15 @@ fn expand_is_identity(body: &Vec<ASTNode>, params: &Vec<String>) -> bool {
|
||||
}
|
||||
|
||||
fn expand_indicates_uppercase(body: &Vec<ASTNode>, params: &Vec<String>) -> bool {
|
||||
if body.len() != 1 { return false; }
|
||||
if body.len() != 1 {
|
||||
return false;
|
||||
}
|
||||
let p0 = params.get(0).cloned().unwrap_or_else(|| "ast".to_string());
|
||||
match &body[0] {
|
||||
ASTNode::Return { value: Some(v), .. } => match &**v {
|
||||
ASTNode::FunctionCall { name, arguments, .. } => {
|
||||
ASTNode::FunctionCall {
|
||||
name, arguments, ..
|
||||
} => {
|
||||
if (name == "uppercase_print" || name == "upper_print") && arguments.len() == 1 {
|
||||
if let ASTNode::Variable { name: an, .. } = &arguments[0] {
|
||||
return an == &p0;
|
||||
@ -184,32 +268,80 @@ fn expand_indicates_uppercase(body: &Vec<ASTNode>, params: &Vec<String>) -> bool
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MacroBehavior { Identity, Uppercase, ArrayPrependZero, MapInsertTag, LoopNormalize, IfMatchNormalize, ForForeachNormalize, EnvTagString }
|
||||
pub enum MacroBehavior {
|
||||
Identity,
|
||||
Uppercase,
|
||||
ArrayPrependZero,
|
||||
MapInsertTag,
|
||||
LoopNormalize,
|
||||
IfMatchNormalize,
|
||||
ForForeachNormalize,
|
||||
EnvTagString,
|
||||
}
|
||||
|
||||
pub fn analyze_macro_file(path: &str) -> MacroBehavior {
|
||||
let src = match std::fs::read_to_string(path) { Ok(s) => s, Err(_) => return MacroBehavior::Identity };
|
||||
let ast = match nyash_rust::parser::NyashParser::parse_from_string(&src) { Ok(a) => a, Err(_) => return MacroBehavior::Identity };
|
||||
let src = match std::fs::read_to_string(path) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return MacroBehavior::Identity,
|
||||
};
|
||||
let ast = match nyash_rust::parser::NyashParser::parse_from_string(&src) {
|
||||
Ok(a) => a,
|
||||
Err(_) => return MacroBehavior::Identity,
|
||||
};
|
||||
// Quick heuristics based on literals present in file
|
||||
fn ast_has_literal_string(a: &ASTNode, needle: &str) -> bool {
|
||||
use nyash_rust::ast::ASTNode as A;
|
||||
match a {
|
||||
A::Literal { value: nyash_rust::ast::LiteralValue::String(s), .. } => s.contains(needle),
|
||||
A::Program { statements, .. } => statements.iter().any(|n| ast_has_literal_string(n, needle)),
|
||||
A::Literal {
|
||||
value: nyash_rust::ast::LiteralValue::String(s),
|
||||
..
|
||||
} => s.contains(needle),
|
||||
A::Program { statements, .. } => {
|
||||
statements.iter().any(|n| ast_has_literal_string(n, needle))
|
||||
}
|
||||
A::Print { expression, .. } => ast_has_literal_string(expression, needle),
|
||||
A::Return { value, .. } => value.as_ref().map(|v| ast_has_literal_string(v, needle)).unwrap_or(false),
|
||||
A::Assignment { target, value, .. } => ast_has_literal_string(target, needle) || ast_has_literal_string(value, needle),
|
||||
A::If { condition, then_body, else_body, .. } => {
|
||||
A::Return { value, .. } => value
|
||||
.as_ref()
|
||||
.map(|v| ast_has_literal_string(v, needle))
|
||||
.unwrap_or(false),
|
||||
A::Assignment { target, value, .. } => {
|
||||
ast_has_literal_string(target, needle) || ast_has_literal_string(value, needle)
|
||||
}
|
||||
A::If {
|
||||
condition,
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => {
|
||||
ast_has_literal_string(condition, needle)
|
||||
|| then_body.iter().any(|n| ast_has_literal_string(n, needle))
|
||||
|| else_body.as_ref().map(|v| v.iter().any(|n| ast_has_literal_string(n, needle))).unwrap_or(false)
|
||||
|| else_body
|
||||
.as_ref()
|
||||
.map(|v| v.iter().any(|n| ast_has_literal_string(n, needle)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
A::FunctionDeclaration { body, .. } => {
|
||||
body.iter().any(|n| ast_has_literal_string(n, needle))
|
||||
}
|
||||
A::BinaryOp { left, right, .. } => {
|
||||
ast_has_literal_string(left, needle) || ast_has_literal_string(right, needle)
|
||||
}
|
||||
A::FunctionDeclaration { body, .. } => body.iter().any(|n| ast_has_literal_string(n, needle)),
|
||||
A::BinaryOp { left, right, .. } => ast_has_literal_string(left, needle) || ast_has_literal_string(right, needle),
|
||||
A::UnaryOp { operand, .. } => ast_has_literal_string(operand, needle),
|
||||
A::MethodCall { object, arguments, .. } => ast_has_literal_string(object, needle) || arguments.iter().any(|n| ast_has_literal_string(n, needle)),
|
||||
A::FunctionCall { arguments, .. } => arguments.iter().any(|n| ast_has_literal_string(n, needle)),
|
||||
A::ArrayLiteral { elements, .. } => elements.iter().any(|n| ast_has_literal_string(n, needle)),
|
||||
A::MapLiteral { entries, .. } => entries.iter().any(|(_, v)| ast_has_literal_string(v, needle)),
|
||||
A::MethodCall {
|
||||
object, arguments, ..
|
||||
} => {
|
||||
ast_has_literal_string(object, needle)
|
||||
|| arguments.iter().any(|n| ast_has_literal_string(n, needle))
|
||||
}
|
||||
A::FunctionCall { arguments, .. } => {
|
||||
arguments.iter().any(|n| ast_has_literal_string(n, needle))
|
||||
}
|
||||
A::ArrayLiteral { elements, .. } => {
|
||||
elements.iter().any(|n| ast_has_literal_string(n, needle))
|
||||
}
|
||||
A::MapLiteral { entries, .. } => entries
|
||||
.iter()
|
||||
.any(|(_, v)| ast_has_literal_string(v, needle)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -218,29 +350,59 @@ pub fn analyze_macro_file(path: &str) -> MacroBehavior {
|
||||
match a {
|
||||
A::Program { statements, .. } => statements.iter().any(|n| ast_has_method(n, method)),
|
||||
A::Print { expression, .. } => ast_has_method(expression, method),
|
||||
A::Return { value, .. } => value.as_ref().map(|v| ast_has_method(v, method)).unwrap_or(false),
|
||||
A::Assignment { target, value, .. } => ast_has_method(target, method) || ast_has_method(value, method),
|
||||
A::If { condition, then_body, else_body, .. } => ast_has_method(condition, method)
|
||||
|| then_body.iter().any(|n| ast_has_method(n, method))
|
||||
|| else_body.as_ref().map(|v| v.iter().any(|n| ast_has_method(n, method))).unwrap_or(false),
|
||||
A::Return { value, .. } => value
|
||||
.as_ref()
|
||||
.map(|v| ast_has_method(v, method))
|
||||
.unwrap_or(false),
|
||||
A::Assignment { target, value, .. } => {
|
||||
ast_has_method(target, method) || ast_has_method(value, method)
|
||||
}
|
||||
A::If {
|
||||
condition,
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => {
|
||||
ast_has_method(condition, method)
|
||||
|| then_body.iter().any(|n| ast_has_method(n, method))
|
||||
|| else_body
|
||||
.as_ref()
|
||||
.map(|v| v.iter().any(|n| ast_has_method(n, method)))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
A::FunctionDeclaration { body, .. } => body.iter().any(|n| ast_has_method(n, method)),
|
||||
A::BinaryOp { left, right, .. } => ast_has_method(left, method) || ast_has_method(right, method),
|
||||
A::BinaryOp { left, right, .. } => {
|
||||
ast_has_method(left, method) || ast_has_method(right, method)
|
||||
}
|
||||
A::UnaryOp { operand, .. } => ast_has_method(operand, method),
|
||||
A::MethodCall { object, method: m, arguments, .. } => m == method
|
||||
|| ast_has_method(object, method)
|
||||
|| arguments.iter().any(|n| ast_has_method(n, method)),
|
||||
A::FunctionCall { arguments, .. } => arguments.iter().any(|n| ast_has_method(n, method)),
|
||||
A::MethodCall {
|
||||
object,
|
||||
method: m,
|
||||
arguments,
|
||||
..
|
||||
} => {
|
||||
m == method
|
||||
|| ast_has_method(object, method)
|
||||
|| arguments.iter().any(|n| ast_has_method(n, method))
|
||||
}
|
||||
A::FunctionCall { arguments, .. } => {
|
||||
arguments.iter().any(|n| ast_has_method(n, method))
|
||||
}
|
||||
A::ArrayLiteral { elements, .. } => elements.iter().any(|n| ast_has_method(n, method)),
|
||||
A::MapLiteral { entries, .. } => entries.iter().any(|(_, v)| ast_has_method(v, method)),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
// Detect array prepend-zero macro by pattern strings present in macro source
|
||||
if ast_has_literal_string(&ast, "\"kind\":\"Array\",\"elements\":[") || ast_has_literal_string(&ast, "\"elements\":[") {
|
||||
if ast_has_literal_string(&ast, "\"kind\":\"Array\",\"elements\":[")
|
||||
|| ast_has_literal_string(&ast, "\"elements\":[")
|
||||
{
|
||||
return MacroBehavior::ArrayPrependZero;
|
||||
}
|
||||
// Detect map insert-tag macro by pattern strings
|
||||
if ast_has_literal_string(&ast, "\"kind\":\"Map\",\"entries\":[") || ast_has_literal_string(&ast, "\"entries\":[") {
|
||||
if ast_has_literal_string(&ast, "\"kind\":\"Map\",\"entries\":[")
|
||||
|| ast_has_literal_string(&ast, "\"entries\":[")
|
||||
{
|
||||
return MacroBehavior::MapInsertTag;
|
||||
}
|
||||
// Detect upper-string macro by pattern or toUpperCase usage
|
||||
@ -253,23 +415,47 @@ pub fn analyze_macro_file(path: &str) -> MacroBehavior {
|
||||
}
|
||||
if let ASTNode::Program { statements, .. } = ast {
|
||||
for st in statements {
|
||||
if let ASTNode::BoxDeclaration { name: _, methods, .. } = st {
|
||||
if let ASTNode::BoxDeclaration {
|
||||
name: _, methods, ..
|
||||
} = st
|
||||
{
|
||||
// Detect LoopNormalize/IfMatchNormalize by name() returning a specific string
|
||||
if let Some(ASTNode::FunctionDeclaration { name: mname, body, .. }) = methods.get("name") {
|
||||
if let Some(ASTNode::FunctionDeclaration {
|
||||
name: mname, body, ..
|
||||
}) = methods.get("name")
|
||||
{
|
||||
if mname == "name" {
|
||||
if body.len() == 1 {
|
||||
if let ASTNode::Return { value: Some(v), .. } = &body[0] {
|
||||
if let ASTNode::Literal { value: nyash_rust::ast::LiteralValue::String(s), .. } = &**v {
|
||||
if s == "LoopNormalize" { return MacroBehavior::LoopNormalize; }
|
||||
if s == "IfMatchNormalize" { return MacroBehavior::IfMatchNormalize; }
|
||||
if s == "ForForeach" { return MacroBehavior::ForForeachNormalize; }
|
||||
if s == "EnvTagString" { return MacroBehavior::EnvTagString; }
|
||||
if let ASTNode::Literal {
|
||||
value: nyash_rust::ast::LiteralValue::String(s),
|
||||
..
|
||||
} = &**v
|
||||
{
|
||||
if s == "LoopNormalize" {
|
||||
return MacroBehavior::LoopNormalize;
|
||||
}
|
||||
if s == "IfMatchNormalize" {
|
||||
return MacroBehavior::IfMatchNormalize;
|
||||
}
|
||||
if s == "ForForeach" {
|
||||
return MacroBehavior::ForForeachNormalize;
|
||||
}
|
||||
if s == "EnvTagString" {
|
||||
return MacroBehavior::EnvTagString;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(ASTNode::FunctionDeclaration { name: mname, body, params, .. }) = methods.get("expand") {
|
||||
if let Some(ASTNode::FunctionDeclaration {
|
||||
name: mname,
|
||||
body,
|
||||
params,
|
||||
..
|
||||
}) = methods.get("expand")
|
||||
{
|
||||
if mname == "expand" {
|
||||
if expand_indicates_uppercase(body, params) {
|
||||
return MacroBehavior::Uppercase;
|
||||
@ -282,7 +468,10 @@ pub fn analyze_macro_file(path: &str) -> MacroBehavior {
|
||||
MacroBehavior::Identity
|
||||
}
|
||||
|
||||
struct NyChildMacroBox { nm: &'static str, file: &'static str }
|
||||
struct NyChildMacroBox {
|
||||
nm: &'static str,
|
||||
file: &'static str,
|
||||
}
|
||||
|
||||
fn cap_enabled(name: &str) -> bool {
|
||||
match std::env::var(name).ok() {
|
||||
@ -301,67 +490,148 @@ fn caps_allow_macro_source(ast: &ASTNode) -> Result<(), String> {
|
||||
fn scan(n: &A, seen: &mut Vec<String>) {
|
||||
match n {
|
||||
A::New { class, .. } => seen.push(class.clone()),
|
||||
A::Program { statements, .. } => for s in statements { scan(s, seen); },
|
||||
A::FunctionDeclaration { body, .. } => for s in body { scan(s, seen); },
|
||||
A::Assignment { target, value, .. } => { scan(target, seen); scan(value, seen); },
|
||||
A::Return { value, .. } => if let Some(v) = value { scan(v, seen); },
|
||||
A::If { condition, then_body, else_body, .. } => {
|
||||
scan(condition, seen);
|
||||
for s in then_body { scan(s, seen); }
|
||||
if let Some(b) = else_body { for s in b { scan(s, seen); } }
|
||||
A::Program { statements, .. } => {
|
||||
for s in statements {
|
||||
scan(s, seen);
|
||||
}
|
||||
}
|
||||
A::FunctionDeclaration { body, .. } => {
|
||||
for s in body {
|
||||
scan(s, seen);
|
||||
}
|
||||
}
|
||||
A::Assignment { target, value, .. } => {
|
||||
scan(target, seen);
|
||||
scan(value, seen);
|
||||
}
|
||||
A::Return { value, .. } => {
|
||||
if let Some(v) = value {
|
||||
scan(v, seen);
|
||||
}
|
||||
}
|
||||
A::If {
|
||||
condition,
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => {
|
||||
scan(condition, seen);
|
||||
for s in then_body {
|
||||
scan(s, seen);
|
||||
}
|
||||
if let Some(b) = else_body {
|
||||
for s in b {
|
||||
scan(s, seen);
|
||||
}
|
||||
}
|
||||
}
|
||||
A::BinaryOp { left, right, .. } => {
|
||||
scan(left, seen);
|
||||
scan(right, seen);
|
||||
}
|
||||
A::BinaryOp { left, right, .. } => { scan(left, seen); scan(right, seen); }
|
||||
A::UnaryOp { operand, .. } => scan(operand, seen),
|
||||
A::MethodCall { object, arguments, .. } => { scan(object, seen); for a in arguments { scan(a, seen); } }
|
||||
A::FunctionCall { arguments, .. } => for a in arguments { scan(a, seen); },
|
||||
A::ArrayLiteral { elements, .. } => for e in elements { scan(e, seen); },
|
||||
A::MapLiteral { entries, .. } => for (_, v) in entries { scan(v, seen); },
|
||||
A::MethodCall {
|
||||
object, arguments, ..
|
||||
} => {
|
||||
scan(object, seen);
|
||||
for a in arguments {
|
||||
scan(a, seen);
|
||||
}
|
||||
}
|
||||
A::FunctionCall { arguments, .. } => {
|
||||
for a in arguments {
|
||||
scan(a, seen);
|
||||
}
|
||||
}
|
||||
A::ArrayLiteral { elements, .. } => {
|
||||
for e in elements {
|
||||
scan(e, seen);
|
||||
}
|
||||
}
|
||||
A::MapLiteral { entries, .. } => {
|
||||
for (_, v) in entries {
|
||||
scan(v, seen);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let mut boxes = Vec::new();
|
||||
scan(ast, &mut boxes);
|
||||
if !allow_io && boxes.iter().any(|c| c == "FileBox" || c == "PathBox" || c == "DirBox") {
|
||||
if !allow_io
|
||||
&& boxes
|
||||
.iter()
|
||||
.any(|c| c == "FileBox" || c == "PathBox" || c == "DirBox")
|
||||
{
|
||||
return Err("macro capability violation: IO (File/Path/Dir) denied".into());
|
||||
}
|
||||
if !allow_net && boxes.iter().any(|c| c.contains("HTTP") || c.contains("Http") || c == "SocketBox") {
|
||||
if !allow_net
|
||||
&& boxes
|
||||
.iter()
|
||||
.any(|c| c.contains("HTTP") || c.contains("Http") || c == "SocketBox")
|
||||
{
|
||||
return Err("macro capability violation: NET (HTTP/Socket) denied".into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl super::macro_box::MacroBox for NyChildMacroBox {
|
||||
fn name(&self) -> &'static str { self.nm }
|
||||
fn name(&self) -> &'static str {
|
||||
self.nm
|
||||
}
|
||||
fn expand(&self, ast: &ASTNode) -> ASTNode {
|
||||
// Parent-side proxy: prefer runner script (PyVM) when enabled; otherwise fallback to internal child mode.
|
||||
let exe = match std::env::current_exe() {
|
||||
Ok(p) => p,
|
||||
Err(e) => { eprintln!("[macro-proxy] current_exe failed: {}", e); return ast.clone(); }
|
||||
Err(e) => {
|
||||
eprintln!("[macro-proxy] current_exe failed: {}", e);
|
||||
return ast.clone();
|
||||
}
|
||||
};
|
||||
// Prefer Nyash runner route by default for self-hosting; legacy env can force internal child with 0.
|
||||
let use_runner = std::env::var("NYASH_MACRO_BOX_CHILD_RUNNER").ok().map(|v| v != "0" && v != "false" && v != "off").unwrap_or(false);
|
||||
let use_runner = std::env::var("NYASH_MACRO_BOX_CHILD_RUNNER")
|
||||
.ok()
|
||||
.map(|v| v != "0" && v != "false" && v != "off")
|
||||
.unwrap_or(false);
|
||||
if std::env::var("NYASH_MACRO_BOX_CHILD_RUNNER").ok().is_some() {
|
||||
eprintln!("[macro][compat] NYASH_MACRO_BOX_CHILD_RUNNER is deprecated; prefer defaults");
|
||||
eprintln!(
|
||||
"[macro][compat] NYASH_MACRO_BOX_CHILD_RUNNER is deprecated; prefer defaults"
|
||||
);
|
||||
}
|
||||
let mut cmd = std::process::Command::new(exe.clone());
|
||||
// Build MacroCtx JSON once (caps only, MVP)
|
||||
let mctx = crate::r#macro::ctx::MacroCtx::from_env();
|
||||
let ctx_json = format!("{{\"caps\":{{\"io\":{},\"net\":{},\"env\":{}}}}}", mctx.caps.io, mctx.caps.net, mctx.caps.env);
|
||||
let ctx_json = format!(
|
||||
"{{\"caps\":{{\"io\":{},\"net\":{},\"env\":{}}}}}",
|
||||
mctx.caps.io, mctx.caps.net, mctx.caps.env
|
||||
);
|
||||
if use_runner {
|
||||
// Synthesize a tiny runner that inlines the macro file and calls MacroBoxSpec.expand
|
||||
use std::io::Write as _;
|
||||
let tmp_dir = std::path::Path::new("tmp");
|
||||
let _ = std::fs::create_dir_all(tmp_dir);
|
||||
let ts = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_millis();
|
||||
let ts = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis();
|
||||
let tmp_path = tmp_dir.join(format!("macro_expand_runner_{}.hako", ts));
|
||||
let mut f = match std::fs::File::create(&tmp_path) { Ok(x) => x, Err(e) => { eprintln!("[macro-proxy] create tmp runner failed: {}", e); return ast.clone(); } };
|
||||
let mut f = match std::fs::File::create(&tmp_path) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
eprintln!("[macro-proxy] create tmp runner failed: {}", e);
|
||||
return ast.clone();
|
||||
}
|
||||
};
|
||||
let macro_src = std::fs::read_to_string(self.file)
|
||||
.unwrap_or_else(|_| String::from("// failed to read macro file\n"));
|
||||
let script = format!(
|
||||
"{}\n\nfunction main(args) {{\n if args.length() == 0 {{\n print(\"{{}}\")\n return 0\n }}\n local j, r, ctx\n j = args.get(0)\n if args.length() > 1 {{ ctx = args.get(1) }} else {{ ctx = \"{{}}\" }}\n r = MacroBoxSpec.expand(j, ctx)\n print(r)\n return 0\n}}\n",
|
||||
macro_src
|
||||
);
|
||||
if let Err(e) = f.write_all(script.as_bytes()) { eprintln!("[macro-proxy] write tmp runner failed: {}", e); return ast.clone(); }
|
||||
if let Err(e) = f.write_all(script.as_bytes()) {
|
||||
eprintln!("[macro-proxy] write tmp runner failed: {}", e);
|
||||
return ast.clone();
|
||||
}
|
||||
// Run Nyash runner script under PyVM: nyash --backend vm <tmp_runner> -- <json>
|
||||
cmd.arg("--backend").arg("vm").arg(tmp_path);
|
||||
// Append script args after '--'
|
||||
@ -372,7 +642,8 @@ impl super::macro_box::MacroBox for NyChildMacroBox {
|
||||
cmd.stdin(std::process::Stdio::null());
|
||||
} else {
|
||||
// Internal child mode: --macro-expand-child <macro file> with stdin JSON
|
||||
cmd.arg("--macro-expand-child").arg(self.file)
|
||||
cmd.arg("--macro-expand-child")
|
||||
.arg(self.file)
|
||||
.stdin(std::process::Stdio::piped());
|
||||
// Provide MacroCtx via env for internal child
|
||||
cmd.env("NYASH_MACRO_CTX_JSON", ctx_json.clone());
|
||||
@ -393,13 +664,21 @@ impl super::macro_box::MacroBox for NyChildMacroBox {
|
||||
cmd.env_remove("NYASH_MACRO_BOX_CHILD");
|
||||
cmd.env_remove("NYASH_MACRO_BOX_CHILD_RUNNER");
|
||||
// Timeout
|
||||
let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS").ok().and_then(|s| s.parse().ok()).unwrap_or(2000);
|
||||
let timeout_ms: u64 = std::env::var("NYASH_NY_COMPILER_TIMEOUT_MS")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(2000);
|
||||
// Spawn
|
||||
let mut child = match cmd.spawn() { Ok(c) => c, Err(e) => {
|
||||
eprintln!("[macro-proxy] spawn failed: {}", e);
|
||||
if strict_enabled() { std::process::exit(2); }
|
||||
return ast.clone();
|
||||
} };
|
||||
let mut child = match cmd.spawn() {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
eprintln!("[macro-proxy] spawn failed: {}", e);
|
||||
if strict_enabled() {
|
||||
std::process::exit(2);
|
||||
}
|
||||
return ast.clone();
|
||||
}
|
||||
};
|
||||
// Write stdin only in internal child mode
|
||||
if !use_runner {
|
||||
if let Some(mut sin) = child.stdin.take() {
|
||||
@ -415,34 +694,60 @@ impl super::macro_box::MacroBox for NyChildMacroBox {
|
||||
loop {
|
||||
match child.try_wait() {
|
||||
Ok(Some(_status)) => {
|
||||
if let Some(mut so) = child.stdout.take() { use std::io::Read; let _ = so.read_to_string(&mut out); }
|
||||
if let Some(mut so) = child.stdout.take() {
|
||||
use std::io::Read;
|
||||
let _ = so.read_to_string(&mut out);
|
||||
}
|
||||
break;
|
||||
}
|
||||
Ok(None) => {
|
||||
if start.elapsed() >= Duration::from_millis(timeout_ms) {
|
||||
let _ = child.kill(); let _ = child.wait();
|
||||
let _ = child.kill();
|
||||
let _ = child.wait();
|
||||
eprintln!("[macro-proxy] timeout {} ms", timeout_ms);
|
||||
if strict_enabled() { std::process::exit(124); }
|
||||
if strict_enabled() {
|
||||
std::process::exit(124);
|
||||
}
|
||||
return ast.clone();
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(5));
|
||||
}
|
||||
Err(e) => { eprintln!("[macro-proxy] wait error: {}", e); if strict_enabled() { std::process::exit(2); } return ast.clone(); }
|
||||
Err(e) => {
|
||||
eprintln!("[macro-proxy] wait error: {}", e);
|
||||
if strict_enabled() {
|
||||
std::process::exit(2);
|
||||
}
|
||||
return ast.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
// capture stderr for diagnostics and continue
|
||||
// Capture stderr for diagnostics
|
||||
let mut err = String::new();
|
||||
if let Some(mut se) = child.stderr.take() { use std::io::Read; let _ = se.read_to_string(&mut err); }
|
||||
if let Some(mut se) = child.stderr.take() {
|
||||
use std::io::Read;
|
||||
let _ = se.read_to_string(&mut err);
|
||||
}
|
||||
// Parse output JSON
|
||||
match serde_json::from_str::<serde_json::Value>(&out) {
|
||||
Ok(v) => match crate::r#macro::ast_json::json_to_ast(&v) {
|
||||
Some(a) => a,
|
||||
None => { eprintln!("[macro-proxy] child JSON did not map to AST. stderr=\n{}", err); if strict_enabled() { std::process::exit(2); } ast.clone() }
|
||||
None => {
|
||||
eprintln!(
|
||||
"[macro-proxy] child JSON did not map to AST. stderr=\n{}",
|
||||
err
|
||||
);
|
||||
if strict_enabled() {
|
||||
std::process::exit(2);
|
||||
}
|
||||
ast.clone()
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
eprintln!("[macro-proxy] invalid JSON from child: {}\n-- child stderr --\n{}\n-- end stderr --", e, err);
|
||||
if strict_enabled() { std::process::exit(2); }
|
||||
if strict_enabled() {
|
||||
std::process::exit(2);
|
||||
}
|
||||
ast.clone()
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user