using: safer seam defaults (fix_braces OFF by default) + path-alias handling; json_native: robust integer parse + EscapeUtils unquote; add JsonCompat layer; builder: preindex static methods + fallback for bare calls; diagnostics: seam dump + function-call trace

This commit is contained in:
Selfhosting Dev
2025-09-25 10:23:14 +09:00
parent 2f306dd6a5
commit 9384c80623
30 changed files with 1786 additions and 527 deletions

View File

@ -1,11 +1,56 @@
# Current Task — Phase 15: Nyashセルフホスティング実行器統一化
# Current Task — Phase 15 (Revised): SelfHosting Focus, JSON→Ny Executor
Updated: 20250926
Quick execution summary (local)
- Build: cargo build --release → OK
- v2 smokes (quick): core set PASS/expected SKIP only
- v2 smokes (plugins): Fixture dylib autoload PASS環境に応じて一部 SKIP 設計
Quick status
- Build: `cargo build --release` → OK(警告のみ)
- Smokes v2: quick/core PASS、integration/parity PASSPython LLVM harness
- Parser: TokenCursor 統一 Step2/3 完了env ゲート
- PHI: if/else の incoming pred を exit ブロックへ修正VM 未定義値を根治)
## 今日の合意(方向修正の確定)
- Rust層は新機能を最小化。今後は Nyash VM/コンパイラ(自己ホスト)へリソース集中。
- 次タスクは Nyash 製 JSON ライブラリJSON v0 DOM: parse/stringify。完了次第、Ny Executor 最小命令の実装を着手。
- LLVM ラインは Python/llvmlite ハーネスを正式優先llvm_sys_180 依存は前提にしない)。
- GC は“安全網と計測の小粒強化”に限定(既定: RC、不変
## 直近10日の実行計画小粒・仕様不変・既定OFF
1) JSON types/lexer/parser/encoderNyash
- Path: `apps/lib/json_native/{types,lexer,parser,encode}.nyash`
- Env: `NYASH_JSON_PROVIDER=ny`既定OFF
- Smokes: roundtrip/parse_error 最小セットquick/core には既定OFFで影響なし
2) Ny Executor最小命令
- ops: const/binop/compare/branch/jump/ret/phi
- Env: `NYASH_SELFHOST_EXEC=1`既定OFF
- Smokes: arith/if/phiparity: PyVM/LLVM harness
3) 呼び出し最小MVP
- call/externcall/boxcallConsole/String/Array/Map の P0
- 代表スモーク: print/concat/len/has 基本
4) 監視期間(数日)→ 旧 depth/skip 残骸の完全削除と警告掃除(任意)
受け入れゲート
- quick/core + integration/parity 緑env ON/OFF 双方)
- 既定挙動を変えない(新経路はすべて env トグルで optin
- 変更は小さくロールバック容易
主要トグル(統一)
- `NYASH_LLVM_USE_HARNESS=1`Python llvmlite ハーネス)
- `NYASH_PARSER_TOKEN_CURSOR=1`(式/文を Cursor 経路で)
- `NYASH_JSON_PROVIDER=ny`Ny JSON
- `NYASH_SELFHOST_EXEC=1`Ny Executor
- `NYASH_GC_MODE=rc|rc+cycle|stw`既定rc/ `NYASH_GC_METRICS=1`(任意)
ドキュメント更新(本日)
- phase15/README.md: 20250926 更新ートを追加JSON→SelfHost への舵切り、TokenCursor/PHI/Loop PHI 統合の反映)
- phase15/ROADMAP.md: Now/Next を刷新JSON ライブラリを Next1 に昇格、Cranelift 記述は凍結注記)
- selfhostingnyexecutor.md: Stage1 に Ny JSON 依存を明記
- README.md: Phase15202509アップデートのクイックート追記Python ハーネス・トグル案内)
---
以下は履歴ノート(必要時の参照用)。最新の計画は上記ブロックを正とする。
---
Include → Using 移行状況20250926
- コード一式を `using` に統一apps/examples/selfhost/JSON Native 等)。
@ -56,12 +101,38 @@ Addendum (20250926 2nd half)
- [x] PHI 修正: incoming pred を then/else の exit ブロックに統一VM 未定義値を根治)
- [x] PHI 検証dev: 重複 pred/自己参照/CFG preds 含有の debug アサート追加
- [x] テストランナー: 出力イズの共通フィルタ化filter_noise
- [x] Legacy 撤去(1): `src/parser/depth_tracking.rs` を削除。`NyashParser` から `paren/br ace/bracket` 深度フィールドを除去し、`impl ParserUtils for NyashParser``src/parser/mod.rs` に最小実装depth 無し)で移設。既定の Smart advance は共通実装(`common.rs`を既定ONに統一`NYASH_SMART_ADVANCE=0|off|false` で無効化)。
- [x] Legacy 撤去(2): `src/parser/nyash_parser_v2.rs` を削除(参照ゼロの実験コード)。
- [x] Bridge hardening: `ParserUtils::advance``NYASH_PARSER_TOKEN_CURSOR=1` 時に改行自動スキップを停止改行処理の一元化。既定OFFのため互換維持。
Rollback簡易
- `git revert <commit>` または `git checkout``src/parser/depth_tracking.rs` を復活し、`src/parser/mod.rs``impl ParserUtils` とフィールド削除差分を戻す。
- 追加フラグ/挙動変更は無し(`NYASH_SMART_ADVANCE` の扱いは旧来と同等に既定ON
次アクション
- [x] Step2: primary/postfix/new/unary(/not/await) を TokenCursor 経路へ寄せるenv トグル配下)
- [x] Step2: parity 代表(優先順位/単項)を追加し VM↔LLVM 整合を確認
- [ ] Step3: statements 側の薄いラッパ導入env トグル時のみ Cursor を用いた if/loop/print/local/return の最小経路)
- [ ] Step3: 旧来 skip 系common.rs/depth_tracking.rs/parser_enhanced.rs参照ゼロ確認→段階撤去
- [x] Step3: statements 側の薄いラッパ導入env トグル時のみ Cursor を用いた if/loop/print/local/return の最小経路)
- [x] Step3: 旧来 skip 系削除(`should_auto_skip_newlines` / `skip_newlines(_internal)``common.rs` から撤去)。`advance` は Cursor 無効時に限り最小限の NEWLINE/; スキップのみを内蔵(非再帰)。
---
## Loop/PHI 統合リファクタ(準備段階)
目的
- if/loop の PHI 管理を将来的に一箇所へ統合(挙動は不変、段階導入)。
Phase 1完了
- 追加: `src/mir/phi_core/`scaffold のみ、挙動不変)
- `mod.rs` / `common.rs` / `if_phi.rs` / `loop_phi.rs`
- 現時点では再エクスポート無し(`builder::phi` は private / `pub(super)` のため)。
- 追加: `src/mir/mod.rs``pub mod phi_core;`
- 受け入れ: cargo check / quick(core代表) / parity 代表 PASS
- 付随: `loop_builder` 内の `IncompletePhi``phi_core::loop_phi::IncompletePhi` に移設(ロジック変更なし)
次段(提案)
- Phase 2: if系呼び出し側の import を `phi_core` に寄せるための薄い public wrapper を `phi_core::if_phi` に追加(機能同一)。
- Phase 3: `loop_builder` の PHI 部分を `phi_core::loop_phi` に段階委譲(仕様不変)。
---

View File

@ -20,6 +20,13 @@ AST JSON v0マクロ/ブリッジ): `docs/reference/ir/ast-json-v0.md`
セルフホスト1枚ガイド: `docs/how-to/self-hosting.md`
ExternCallenv.*)と println 正規化: `docs/reference/runtime/externcall.md`
Phase15202509アップデート
- LLVM は Python/llvmlite ハーネスを優先(`NYASH_LLVM_USE_HARNESS=1`。Rust VM/JIT は保守・比較用途。
- パーサの改行処理は TokenCursor に統一中(`NYASH_PARSER_TOKEN_CURSOR=1`)。
- if/else の PHI は実際の遷移元exitを pred として使用VM/LLVM パリティ緑)。
- 自己ホスト準備として Ny 製 JSON ライブラリと Ny Executor最小命令を既定OFFトグルで段階導入予定。
- 推奨トグル: `NYASH_LLVM_USE_HARNESS=1`, `NYASH_PARSER_TOKEN_CURSOR=1`, `NYASH_JSON_PROVIDER=ny`, `NYASH_SELFHOST_EXEC=1`
仕様と既知制約
- 必須不変条件Invariants: `docs/reference/invariants.md`
- 制約(既知/一時/解消済み): `docs/reference/constraints.md`

View File

@ -26,6 +26,12 @@ Quick pointers
- Emit object with harness: set `NYASH_LLVM_USE_HARNESS=1` and `NYASH_LLVM_OBJ_OUT=<path>` (defaults in tools use `tmp/`).
- Run PyVM: `NYASH_VM_USE_PY=1 ./target/release/nyash --backend vm apps/APP/main.nyash`.
Phase15 (202509) update
- Parser newline/TokenCursor 統一は env ゲート下で進行中(`NYASH_PARSER_TOKEN_CURSOR=1`)。
- if/else の PHI incoming は実際の遷移元exitへ修正済みVM/LLVM パリティ緑)。
- 自己ホスト準備として Nyash 製 JSON ライブラリと Ny Executor最小命令を既定OFFのトグルで追加予定。
- 推奨トグル: `NYASH_LLVM_USE_HARNESS=1`, `NYASH_PARSER_TOKEN_CURSOR=1`, `NYASH_JSON_PROVIDER=ny`, `NYASH_SELFHOST_EXEC=1`
Developer quickstart: see `docs/DEV_QUICKSTART.md`. Changelog highlights: `CHANGELOG.md`.
User Macros (Phase 2): `docs/guides/user-macros.md`
Exceptions (postfix catch/cleanup): `docs/guides/exception-handling.md`

View File

@ -0,0 +1,108 @@
// Json Native Compat Layer — emulate JsonDocBox/JsonNodeBox API (MVP)
// Purpose: Provide a thin, script-only compatibility wrapper so existing Ny code
// that expects JsonDocBox/JsonNodeBox style methods can operate
// with json_native without changing call sites.
using "apps/lib/json_native/parser/parser.nyash" as JsonParserUtils
using "apps/lib/json_native/core/node.nyash" as JsonNode
// Box that mimics a document holder with parse()/root()/error()
box JsonDocCompat {
_root: Box
_error: StringBox
birth() {
me._root = null
me._error = ""
}
// Parse JSON text and populate state; returns bool success
parse(text) {
local node = JsonParserUtils.parse_json(text)
if node == null {
me._root = null
me._error = "parse_error"
return false
}
me._root = node
me._error = ""
return true
}
// Return root node (wrapped by JsonNodeCompat)
root() {
if me._root == null { return null }
return new JsonNodeCompat(me._root)
}
// Return last error string (empty when no error)
error() {
return me._error
}
}
// Node wrapper that offers JsonNodeBox-like APIs
box JsonNodeCompat {
_node: Box
birth(node) {
me._node = node
}
// kind(): "null"|"bool"|"int"|"float"|"string"|"array"|"object"
kind() {
if me._node == null { return "null" }
return me._node.get_kind()
}
// get(k): object lookup
get(key) {
if me._node == null { return null }
if me._node.get_kind() != "object" { return null }
local v = me._node.object_get(key)
if v == null { return null }
return new JsonNodeCompat(v)
}
// at(i): array lookup (0-based)
at(i) {
if me._node == null { return null }
if me._node.get_kind() != "array" { return null }
local v = me._node.array_get(i)
if v == null { return null }
return new JsonNodeCompat(v)
}
// size(): array/object length
size() {
if me._node == null { return 0 }
if me._node.get_kind() == "array" { return me._node.array_size() }
if me._node.get_kind() == "object" {
// MapBox.keys() is expected to exist on underlying map
local keys = me._node.object_keys()
return keys.length()
}
return 0
}
// Scalar extractors
str() {
if me._node == null { return "" }
return me._node.as_string()
}
int() {
if me._node == null { return 0 }
return me._node.as_int()
}
bool() {
if me._node == null { return false }
return me._node.as_bool()
}
// toString (stringify underlying JSON)
stringify() {
if me._node == null { return "null" }
return me._node.stringify()
}
}

View File

@ -147,6 +147,16 @@ box JsonScanner {
return me.is_digit_char(ch) or ch == "a" or ch == "b" or ch == "c" or ch == "d" or ch == "e" or ch == "f" or ch == "A" or ch == "B" or ch == "C" or ch == "D" or ch == "E" or ch == "F"
}
// 英字判定(内蔵)
is_alpha_char(ch) {
return (ch >= "a" and ch <= "z") or (ch >= "A" and ch <= "Z")
}
// 英数字 + 下線(識別子向け)
is_alphanumeric_or_underscore(ch) {
return me.is_alpha_char(ch) or me.is_digit_char(ch) or ch == "_"
}
// ===== 高レベル読み取りメソッド =====
// 空白をスキップ
@ -199,6 +209,18 @@ box JsonScanner {
return me.text.substring(start_pos, me.position)
}
// 識別子を読み取り(英数字+アンダースコア)
read_identifier() {
local start_pos = me.position
if not me.is_alpha_char(me.current()) and me.current() != "_" {
return ""
}
loop(not me.is_eof() and me.is_alphanumeric_or_underscore(me.current())) {
me.advance()
}
return me.text.substring(start_pos, me.position)
}
// 数値文字列を読み取り
read_number() {
local start_pos = me.position

View File

@ -3,6 +3,7 @@
using "apps/lib/json_native/lexer/scanner.nyash" as JsonScanner
using "apps/lib/json_native/lexer/token.nyash" as JsonToken
using "apps/lib/json_native/utils/escape.nyash" as EscapeUtils
// Removed other dependencies - using self-contained methods
// 🎯 高精度JSONトークナイザーEverything is Box
@ -109,8 +110,8 @@ box JsonTokenizer {
return new JsonToken("ERROR", "Unterminated string literal", start_pos, me.scanner.get_position())
}
// エスケープ解除して値を取得
local unescaped = me.unquote_string(literal)
// エスケープ解除して値を取得(厳密版)
local unescaped = EscapeUtils.unquote_string(literal)
// 文字列妥当性検証
if not me.validate_string(unescaped) {
@ -141,8 +142,8 @@ box JsonTokenizer {
tokenize_keyword() {
local start_pos = me.scanner.get_position()
// アルファベット文字を読み取り
local keyword = me.scanner.read_while(this.is_identifier_char)
// アルファベット/数字/下線を読み取り(関数参照を避ける安全版)
local keyword = me.scanner.read_identifier()
// キーワード判定
local token_type = me.keyword_to_token_type(keyword)
@ -190,10 +191,7 @@ box JsonTokenizer {
}
}
// 識別子文字かどうか判定
is_identifier_char(ch) {
return me.is_alphanumeric_char(ch) or ch == "_"
}
// 数値形式の妥当性検証
validate_number_format(num_str) {

View File

@ -14,6 +14,8 @@ static box JsonParserModule {
}
}
}
box JsonParser {
tokens: ArrayBox // トークン配列
position: IntegerBox // 現在のトークン位置
@ -356,7 +358,8 @@ box JsonParser {
out.set("value", StringUtils.parse_integer(number_str))
return out
}
if StringUtils.is_float(number_str) {
// Float detection disabled in MVP pending StringUtils float API stabilization
if false {
out.set("kind", "float")
out.set("value", StringUtils.parse_float(number_str))
return out

View File

@ -0,0 +1,13 @@
using "apps/lib/json_native/core/compat.nyash" as JsonCompat
print("compat: begin")
local doc = new JsonDocCompat()
doc.parse("{\"hello\": [1,2,3], \"msg\": \"hi\"}")
local root = doc.root()
print("kind(root): " + root.kind())
print("size(hello): " + root.get("hello").size())
print("msg: " + root.get("msg").str())
print("roundtrip: " + root.stringify())
print("compat: end")

View File

@ -237,6 +237,14 @@ static box StringUtils {
start = 1
}
// 先頭ゼロの禁止("0" 単独は許可、符号付きの "-0" も許可)
if s.length() - start > 1 and s.substring(start, start + 1) == "0" {
// 2文字目以降が数字なら先頭ゼロ不正
if this.is_digit(s.substring(start + 1, start + 2)) {
return false
}
}
local i = start
loop(i < s.length()) {
if not this.is_digit(s.substring(i, i + 1)) {
@ -253,83 +261,29 @@ static box StringUtils {
return 0
}
// 超簡易実装: よく使われる数値のみ対応
// JSON処理に必要な数値パース実装
// 基本数字 0-9
if s == "0" { return 0 } if s == "1" { return 1 } if s == "2" { return 2 }
if s == "3" { return 3 } if s == "4" { return 4 } if s == "5" { return 5 }
if s == "6" { return 6 } if s == "7" { return 7 } if s == "8" { return 8 } if s == "9" { return 9 }
// よく使われる2桁数値 10-20
if s == "10" { return 10 } if s == "11" { return 11 } if s == "12" { return 12 }
if s == "13" { return 13 } if s == "14" { return 14 } if s == "15" { return 15 }
if s == "16" { return 16 } if s == "17" { return 17 } if s == "18" { return 18 }
if s == "19" { return 19 } if s == "20" { return 20 }
// JSON頻出数値
if s == "42" { return 42 } if s == "100" { return 100 } if s == "200" { return 200 }
if s == "404" { return 404 } if s == "500" { return 500 } if s == "1000" { return 1000 }
// 負数
if s == "-1" { return -1 } if s == "-2" { return -2 } if s == "-10" { return -10 }
// TODO: より完全な数値パース実装が必要(算術演算による動的パース)
// 現在はJSON処理によく使われる数値のみ対応
return 0
// 汎用整数パース(符号と任意桁に対応
local neg = false
local i = 0
if s.substring(0, 1) == "-" {
neg = true
i = 1
}
// 文字列が浮動小数点数表現かどうか(簡易版: 10進/指数部のみ対応)
// 許容: [-+]? DIGITS '.' DIGITS ([eE] [+-]? DIGITS)?
// | [-+]? DIGITS ([eE] [+-]? DIGITS)
is_float(s) {
if s.length() == 0 { return false }
// 符号処理
local start = 0
if s.substring(0, 1) == "-" or s.substring(0, 1) == "+" {
if s.length() == 1 { return false }
start = 1
}
local i = start
local has_digit = false
local has_dot = false
local has_exp = false
local acc = 0
loop(i < s.length()) {
// 各桁の数値を計算
local ch = s.substring(i, i + 1)
if this.is_digit(ch) {
has_digit = true
// '0'..'9' 前提is_integer で検証済み)
// 文字を数値へ: (ch - '0') 相当の分岐
local digit = 0
if ch == "0" { digit = 0 } else { if ch == "1" { digit = 1 } else { if ch == "2" { digit = 2 } else { if ch == "3" { digit = 3 } else {
if ch == "4" { digit = 4 } else { if ch == "5" { digit = 5 } else { if ch == "6" { digit = 6 } else { if ch == "7" { digit = 7 } else {
if ch == "8" { digit = 8 } else { if ch == "9" { digit = 9 } else { digit = 0 } } } } } } } } }
acc = acc * 10 + digit
i = i + 1
continue
}
if ch == "." {
if has_dot or has_exp { return false }
has_dot = true
i = i + 1
continue
}
if ch == "e" or ch == "E" {
if has_exp or not has_digit { return false }
has_exp = true
i = i + 1
// 指数部符号
if i < s.length() {
local sgn = s.substring(i, i + 1)
if sgn == "+" or sgn == "-" { i = i + 1 }
}
// 指数部の桁
local j = i
local exp_digit = false
loop(j < s.length()) {
local ch2 = s.substring(j, j + 1)
if this.is_digit(ch2) { exp_digit = true j = j + 1 } else { break }
}
if not exp_digit { return false }
i = j
continue
}
return false
}
if not has_digit { return false }
return has_dot or has_exp
if neg { return 0 - acc } else { return acc }
}
// 浮動小数点の簡易パース(現段階は正規化のみ。数値演算は行わない)
@ -360,3 +314,4 @@ static box StringUtils {
return s.substring(s.length() - suffix.length(), s.length()) == suffix
}
}
}

View File

@ -6,6 +6,20 @@ NyashでNyashコンパイラを書く、完全なセルフホスティングの
MIR 13命令の美しさを最大限に活かし、外部コンパイラ依存から完全に解放される。
**究極の目標80,000行→20,000行75%削減)→ さらなる最適化へ**
## 🔄 20250926 Update方針の明確化
- 実行系の優先順位: LLVM は Python/llvmlite ハーネスを主経路に固定llvm_sys 依存は前提にしない。Rust VM/JIT は保守最小・比較用。
- パーサ: TokenCursor 統一を env ゲート下で進行。Step2/3主要文の薄ラッパ完了、代表スモーク/パリティは緑。
- PHI: if/else の incoming は「実際の遷移元exit ブロック」を使用する規約で統一。dev 検証を追加pred 重複/自己参照/CFG 包含)。
- ループPHI: `phi_core` に統合IncompletePhi/スナップショット/exit PHI の責務集約)。`loop_builder.rs` は委譲化で軽量化。
- 次の主タスク: Nyash 製 JSON ライブラリJSON v0 DOM: parse/stringify。完了後に Ny Executor最小命令へ直行。
- 既定挙動は不変。新経路はすべて env トグルで optin。
推奨トグル
- `NYASH_LLVM_USE_HARNESS=1`LLVM Python ハーネス)
- `NYASH_PARSER_TOKEN_CURSOR=1`TokenCursor 経路)
- `NYASH_JSON_PROVIDER=ny`Ny JSON ライブラリ)
- `NYASH_SELFHOST_EXEC=1`Ny Executor 最小経路)
## 🎯 フェーズの目的
1. **完全なセルフホスティング**: NyashコンパイラをNyashで実装

View File

@ -27,60 +27,23 @@ This roadmap is a living checklist to advance Phase 15 with small, safe boxes. U
## Next (small boxes)
1) EXE-first: Selfhost Parser → EXEPhase 15.2)🚀
- tools/build_compiler_exe.sh で EXE をビルド同梱distパッケージ作成
- dist/nyash_compiler/{nyash_compiler,nyash.toml,plugins/...} で独立実行
- 入力: Nyソース → 出力: JSON v0stdout
- Smokes: sample.nyash→JSON 行生成JSONのみ出力
- リスク: プラグイン解決FileBoxをnyash.tomlで固定
2) LLVM Native EXE GenerationAOTパイプライン継続
- Python/llvmlite implementation as primary path (2400 lines, 10x faster development)
- LLVM backend object → executable pipeline completion
- Separate `nyash-llvm-compiler` crate (reduce main build weight)
- Input: MIR (JSON/binary) → Output: native executable
- Link with nyrt runtime (static/dynamic options)
- Plugin all-direction build strategy (.so/.o/.a simultaneous generation)
- Integration: `nyash --backend llvm --emit exe program.nyash -o program.exe`
3) Standard Ny std impl (P0→実体化)
- Implement P0 methods for string/array/map in Nyash (keep NyRT primitives minimal)
- Enable via `nyash.toml` `[ny_plugins]` (optin); extend `tools/jit_smoke.sh`
4) Ny compiler MVP (Ny→MIR on JIT path) (Phase 15.3) 🎯
- Ny tokenizer + recursivedescent parser (current subset) in Ny; drive existing MIR builder
- Target: 800 lines parser + 2500 lines MIR builder = 3300 lines total
- No circular dependency: nyrt provides StringBox/ArrayBox via C ABI
- Flag path: `NYASH_USE_NY_COMPILER=1` to switch rust→ny compiler; rust parser as fallback
- Add apps/selfhost-compiler/ and minimal smokes
- Stage1 checklist:
- [ ] return/int/string/arithmetic/paren JSON v0 emit
- [ ] Minimal ASInewline separator + continuation tokens
- [ ] Smokes: `return 1+2*3` / grouping / string literal
- Stage2 checklist:
- [ ] local/if/loop/call/method/new/var/logical/compare
- [ ] PHI 合流は Bridge に委譲If/Loop
- [ ] Smokes: nested if / loop 累積 / and/or × if/loop
5) Phase 15.5: Core Box Unification3層→2層革命🎯
- コアBoxnyrt内蔵削除、プラグインBox/ユーザーBoxの2層に統一
- 環境変数制御で段階的移行: `NYASH_USE_PLUGIN_CORE_BOXES=1`
- 削減目標: 約700行nyrt実装600行 + 特別扱い100行
- DLL動作確認→Nyashコード化の安全な移行戦略
- **using構文完全実装**: compiler.nyashのusing構文パース問題解決
- **LLVM ExternCall改善**: print出力問題修正LLVMバックエンド
- 詳細: [phase-15.5-core-box-unification.md](phase-15.5-core-box-unification.md)
6) PHI 自動化は Phase15 後LoopForm = MIR18
- Phase15: 現行の BridgePHI を維持し、E2E 緑とパリティを最優先
- MIR18 (LoopForm): LoopForm 強化逆Loweringで PHI を自動生成(合流点の定型化)
7) Bootstrap loop (c0→c1→c1')
- Use existing trace/hash harness to compare parity; add optional CI gate
- **This achieves self-hosting!** Nyash compiles Nyash
8) VM Layer in Nyash (Phase 15.4) ⚡
- Implement MIR interpreter in Nyash (13 core instructions)
- Dynamic dispatch via MapBox for instruction handlers
- BoxCall/ExternCall bridge to existing infrastructure
- Optional LLVM JIT acceleration for hot paths
- Enable instant execution without compilation
- Expected: 5000 lines for complete VM implementation
9) Plugins CI split (継続)
- Core alwayson (JIT, plugins disabled); Plugins as optional job (strict off by default)
1) Ny JSON ライブラリ(最小 DOM / JSON v0 対応)
- Nyash 製の parse/stringifyobject/array/string/number/bool/null
- Env: `NYASH_JSON_PROVIDER=ny`既定OFF
- Smokes: roundtrip/エラー位置検証quick 任意; CI非ブロック
2) Ny Executor最小命令セット
- ops: const/binop/compare/branch/jump/ret/phiBox 呼び出しは後段)。
- Env: `NYASH_SELFHOST_EXEC=1`既定OFF
- Parity: PyVM/LLVM harness と stdout/exit の一致。
3) 呼び出し最小Console/String/Array/Map P0
- call/externcall/boxcall の最小を接続。未知 extern は STRICT で拒否。
4) Selfhost Parser の EXE 化(任意・後回し可)
- `tools/build_compiler_exe.sh` により JSON v0 emit の単体配布(開発者向け)。
5) PHI 自動化は Phase15 後LoopForm = MIR18
- Phase15: 現行の BridgePHI を維持規約は「incoming pred=実際の遷移元」)。
- MIR18: LoopForm 強化逆Loweringでの自動化に委譲設計のみ先行
6) Plugins CI split継続
- Plugins は任意ジョブstrict offを維持。Core は軽量 quick を常時。
## Later (incremental)
@ -99,6 +62,8 @@ This roadmap is a living checklist to advance Phase 15 with small, safe boxes. U
- JSON dump: `NYASH_DUMP_JSON_IR=1`
- 予告LoopForm: MIR18 で仕様化予定
- Selfhost compiler: `NYASH_USE_NY_COMPILER=1`, child quiet: `NYASH_JSON_ONLY=1`
- JSON provider: `NYASH_JSON_PROVIDER=ny`Ny JSON; 既定OFF
- Ny executor: `NYASH_SELFHOST_EXEC=1`既定OFF
- EXE-first bundle: `tools/build_compiler_exe.sh``dist/nyash_compiler/`
- Load Ny plugins: `NYASH_LOAD_NY_PLUGINS=1` / `--load-ny-plugins`
- AOT smoke: `CLIF_SMOKE_RUN=1`

View File

@ -37,6 +37,7 @@ Rust ランナー側は PyVM 経路にて `NYASH_SELFHOST_EXEC=1` を検出し
### Stage 1 — MIR ローダ23日
- `mir_loader.nyash` で JSON v0 を読み込み、関数/ブロック/命令の構造体に展開(最初は要約のみ)。
- 依存: Nyash 製 JSON ライブラリ(`NYASH_JSON_PROVIDER=ny`)で DOM を提供既定OFF、開発時のみON
- 受け入れ: ロードのみのスモーク(構文要素の個数検証)。
- 備考: 立ち上げ初期は PyVM ハーネス用 MIR JSON`{"functions":…}`も受理し、要約functions数だけ行う既定OFF

View File

@ -8,6 +8,15 @@ NYASH_DEV_AT_LOCAL = "1"
[using]
paths = ["apps", "lib", "."]
# Optional package-style entries (opt-in via using resolver)
[using.json_native]
path = "apps/lib/json_native/"
main = "parser/parser.nyash"
[using.aliases]
# Resolve `using json as ...` to json_native when desired
json = "json_native"
[modules]
# Map logical namespaces to Nyash source paths (consumed by runner)
selfhost.compiler.debug = "apps/selfhost/compiler/boxes/debug_box.nyash"

View File

@ -336,12 +336,12 @@ pub fn enable_using() -> bool {
}
}
pub fn resolve_fix_braces() -> bool {
// Phase 15: デフォルトONusing時のブレース均等修正が必須
// NYASH_RESOLVE_FIX_BRACES=0 で明示的に無効化可能
match std::env::var("NYASH_RESOLVE_FIX_BRACES").ok().as_deref() {
Some("0") | Some("false") | Some("off") => false,
_ => enable_using(), // using有効時は自動でON
}
// Safer default: OFF誤補正の副作用を避ける
// 明示ON: NYASH_RESOLVE_FIX_BRACES=1
matches!(
std::env::var("NYASH_RESOLVE_FIX_BRACES").ok().as_deref(),
Some("1") | Some("true") | Some("on")
)
}
pub fn vm_use_py() -> bool {
std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1")

View File

@ -93,6 +93,8 @@ pub struct MirBuilder {
plugin_method_sigs: HashMap<(String, String), super::MirType>,
/// Current static box name when lowering a static box body (e.g., "Main")
current_static_box: Option<String>,
/// Index of static methods seen during lowering: name -> [(BoxName, arity)]
pub(super) static_method_index: std::collections::HashMap<String, Vec<(String, usize)>>,
// include guards removed
@ -148,6 +150,7 @@ impl MirBuilder {
value_types: HashMap::new(),
plugin_method_sigs,
current_static_box: None,
static_method_index: std::collections::HashMap::new(),
loop_header_stack: Vec::new(),
loop_exit_stack: Vec::new(),

View File

@ -296,6 +296,13 @@ impl super::MirBuilder {
name: String,
args: Vec<ASTNode>,
) -> Result<ValueId, String> {
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
eprintln!(
"[builder] function-call name={} static_ctx={}",
name,
self.current_static_box.as_deref().unwrap_or("")
);
}
// Minimal TypeOp wiring via function-style: isType(value, "Type"), asType(value, "Type")
if (name == "isType" || name == "asType") && args.len() == 2 {
if let Some(type_name) = special_handlers::extract_string_literal(&args[1]) {
@ -321,7 +328,7 @@ impl super::MirBuilder {
if let Some(res) = self.try_handle_math_function(&name, raw_args) { return res; }
// Build argument values
// Build argument values first (needed for arity-aware fallback)
let mut arg_values = Vec::new();
for a in args {
arg_values.push(self.build_expression(a)?);
@ -344,9 +351,30 @@ impl super::MirBuilder {
let dst = self.value_gen.next();
// === ChatGPT5 Pro Design: Type-safe function call resolution ===
// Resolve call target using new type-safe system
let callee = self.resolve_call_target(&name)?;
// Resolve call target using new type-safe system; if it fails, try static-method fallback
let callee = match self.resolve_call_target(&name) {
Ok(c) => c,
Err(_e) => {
// Fallback: if exactly one static method with this name and arity is known, call it.
if let Some(cands) = self.static_method_index.get(&name) {
let mut matches: Vec<(String, usize)> = cands
.iter()
.cloned()
.filter(|(_, ar)| *ar == arg_values.len())
.collect();
if matches.len() == 1 {
let (bx, _arity) = matches.remove(0);
let dst = self.value_gen.next();
let func_name = format!("{}.{}{}", bx, name, format!("/{}", arg_values.len()));
// Emit legacy global call to the lowered static method function
self.emit_legacy_call(Some(dst), CallTarget::Global(func_name), arg_values)?;
return Ok(dst);
}
}
// Propagate original error
return Err(format!("Unresolved function: '{}'. {}", name, super::call_resolution::suggest_resolution(&name)));
}
};
// Legacy compatibility: Create dummy func value for old systems
let fun_val = self.value_gen.next();

View File

@ -46,7 +46,17 @@ pub fn resolve_call_target(
return Ok(Callee::Extern(name.to_string()));
}
// 5. Resolution failed - this prevents runtime string-based resolution
// 5. Fallback: when inside a static box, treat bare `name()` as a static method of the box.
// This helps scripts that omit the box qualifier inside the same static box scope.
if let Some(box_name) = current_static_box {
return Ok(Callee::Method {
box_name: box_name.clone(),
method: name.to_string(),
receiver: None,
});
}
// 6. Resolution failed - prevent runtime string-based resolution
Err(format!("Unresolved function: '{}'. {}", name, suggest_resolution(name)))
}

View File

@ -137,6 +137,11 @@ impl super::MirBuilder {
params.clone(),
body.clone(),
)?;
// Index static method for fallback resolution of bare calls
self.static_method_index
.entry(method_name.clone())
.or_insert_with(Vec::new)
.push((name.clone(), params.len()));
}
}
// Return void for declaration context

View File

@ -71,10 +71,10 @@ impl MirBuilder {
self.push_if_merge(merge_block);
// Pre-analysis: identify then/else assigned var for skip and hints
let assigned_then_pre = super::phi::extract_assigned_var(&then_ast_for_analysis);
let assigned_then_pre = crate::mir::phi_core::if_phi::extract_assigned_var(&then_ast_for_analysis);
let assigned_else_pre = else_ast_for_analysis
.as_ref()
.and_then(|e| super::phi::extract_assigned_var(e));
.and_then(|e| crate::mir::phi_core::if_phi::extract_assigned_var(e));
let pre_then_var_value = assigned_then_pre
.as_ref()
.and_then(|name| pre_if_var_map.get(name).copied());

View File

@ -3,6 +3,28 @@ use crate::ast::ASTNode;
// Lifecycle routines extracted from builder.rs
impl super::MirBuilder {
fn preindex_static_methods_from_ast(&mut self, node: &ASTNode) {
match node {
ASTNode::Program { statements, .. } => {
for st in statements {
self.preindex_static_methods_from_ast(st);
}
}
ASTNode::BoxDeclaration { name, methods, is_static, .. } => {
if *is_static {
for (mname, mast) in methods {
if let ASTNode::FunctionDeclaration { params, .. } = mast {
self.static_method_index
.entry(mname.clone())
.or_insert_with(Vec::new)
.push((name.clone(), params.len()));
}
}
}
}
_ => {}
}
}
pub(super) fn prepare_module(&mut self) -> Result<(), String> {
let module = MirModule::new("main".to_string());
let main_signature = FunctionSignature {
@ -35,6 +57,9 @@ impl super::MirBuilder {
}
pub(super) fn lower_root(&mut self, ast: ASTNode) -> Result<ValueId, String> {
// Pre-index static methods to enable safe fallback for bare calls in using-prepended code
let snapshot = ast.clone();
self.preindex_static_methods_from_ast(&snapshot);
self.build_expression(ast)
}
@ -74,7 +99,7 @@ impl super::MirBuilder {
inferred = Some(mt);
break 'outer;
}
if let Some(mt) = super::phi::infer_type_from_phi(
if let Some(mt) = crate::mir::phi_core::if_phi::infer_type_from_phi(
&function,
*v,
&self.value_types,
@ -89,7 +114,7 @@ impl super::MirBuilder {
inferred = Some(mt);
break;
}
if let Some(mt) = super::phi::infer_type_from_phi(
if let Some(mt) = crate::mir::phi_core::if_phi::infer_type_from_phi(
&function,
*v,
&self.value_types,

View File

@ -1,109 +1,11 @@
use super::MirBuilder;
use crate::ast::ASTNode;
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, MirType, ValueId};
use crate::mir::{BasicBlockId, MirInstruction, ValueId};
use std::collections::HashMap;
// PHI-based return type inference helper
pub(super) fn infer_type_from_phi(
function: &MirFunction,
ret_val: ValueId,
types: &HashMap<ValueId, MirType>,
) -> Option<MirType> {
for (_bid, bb) in function.blocks.iter() {
for inst in bb.instructions.iter() {
if let MirInstruction::Phi { dst, inputs } = inst {
if *dst == ret_val {
let mut it = inputs.iter().filter_map(|(_, v)| types.get(v));
if let Some(first) = it.next() {
if it.all(|mt| mt == first) {
return Some(first.clone());
}
}
}
}
}
}
None
}
// Local helper for if-statement analysis (moved from stmts.rs)
pub(super) fn extract_assigned_var(ast: &ASTNode) -> Option<String> {
match ast {
ASTNode::Assignment { target, .. } => {
if let ASTNode::Variable { name, .. } = target.as_ref() {
Some(name.clone())
} else {
None
}
}
ASTNode::Program { statements, .. } => {
statements.last().and_then(|st| extract_assigned_var(st))
}
ASTNode::If {
then_body,
else_body,
..
} => {
// Look into nested if: if both sides assign the same variable, propagate that name upward.
let then_prog = ASTNode::Program {
statements: then_body.clone(),
span: crate::ast::Span::unknown(),
};
let tvar = extract_assigned_var(&then_prog);
let evar = else_body.as_ref().and_then(|eb| {
let ep = ASTNode::Program {
statements: eb.clone(),
span: crate::ast::Span::unknown(),
};
extract_assigned_var(&ep)
});
match (tvar, evar) {
(Some(tv), Some(ev)) if tv == ev => Some(tv),
_ => None,
}
}
_ => None,
}
}
// Local helper has moved to phi_core::if_phi; keep call sites minimal
impl MirBuilder {
#[inline]
#[cfg(debug_assertions)]
fn debug_verify_phi_inputs(&self, inputs: &Vec<(BasicBlockId, ValueId)>) {
use std::collections::HashSet;
if let Some(cur_bb) = self.current_block {
let mut seen = HashSet::new();
for (pred, _v) in inputs.iter() {
debug_assert_ne!(
*pred, cur_bb,
"PHI incoming predecessor must not be the merge block itself"
);
debug_assert!(
seen.insert(*pred),
"Duplicate PHI incoming predecessor detected: {:?}",
pred
);
}
// Ensure all incoming predecessors are known CFG predecessors of the merge block
if let Some(func) = &self.current_function {
if let Some(block) = func.blocks.get(&cur_bb) {
for (pred, _v) in inputs.iter() {
debug_assert!(
block.predecessors.contains(pred),
"PHI incoming pred {:?} is not a predecessor of merge bb {:?}",
pred,
cur_bb
);
}
}
}
}
}
#[inline]
#[cfg(not(debug_assertions))]
fn debug_verify_phi_inputs(&self, _inputs: &Vec<(BasicBlockId, ValueId)>) {}
/// Merge all variables modified in then/else relative to pre_if_snapshot.
/// In PHI-off mode inserts edge copies from branch exits to merge. In PHI-on mode emits Phi.
/// `skip_var` allows skipping a variable already merged elsewhere (e.g., bound to an expression result).
@ -118,37 +20,23 @@ impl MirBuilder {
else_map_end_opt: &Option<std::collections::HashMap<String, super::ValueId>>,
skip_var: Option<&str>,
) -> Result<(), String> {
use std::collections::HashSet;
let mut names: HashSet<&str> = HashSet::new();
for k in then_map_end.keys() { names.insert(k.as_str()); }
if let Some(emap) = else_map_end_opt.as_ref() {
for k in emap.keys() { names.insert(k.as_str()); }
}
// Only variables that changed against pre_if_snapshot
let mut changed: Vec<&str> = Vec::new();
for &name in &names {
let pre = pre_if_snapshot.get(name);
let t = then_map_end.get(name);
let e = else_map_end_opt.as_ref().and_then(|m| m.get(name));
// changed when either branch value differs from pre
if (t.is_some() && Some(t.copied().unwrap()) != pre.copied())
|| (e.is_some() && Some(e.copied().unwrap()) != pre.copied())
{
changed.push(name);
}
}
let changed = crate::mir::phi_core::if_phi::compute_modified_names(
pre_if_snapshot,
then_map_end,
else_map_end_opt,
);
for name in changed {
if skip_var.map(|s| s == name).unwrap_or(false) {
continue;
}
let pre = match pre_if_snapshot.get(name) {
let pre = match pre_if_snapshot.get(name.as_str()) {
Some(v) => *v,
None => continue, // unknown before-if; skip
};
let then_v = then_map_end.get(name).copied().unwrap_or(pre);
let then_v = then_map_end.get(name.as_str()).copied().unwrap_or(pre);
let else_v = else_map_end_opt
.as_ref()
.and_then(|m| m.get(name).copied())
.and_then(|m| m.get(name.as_str()).copied())
.unwrap_or(pre);
// フェーズM: 常にPHI命令を使用no_phi_mode撤廃
// incoming の predecessor は "実際に merge に遷移してくる出口ブロック" を使用する
@ -156,9 +44,11 @@ impl MirBuilder {
let else_pred = else_exit_block_opt.unwrap_or(else_block);
let merged = self.value_gen.next();
let inputs = vec![(then_pred, then_v), (else_pred, else_v)];
self.debug_verify_phi_inputs(&inputs);
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
}
self.emit_instruction(MirInstruction::Phi { dst: merged, inputs })?;
self.variable_map.insert(name.to_string(), merged);
self.variable_map.insert(name, merged);
}
Ok(())
}
@ -181,10 +71,10 @@ impl MirBuilder {
) -> Result<ValueId, String> {
// If only the then-branch assigns a variable (e.g., `if c { x = ... }`) and the else
// does not assign the same variable, bind that variable to a Phi of (then_value, pre_if_value).
let assigned_var_then = extract_assigned_var(then_ast_for_analysis);
let assigned_var_then = crate::mir::phi_core::if_phi::extract_assigned_var(then_ast_for_analysis);
let assigned_var_else = else_ast_for_analysis
.as_ref()
.and_then(|a| extract_assigned_var(a));
.and_then(|a| crate::mir::phi_core::if_phi::extract_assigned_var(a));
let result_val = self.value_gen.next();
// フェーズM: no_phi_mode分岐削除常にPHI命令を使用
@ -213,7 +103,9 @@ impl MirBuilder {
let else_pred = else_exit_block_opt.unwrap_or(else_block);
// Emit Phi for the assigned variable and bind it
let inputs = vec![(then_pred, then_value_for_var), (else_pred, else_value_for_var)];
self.debug_verify_phi_inputs(&inputs);
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
}
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs })?;
self.variable_map = pre_if_var_map.clone();
self.variable_map.insert(var_name, result_val);
@ -222,7 +114,9 @@ impl MirBuilder {
let then_pred = then_exit_block_opt.unwrap_or(then_block);
let else_pred = else_exit_block_opt.unwrap_or(else_block);
let inputs = vec![(then_pred, then_value_raw), (else_pred, else_value_raw)];
self.debug_verify_phi_inputs(&inputs);
if let (Some(func), Some(cur_bb)) = (&self.current_function, self.current_block) {
crate::mir::phi_core::common::debug_verify_phi_inputs(func, cur_bb, &inputs);
}
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs })?;
// Merge variable map conservatively to pre-if snapshot (no new bindings)
self.variable_map = pre_if_var_map.clone();

View File

@ -13,20 +13,7 @@ impl MirBuilder {
self.lower_if_form(condition, then_branch, else_branch)
}
/// Extract assigned variable name from an AST node if it represents an assignment to a variable.
/// Handles direct Assignment and Program with trailing single-statement Assignment.
fn extract_assigned_var(ast: &ASTNode) -> Option<String> {
match ast {
ASTNode::Assignment { target, .. } => {
if let ASTNode::Variable { name, .. } = target.as_ref() { Some(name.clone()) } else { None }
}
ASTNode::Program { statements, .. } => {
// Inspect the last statement as the resulting value of the block
statements.last().and_then(|st| Self::extract_assigned_var(st))
}
_ => None,
}
}
// Assigned variable extraction is centralized in phi_core::if_phi now.
/// Build a loop statement: loop(condition) { body }
///

View File

@ -6,6 +6,7 @@
*/
use super::{BasicBlockId, ConstValue, MirInstruction, ValueId};
use crate::mir::phi_core::loop_phi::IncompletePhi;
use crate::ast::ASTNode;
use std::collections::HashMap;
@ -15,16 +16,7 @@ use super::utils::{
capture_actual_predecessor_and_jump,
};
/// 不完全なPhi nodeの情報
#[derive(Debug, Clone)]
struct IncompletePhi {
/// Phi nodeの結果ValueId
phi_id: ValueId,
/// 変数名
var_name: String,
/// 既知の入力値 (predecessor block id, value)
known_inputs: Vec<(BasicBlockId, ValueId)>,
}
// IncompletePhi has moved to phi_core::loop_phi
/// ループビルダー - SSA形式でのループ構築を管理
pub struct LoopBuilder<'a> {
@ -52,6 +44,8 @@ pub struct LoopBuilder<'a> {
impl<'a> LoopBuilder<'a> {
// Implement phi_core LoopPhiOps on LoopBuilder for in-place delegation
// =============================================================
// Control Helpers — break/continue/jumps/unreachable handling
// =============================================================
@ -125,33 +119,9 @@ impl<'a> LoopBuilder<'a> {
body: Vec<ASTNode>,
) -> Result<ValueId, String> {
// Pre-scan body for simple carrier pattern (up to 2 assigned variables, no break/continue)
fn collect_assigns(n: &ASTNode, vars: &mut Vec<String>, has_ctrl: &mut bool) {
match n {
ASTNode::Assignment { target, .. } => {
if let ASTNode::Variable { name, .. } = target.as_ref() {
if !vars.iter().any(|v| v == name) {
vars.push(name.clone());
}
}
}
ASTNode::Break { .. } | ASTNode::Continue { .. } => { *has_ctrl = true; }
ASTNode::If { then_body, else_body, .. } => {
let tp = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() };
collect_assigns(&tp, vars, has_ctrl);
if let Some(eb) = else_body {
let ep = ASTNode::Program { statements: eb.clone(), span: crate::ast::Span::unknown() };
collect_assigns(&ep, vars, has_ctrl);
}
}
ASTNode::Program { statements, .. } => {
for s in statements { collect_assigns(s, vars, has_ctrl); }
}
_ => {}
}
}
let mut assigned_vars: Vec<String> = Vec::new();
let mut has_ctrl = false;
for st in &body { collect_assigns(st, &mut assigned_vars, &mut has_ctrl); }
for st in &body { crate::mir::phi_core::loop_phi::collect_carrier_assigns(st, &mut assigned_vars, &mut has_ctrl); }
if !has_ctrl && !assigned_vars.is_empty() && assigned_vars.len() <= 2 {
// Emit a carrier hint (no-op sink by default; visible with NYASH_MIR_TRACE_HINTS=1)
self.parent_builder.hint_loop_carrier(assigned_vars.clone());
@ -214,7 +184,11 @@ impl<'a> LoopBuilder<'a> {
let latch_snapshot = self.get_current_variable_map();
// 以前は body_id に保存していたが、複数ブロックのボディや continue 混在時に不正確になるため
// 実際の latch_id に対してスナップショットを紐づける
self.block_var_maps.insert(latch_id, latch_snapshot);
crate::mir::phi_core::loop_phi::save_block_snapshot(
&mut self.block_var_maps,
latch_id,
&latch_snapshot,
);
// Only jump back to header if the latch block is not already terminated
{
let need_jump = {
@ -266,113 +240,49 @@ impl<'a> LoopBuilder<'a> {
header_id: BasicBlockId,
preheader_id: BasicBlockId,
) -> Result<(), String> {
// 現在の変数マップから、ループで使用される可能性のある変数を取得
let current_vars = self.get_current_variable_map();
// preheader時点のスナップショット後でphi入力の解析に使う
self.block_var_maps
.insert(preheader_id, current_vars.clone());
// 各変数に対して不完全なPhi nodeを作成
let mut incomplete_phis = Vec::new();
for (var_name, &value_before) in &current_vars {
let phi_id = self.new_value();
// 不完全なPhi nodeを作成preheaderからの値のみ設定
let incomplete_phi = IncompletePhi {
phi_id,
var_name: var_name.clone(),
known_inputs: vec![(preheader_id, value_before)],
};
incomplete_phis.push(incomplete_phi);
// フェーズM: no_phi_mode分岐削除常にPHI使用
// 変数マップを更新Phi nodeの結果を使用
self.update_variable(var_name.clone(), phi_id);
}
// 不完全なPhi nodeを記録
self.incomplete_phis.insert(header_id, incomplete_phis);
crate::mir::phi_core::loop_phi::save_block_snapshot(
&mut self.block_var_maps,
preheader_id,
&current_vars,
);
let incs = crate::mir::phi_core::loop_phi::prepare_loop_variables_with(
self,
header_id,
preheader_id,
&current_vars,
)?;
self.incomplete_phis.insert(header_id, incs);
Ok(())
}
/// ブロックをシールし、不完全なPhi nodeを完成させる
fn seal_block(&mut self, block_id: BasicBlockId, latch_id: BasicBlockId) -> Result<(), String> {
// 不完全なPhi nodeを取得
if let Some(incomplete_phis) = self.incomplete_phis.remove(&block_id) {
for mut phi in incomplete_phis {
for (cid, snapshot) in &self.continue_snapshots {
if let Some(v) = snapshot.get(&phi.var_name) {
phi.known_inputs.push((*cid, *v));
let cont_snaps = self.continue_snapshots.clone();
crate::mir::phi_core::loop_phi::seal_incomplete_phis_with(
self,
block_id,
latch_id,
incomplete_phis,
&cont_snaps,
)?;
}
}
let value_after = self
.get_variable_at_block(&phi.var_name, latch_id)
.ok_or_else(|| format!("Variable {} not found at latch block", phi.var_name))?;
phi.known_inputs.push((latch_id, value_after));
// フェーズM: 常にPHI命令を使用no_phi_mode分岐削除
self.emit_phi_at_block_start(block_id, phi.phi_id, phi.known_inputs)?;
self.update_variable(phi.var_name.clone(), phi.phi_id);
}
}
// ブロックをシール済みとしてマーク
self.mark_block_sealed(block_id)?;
Ok(())
}
/// Exitブロックで変数のPHIを生成breakポイントでの値を統一
fn create_exit_phis(&mut self, header_id: BasicBlockId, exit_id: BasicBlockId) -> Result<(), String> {
// 全変数名を収集exit_snapshots内のすべての変数
let mut all_vars = std::collections::HashSet::new();
// Header直行ケース0回実行の変数を収集
let header_vars = self.get_current_variable_map();
for var_name in header_vars.keys() {
all_vars.insert(var_name.clone());
}
// break時点の変数を収集
for (_, snapshot) in &self.exit_snapshots {
for var_name in snapshot.keys() {
all_vars.insert(var_name.clone());
}
}
// 各変数に対してExit PHIを生成
for var_name in all_vars {
let mut phi_inputs = Vec::new();
// Header直行ケース0回実行の入力
if let Some(header_value) = header_vars.get(&var_name) {
phi_inputs.push((header_id, *header_value));
}
// 各breakポイントからの入力
for (block_id, snapshot) in &self.exit_snapshots {
if let Some(value) = snapshot.get(&var_name) {
phi_inputs.push((*block_id, *value));
}
}
// PHI入力が2つ以上なら、PHIードを生成
if phi_inputs.len() > 1 {
let phi_dst = self.new_value();
self.emit_phi_at_block_start(exit_id, phi_dst, phi_inputs)?;
self.update_variable(var_name, phi_dst);
} else if phi_inputs.len() == 1 {
// 単一入力なら直接使用(最適化)
self.update_variable(var_name, phi_inputs[0].1);
}
}
Ok(())
let exit_snaps = self.exit_snapshots.clone();
crate::mir::phi_core::loop_phi::build_exit_phis_with(
self,
header_id,
exit_id,
&header_vars,
&exit_snaps,
)
}
// --- ヘルパーメソッド(親ビルダーへの委譲) ---
@ -549,7 +459,7 @@ impl<'a> LoopBuilder<'a> {
// Capture pre-if variable map (used for phi normalization)
let pre_if_var_map = self.get_current_variable_map();
let pre_then_var_value = pre_if_var_map.clone();
// (legacy) kept for earlier merge style; now unified helpers compute deltas directly.
// then branch
self.set_current_block(then_bb)?;
@ -588,60 +498,74 @@ impl<'a> LoopBuilder<'a> {
// Continue at merge
self.set_current_block(merge_bb)?;
// collect assigned variables in both branches
fn collect_assigned_vars(ast: &ASTNode, out: &mut std::collections::HashSet<String>) {
match ast {
ASTNode::Assignment { target, .. } => {
if let ASTNode::Variable { name, .. } = target.as_ref() { out.insert(name.clone()); }
}
ASTNode::Program { statements, .. } => { for s in statements { collect_assigned_vars(s, out); } }
ASTNode::If { then_body, else_body, .. } => {
let tp = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() };
collect_assigned_vars(&tp, out);
if let Some(eb) = else_body {
let ep = ASTNode::Program { statements: eb.clone(), span: crate::ast::Span::unknown() };
collect_assigned_vars(&ep, out);
}
}
_ => {}
}
}
let mut vars: std::collections::HashSet<String> = std::collections::HashSet::new();
let then_prog = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() };
collect_assigned_vars(&then_prog, &mut vars);
crate::mir::phi_core::if_phi::collect_assigned_vars(&then_prog, &mut vars);
if let Some(es) = &else_body {
let else_prog = ASTNode::Program { statements: es.clone(), span: crate::ast::Span::unknown() };
collect_assigned_vars(&else_prog, &mut vars);
crate::mir::phi_core::if_phi::collect_assigned_vars(&else_prog, &mut vars);
}
// Reset to pre-if map before rebinding to ensure a clean environment
self.parent_builder.variable_map = pre_if_var_map.clone();
for var_name in vars.into_iter() {
let then_val = then_var_map_end.get(&var_name).copied().or_else(|| pre_then_var_value.get(&var_name).copied());
let else_val = else_var_map_end_opt.as_ref().and_then(|m| m.get(&var_name).copied()).or_else(|| pre_then_var_value.get(&var_name).copied());
if let (Some(tv), Some(ev)) = (then_val, else_val) {
let mut incomings: Vec<(BasicBlockId, ValueId)> = Vec::new();
if let Some(pred) = then_pred_to_merge { incomings.push((pred, tv)); }
if let Some(pred) = else_pred_to_merge { incomings.push((pred, ev)); }
match incomings.len() {
0 => {}
1 => {
let (_pred, v) = incomings[0];
self.parent_builder.variable_map.insert(var_name, v);
}
_ => {
let phi_id = self.new_value();
// フェーズM: 常にPHI命令を使用no_phi_mode分岐削除
self.emit_phi_at_block_start(merge_bb, phi_id, incomings)?;
self.parent_builder.variable_map.insert(var_name, phi_id);
}
// Use shared helper to merge modified variables at merge block
struct Ops<'b, 'a>(&'b mut LoopBuilder<'a>);
impl<'b, 'a> crate::mir::phi_core::if_phi::PhiMergeOps for Ops<'b, 'a> {
fn new_value(&mut self) -> ValueId { self.0.new_value() }
fn emit_phi_at_block_start(
&mut self,
block: BasicBlockId,
dst: ValueId,
inputs: Vec<(BasicBlockId, ValueId)>,
) -> Result<(), String> { self.0.emit_phi_at_block_start(block, dst, inputs) }
fn update_var(&mut self, name: String, value: ValueId) { self.0.parent_builder.variable_map.insert(name, value); }
fn debug_verify_phi_inputs(&mut self, merge_bb: BasicBlockId, inputs: &[(BasicBlockId, ValueId)]) {
if let Some(ref func) = self.0.parent_builder.current_function {
crate::mir::phi_core::common::debug_verify_phi_inputs(func, merge_bb, inputs);
}
}
}
// Reset to pre-if snapshot, then delegate to shared helper
self.parent_builder.variable_map = pre_if_var_map.clone();
let mut ops = Ops(self);
crate::mir::phi_core::if_phi::merge_modified_at_merge_with(
&mut ops,
merge_bb,
then_bb,
else_bb,
then_pred_to_merge,
else_pred_to_merge,
&pre_if_var_map,
&then_var_map_end,
&else_var_map_end_opt,
None,
)?;
let void_id = self.new_value();
self.emit_const(void_id, ConstValue::Void)?;
Ok(void_id)
}
}
// Implement phi_core LoopPhiOps on LoopBuilder for in-place delegation
impl crate::mir::phi_core::loop_phi::LoopPhiOps for LoopBuilder<'_> {
fn new_value(&mut self) -> ValueId { self.new_value() }
fn emit_phi_at_block_start(
&mut self,
block: BasicBlockId,
dst: ValueId,
inputs: Vec<(BasicBlockId, ValueId)>,
) -> Result<(), String> {
self.emit_phi_at_block_start(block, dst, inputs)
}
fn update_var(&mut self, name: String, value: ValueId) { self.update_variable(name, value) }
fn get_variable_at_block(&mut self, name: &str, block: BasicBlockId) -> Option<ValueId> {
// Call the inherent method (immutable borrow) to avoid recursion
LoopBuilder::get_variable_at_block(self, name, block)
}
fn debug_verify_phi_inputs(&mut self, merge_bb: BasicBlockId, inputs: &[(BasicBlockId, ValueId)]) {
if let Some(ref func) = self.parent_builder.current_function {
crate::mir::phi_core::common::debug_verify_phi_inputs(func, merge_bb, inputs);
}
}
}

View File

@ -20,6 +20,7 @@ pub mod loop_api; // Minimal LoopBuilder facade (adapter-ready)
pub mod loop_builder; // SSA loop construction with phi nodes
pub mod optimizer;
pub mod utils; // Phase 15 control flow utilities for root treatment
pub mod phi_core; // Phase 1 scaffold: unified PHI entry (re-exports only)
pub mod optimizer_passes; // optimizer passes (normalize/diagnostics)
pub mod optimizer_stats; // extracted stats struct
pub mod passes;

View File

@ -0,0 +1,49 @@
/*!
* phi_core::common shared types and invariants (scaffold)
*
* Phase 1 keeps this minimal; future phases may move debug asserts and
* predicate set checks here for both if/loop PHI normalization.
*/
/// Placeholder for future shared PHI input type alias.
/// Using the same tuple form as MIR Phi instruction inputs.
pub type PhiInput = (crate::mir::BasicBlockId, crate::mir::ValueId);
#[cfg(debug_assertions)]
pub fn debug_verify_phi_inputs(
function: &crate::mir::MirFunction,
merge_bb: crate::mir::BasicBlockId,
inputs: &[(crate::mir::BasicBlockId, crate::mir::ValueId)],
) {
use std::collections::HashSet;
let mut seen = HashSet::new();
for (pred, _v) in inputs.iter() {
debug_assert_ne!(
*pred, merge_bb,
"PHI incoming predecessor must not be the merge block itself"
);
debug_assert!(
seen.insert(*pred),
"Duplicate PHI incoming predecessor detected: {:?}",
pred
);
}
if let Some(block) = function.blocks.get(&merge_bb) {
for (pred, _v) in inputs.iter() {
debug_assert!(
block.predecessors.contains(pred),
"PHI incoming pred {:?} is not a predecessor of merge bb {:?}",
pred,
merge_bb
);
}
}
}
#[cfg(not(debug_assertions))]
pub fn debug_verify_phi_inputs(
_function: &crate::mir::MirFunction,
_merge_bb: crate::mir::BasicBlockId,
_inputs: &[(crate::mir::BasicBlockId, crate::mir::ValueId)],
) {
}

222
src/mir/phi_core/if_phi.rs Normal file
View File

@ -0,0 +1,222 @@
/*!
* phi_core::if_phi if/else PHI helpers (Phase 2)
*
* Public thin wrappers that mirror the semantics of existing builder::phi
* helpers. Implemented locally to avoid depending on private submodules.
* Behavior is identical to the current in-tree logic.
*/
use crate::ast::ASTNode;
use crate::mir::{MirFunction, MirInstruction, MirType, ValueId};
use std::collections::HashMap;
/// Infer return type by scanning for a Phi that defines `ret_val` and
/// verifying that all incoming values have the same type in `types`.
pub fn infer_type_from_phi(
function: &MirFunction,
ret_val: ValueId,
types: &HashMap<ValueId, MirType>,
) -> Option<MirType> {
for (_bid, bb) in function.blocks.iter() {
for inst in bb.instructions.iter() {
if let MirInstruction::Phi { dst, inputs } = inst {
if *dst == ret_val {
let mut it = inputs.iter().filter_map(|(_, v)| types.get(v));
if let Some(first) = it.next() {
if it.all(|mt| mt == first) {
return Some(first.clone());
}
}
}
}
}
}
None
}
/// Extract the assigned variable name from an AST fragment commonly used in
/// if/else analysis. Same logic as builder::phi::extract_assigned_var.
pub fn extract_assigned_var(ast: &ASTNode) -> Option<String> {
match ast {
ASTNode::Assignment { target, .. } => {
if let ASTNode::Variable { name, .. } = target.as_ref() {
Some(name.clone())
} else {
None
}
}
ASTNode::Program { statements, .. } => {
statements.last().and_then(|st| extract_assigned_var(st))
}
ASTNode::If { then_body, else_body, .. } => {
let then_prog = ASTNode::Program {
statements: then_body.clone(),
span: crate::ast::Span::unknown(),
};
let tvar = extract_assigned_var(&then_prog);
let evar = else_body.as_ref().and_then(|eb| {
let ep = ASTNode::Program {
statements: eb.clone(),
span: crate::ast::Span::unknown(),
};
extract_assigned_var(&ep)
});
match (tvar, evar) {
(Some(tv), Some(ev)) if tv == ev => Some(tv),
_ => None,
}
}
_ => None,
}
}
/// Collect all variable names that are assigned within the given AST subtree.
/// Useful for computing PHI merge candidates across branches/blocks.
pub fn collect_assigned_vars(ast: &ASTNode, out: &mut std::collections::HashSet<String>) {
match ast {
ASTNode::Assignment { target, .. } => {
if let ASTNode::Variable { name, .. } = target.as_ref() {
out.insert(name.clone());
}
}
ASTNode::Program { statements, .. } => {
for s in statements { collect_assigned_vars(s, out); }
}
ASTNode::If { then_body, else_body, .. } => {
let tp = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() };
collect_assigned_vars(&tp, out);
if let Some(eb) = else_body {
let ep = ASTNode::Program { statements: eb.clone(), span: crate::ast::Span::unknown() };
collect_assigned_vars(&ep, out);
}
}
_ => {}
}
}
/// Compute the set of variable names whose values changed in either branch
/// relative to the pre-if snapshot.
pub fn compute_modified_names(
pre_if_snapshot: &HashMap<String, ValueId>,
then_map_end: &HashMap<String, ValueId>,
else_map_end_opt: &Option<HashMap<String, ValueId>>,
) -> Vec<String> {
use std::collections::HashSet;
let mut names: HashSet<&str> = HashSet::new();
for k in then_map_end.keys() { names.insert(k.as_str()); }
if let Some(emap) = else_map_end_opt.as_ref() {
for k in emap.keys() { names.insert(k.as_str()); }
}
let mut changed: Vec<String> = Vec::new();
for &name in &names {
let pre = pre_if_snapshot.get(name);
let t = then_map_end.get(name);
let e = else_map_end_opt.as_ref().and_then(|m| m.get(name));
if (t.is_some() && Some(*t.unwrap()) != pre.copied())
|| (e.is_some() && Some(*e.unwrap()) != pre.copied())
{
changed.push(name.to_string());
}
}
changed
}
/// Operations required for emitting a PHI or direct binding at a merge point.
pub trait PhiMergeOps {
fn new_value(&mut self) -> ValueId;
fn emit_phi_at_block_start(
&mut self,
block: crate::mir::BasicBlockId,
dst: ValueId,
inputs: Vec<(crate::mir::BasicBlockId, ValueId)>,
) -> Result<(), String>;
fn update_var(&mut self, name: String, value: ValueId);
fn debug_verify_phi_inputs(
&mut self,
_merge_bb: crate::mir::BasicBlockId,
_inputs: &[(crate::mir::BasicBlockId, ValueId)],
) {
}
}
/// Merge variables modified in branches at the merge block using provided ops.
/// Handles both two-pred and single-pred (reachable) cases gracefully.
pub fn merge_modified_at_merge_with<O: PhiMergeOps>(
ops: &mut O,
merge_bb: crate::mir::BasicBlockId,
_then_block: crate::mir::BasicBlockId,
else_block: crate::mir::BasicBlockId,
then_pred_opt: Option<crate::mir::BasicBlockId>,
else_pred_opt: Option<crate::mir::BasicBlockId>,
pre_if_snapshot: &HashMap<String, ValueId>,
then_map_end: &HashMap<String, ValueId>,
else_map_end_opt: &Option<HashMap<String, ValueId>>,
skip_var: Option<&str>,
) -> Result<(), String> {
let changed = compute_modified_names(pre_if_snapshot, then_map_end, else_map_end_opt);
for name in changed {
if skip_var.map(|s| s == name).unwrap_or(false) {
continue;
}
let pre = match pre_if_snapshot.get(name.as_str()) {
Some(v) => *v,
None => continue,
};
let then_v = then_map_end.get(name.as_str()).copied().unwrap_or(pre);
let else_v = else_map_end_opt
.as_ref()
.and_then(|m| m.get(name.as_str()).copied())
.unwrap_or(pre);
// Build incoming pairs from reachable predecessors only
let mut inputs: Vec<(crate::mir::BasicBlockId, ValueId)> = Vec::new();
if let Some(tp) = then_pred_opt { inputs.push((tp, then_v)); }
if let Some(ep) = else_pred_opt.or(Some(else_block)) { inputs.push((ep, else_v)); }
match inputs.len() {
0 => {}
1 => {
let (_pred, v) = inputs[0];
ops.update_var(name, v);
}
_ => {
ops.debug_verify_phi_inputs(merge_bb, &inputs);
let dst = ops.new_value();
ops.emit_phi_at_block_start(merge_bb, dst, inputs)?;
ops.update_var(name, dst);
}
}
}
Ok(())
}
/// Convenience wrapper: reset variable map (via a caller-provided closure)
/// then perform merge at the merge block. Keeps caller simple while
/// avoiding tying phi_core to concrete builder internals.
pub fn merge_with_reset_at_merge_with<O: PhiMergeOps>(
ops: &mut O,
merge_bb: crate::mir::BasicBlockId,
then_block: crate::mir::BasicBlockId,
else_block: crate::mir::BasicBlockId,
then_pred_opt: Option<crate::mir::BasicBlockId>,
else_pred_opt: Option<crate::mir::BasicBlockId>,
pre_if_snapshot: &HashMap<String, ValueId>,
then_map_end: &HashMap<String, ValueId>,
else_map_end_opt: &Option<HashMap<String, ValueId>>,
reset_vars: impl FnOnce(),
skip_var: Option<&str>,
) -> Result<(), String> {
reset_vars();
merge_modified_at_merge_with(
ops,
merge_bb,
then_block,
else_block,
then_pred_opt,
else_pred_opt,
pre_if_snapshot,
then_map_end,
else_map_end_opt,
skip_var,
)
}

View File

@ -0,0 +1,181 @@
/*!
* phi_core::loop_phi loop-specific PHI management (scaffold)
*
* Phase 1 defines minimal types only. The concrete logic remains in
* `mir::loop_builder` and will be delegated/moved here in later phases.
*/
use crate::mir::{BasicBlockId, ValueId};
use crate::ast::ASTNode;
/// Loop-local placeholder of an incomplete PHI (header-time declaration).
/// Moved from loop_builder to centralize PHI-related types.
#[derive(Debug, Clone)]
pub struct IncompletePhi {
pub phi_id: ValueId,
pub var_name: String,
pub known_inputs: Vec<(BasicBlockId, ValueId)>,
}
/// Common snapshot type used for continue/exit points
pub type VarSnapshot = std::collections::HashMap<String, ValueId>;
pub type SnapshotAt = (BasicBlockId, VarSnapshot);
#[derive(Default)]
pub struct LoopPhiManager;
impl LoopPhiManager {
pub fn new() -> Self { Self::default() }
}
/// Operations required from a loop builder to finalize PHIs.
pub trait LoopPhiOps {
fn new_value(&mut self) -> ValueId;
fn emit_phi_at_block_start(
&mut self,
block: BasicBlockId,
dst: ValueId,
inputs: Vec<(BasicBlockId, ValueId)>,
) -> Result<(), String>;
fn update_var(&mut self, name: String, value: ValueId);
fn get_variable_at_block(&mut self, name: &str, block: BasicBlockId) -> Option<ValueId>;
fn debug_verify_phi_inputs(&mut self, _merge_bb: BasicBlockId, _inputs: &[(BasicBlockId, ValueId)]) {}
}
/// Finalize PHIs at loop exit (merge of break points and header fall-through).
/// Behavior mirrors loop_builder's create_exit_phis using the provided ops.
pub fn build_exit_phis_with<O: LoopPhiOps>(
ops: &mut O,
header_id: BasicBlockId,
exit_id: BasicBlockId,
header_vars: &std::collections::HashMap<String, ValueId>,
exit_snapshots: &[(BasicBlockId, VarSnapshot)],
) -> Result<(), String> {
// 1) Collect all variable names possibly participating in exit PHIs
let mut all_vars = std::collections::HashSet::new();
for var_name in header_vars.keys() {
all_vars.insert(var_name.clone());
}
for (_bid, snapshot) in exit_snapshots.iter() {
for var_name in snapshot.keys() {
all_vars.insert(var_name.clone());
}
}
// 2) For each variable, gather incoming values
for var_name in all_vars {
let mut phi_inputs: Vec<(BasicBlockId, ValueId)> = Vec::new();
if let Some(&hv) = header_vars.get(&var_name) {
phi_inputs.push((header_id, hv));
}
for (block_id, snapshot) in exit_snapshots.iter() {
if let Some(&v) = snapshot.get(&var_name) {
phi_inputs.push((*block_id, v));
}
}
match phi_inputs.len() {
0 => {} // nothing to do
1 => {
// single predecessor: direct binding
ops.update_var(var_name, phi_inputs[0].1);
}
_ => {
let dst = ops.new_value();
ops.debug_verify_phi_inputs(exit_id, &phi_inputs);
ops.emit_phi_at_block_start(exit_id, dst, phi_inputs)?;
ops.update_var(var_name, dst);
}
}
}
Ok(())
}
/// Seal a header block by completing its incomplete PHIs with values from
/// continue snapshots and the latch block.
pub fn seal_incomplete_phis_with<O: LoopPhiOps>(
ops: &mut O,
block_id: BasicBlockId,
latch_id: BasicBlockId,
mut incomplete_phis: Vec<IncompletePhi>,
continue_snapshots: &[(BasicBlockId, VarSnapshot)],
) -> Result<(), String> {
for mut phi in incomplete_phis.drain(..) {
// from continue points
for (cid, snapshot) in continue_snapshots.iter() {
if let Some(&v) = snapshot.get(&phi.var_name) {
phi.known_inputs.push((*cid, v));
}
}
// from latch
let value_after = ops
.get_variable_at_block(&phi.var_name, latch_id)
.ok_or_else(|| format!("Variable {} not found at latch block", phi.var_name))?;
phi.known_inputs.push((latch_id, value_after));
ops.debug_verify_phi_inputs(block_id, &phi.known_inputs);
ops.emit_phi_at_block_start(block_id, phi.phi_id, phi.known_inputs)?;
ops.update_var(phi.var_name.clone(), phi.phi_id);
}
Ok(())
}
/// Prepare loop header PHIs by declaring one IncompletePhi per variable found
/// in `current_vars` (preheader snapshot), seeding each with (preheader_id, val)
/// and rebinding the variable to the newly allocated Phi result in the builder.
pub fn prepare_loop_variables_with<O: LoopPhiOps>(
ops: &mut O,
_header_id: BasicBlockId,
preheader_id: BasicBlockId,
current_vars: &std::collections::HashMap<String, ValueId>,
) -> Result<Vec<IncompletePhi>, String> {
let mut incomplete_phis: Vec<IncompletePhi> = Vec::new();
for (var_name, &value_before) in current_vars.iter() {
let phi_id = ops.new_value();
let inc = IncompletePhi {
phi_id,
var_name: var_name.clone(),
known_inputs: vec![(preheader_id, value_before)],
};
incomplete_phis.push(inc);
ops.update_var(var_name.clone(), phi_id);
}
Ok(incomplete_phis)
}
/// Collect variables assigned within a loop body and detect control-flow
/// statements (break/continue). Used for lightweight carrier hinting.
pub fn collect_carrier_assigns(node: &ASTNode, vars: &mut Vec<String>, has_ctrl: &mut bool) {
match node {
ASTNode::Assignment { target, .. } => {
if let ASTNode::Variable { name, .. } = target.as_ref() {
if !vars.iter().any(|v| v == name) {
vars.push(name.clone());
}
}
}
ASTNode::Break { .. } | ASTNode::Continue { .. } => { *has_ctrl = true; }
ASTNode::If { then_body, else_body, .. } => {
let tp = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() };
collect_carrier_assigns(&tp, vars, has_ctrl);
if let Some(eb) = else_body {
let ep = ASTNode::Program { statements: eb.clone(), span: crate::ast::Span::unknown() };
collect_carrier_assigns(&ep, vars, has_ctrl);
}
}
ASTNode::Program { statements, .. } => {
for s in statements { collect_carrier_assigns(s, vars, has_ctrl); }
}
_ => {}
}
}
/// Save a block-local variable snapshot into the provided store.
pub fn save_block_snapshot(
store: &mut std::collections::HashMap<BasicBlockId, VarSnapshot>,
block: BasicBlockId,
snapshot: &VarSnapshot,
) {
store.insert(block, snapshot.clone());
}

17
src/mir/phi_core/mod.rs Normal file
View File

@ -0,0 +1,17 @@
/*!
* phi_core Unified PHI management scaffold (Phase 1)
*
* Purpose:
* - Provide a single, stable entry point for PHI-related helpers.
* - Start with re-exports of existing, verified logic (zero behavior change).
* - Prepare ground for gradual consolidation of loop PHI handling.
*/
pub mod common;
pub mod if_phi;
pub mod loop_phi;
// Public surface for callers that want a stable path:
// Phase 1: No re-exports to avoid touching private builder internals.
// Callers should continue using existing paths. Future phases may expose
// stable wrappers here once migrated.

View File

@ -0,0 +1,723 @@
/*!
* Nyash Parser - Statement Parsing Module
*
*Statementの解析を担当するモジュール
* if, loop, break, return, print等の制御構文を処理
*/
use super::common::ParserUtils;
use super::cursor::TokenCursor;
use super::{NyashParser, ParseError};
use crate::ast::{ASTNode, CatchClause, Span};
use crate::tokenizer::TokenType;
impl NyashParser {
#[inline]
fn cursor_enabled() -> bool {
std::env::var("NYASH_PARSER_TOKEN_CURSOR").ok().as_deref() == Some("1")
}
/// Thin adapter: when Cursor route is enabled, align statement start position
/// by letting TokenCursor apply its statement-mode newline policy, then
/// continue with the legacy statement parser. No behavior change otherwise.
#[inline]
fn with_stmt_cursor<F>(&mut self, f: F) -> Result<ASTNode, ParseError>
where
F: FnOnce(&mut Self) -> Result<ASTNode, ParseError>,
{
if Self::cursor_enabled() {
let mut cursor = TokenCursor::new(&self.tokens);
cursor.set_position(self.current);
cursor.with_stmt_mode(|c| {
// Allow cursor to collapse any leading NEWLINEs in stmt mode
c.skip_newlines();
});
self.current = cursor.position();
}
f(self)
}
/// Map a starting token into a grammar keyword string used by GRAMMAR_DIFF tracing.
#[inline]
fn grammar_keyword_for(start: &TokenType) -> Option<&'static str> {
match start {
TokenType::BOX => Some("box"),
TokenType::GLOBAL => Some("global"),
TokenType::FUNCTION => Some("function"),
TokenType::STATIC => Some("static"),
TokenType::IF => Some("if"),
TokenType::LOOP => Some("loop"),
TokenType::BREAK => Some("break"),
TokenType::RETURN => Some("return"),
TokenType::PRINT => Some("print"),
TokenType::NOWAIT => Some("nowait"),
TokenType::LOCAL => Some("local"),
TokenType::OUTBOX => Some("outbox"),
TokenType::TRY => Some("try"),
TokenType::THROW => Some("throw"),
TokenType::USING => Some("using"),
TokenType::FROM => Some("from"),
_ => None,
}
}
/// Small helper: build UnexpectedToken with current token and line.
#[inline]
fn err_unexpected<S: Into<String>>(&self, expected: S) -> ParseError {
ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: expected.into(),
line: self.current_token().line,
}
}
/// Expect an identifier and advance. Returns its string or an UnexpectedToken error.
#[inline]
fn expect_identifier(&mut self, what: &str) -> Result<String, ParseError> {
if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
let out = name.clone();
self.advance();
Ok(out)
} else {
Err(self.err_unexpected(what))
}
}
/// Parse a standalone block `{ ... }` and optional postfix `catch/cleanup` sequence.
/// Returns Program(body) when no postfix keywords follow.
fn parse_standalone_block_statement(&mut self) -> Result<ASTNode, ParseError> {
// Parse the block body first
let try_body = self.parse_block_statements()?;
if crate::config::env::block_postfix_catch()
&& (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP))
{
// Parse at most one catch, then optional cleanup
let mut catch_clauses: Vec<CatchClause> = Vec::new();
if self.match_token(&TokenType::CATCH) {
self.advance(); // consume 'catch'
self.consume(TokenType::LPAREN)?;
let (exception_type, exception_var) = self.parse_catch_param()?;
self.consume(TokenType::RPAREN)?;
let catch_body = self.parse_block_statements()?;
catch_clauses.push(CatchClause {
exception_type,
variable_name: exception_var,
body: catch_body,
span: Span::unknown(),
});
// Singlecatch policy (MVP): disallow multiple catch in postfix form
if self.match_token(&TokenType::CATCH) {
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "single catch only after standalone block".to_string(),
line,
});
}
}
// Optional cleanup
let finally_body = if self.match_token(&TokenType::CLEANUP) {
self.advance(); // consume 'cleanup'
Some(self.parse_block_statements()?)
} else {
None
};
Ok(ASTNode::TryCatch {
try_body,
catch_clauses,
finally_body,
span: Span::unknown(),
})
} else {
// No postfix keywords. If gate is on, enforce MVP static check:
// direct top-level `throw` inside the standalone block must be followed by catch
if crate::config::env::block_postfix_catch()
&& try_body.iter().any(|n| matches!(n, ASTNode::Throw { .. }))
{
let line = self.current_token().line;
return Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "block with direct 'throw' must be followed by 'catch'".to_string(),
line,
});
}
Ok(ASTNode::Program {
statements: try_body,
span: Span::unknown(),
})
}
}
/// Helper: parse a block `{ stmt* }` and return its statements
pub(super) fn parse_block_statements(&mut self) -> Result<Vec<ASTNode>, ParseError> {
self.consume(TokenType::LBRACE)?;
let mut body = Vec::new();
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
if !self.match_token(&TokenType::RBRACE) {
body.push(self.parse_statement()?);
}
}
self.consume(TokenType::RBRACE)?;
Ok(body)
}
/// Grouped: declarations (box/interface/global/function/static/import)
fn parse_declaration_statement(&mut self) -> Result<ASTNode, ParseError> {
match &self.current_token().token_type {
TokenType::BOX => self.parse_box_declaration(),
TokenType::IMPORT => self.parse_import(),
TokenType::INTERFACE => self.parse_interface_box_declaration(),
TokenType::GLOBAL => self.parse_global_var(),
TokenType::FUNCTION => self.parse_function_declaration(),
TokenType::STATIC => self.parse_static_declaration(),
_ => Err(self.err_unexpected("declaration statement")),
}
}
/// Grouped: control flow (if/loop/break/continue/return)
fn parse_control_flow_statement(&mut self) -> Result<ASTNode, ParseError> {
match &self.current_token().token_type {
TokenType::IF => self.parse_if(),
TokenType::LOOP => self.parse_loop(),
TokenType::BREAK => self.parse_break(),
TokenType::CONTINUE => self.parse_continue(),
TokenType::RETURN => self.parse_return(),
_ => Err(self.err_unexpected("control-flow statement")),
}
}
/// Grouped: IO/module-ish (print/nowait)
fn parse_io_module_statement(&mut self) -> Result<ASTNode, ParseError> {
match &self.current_token().token_type {
TokenType::PRINT => self.parse_print(),
TokenType::NOWAIT => self.parse_nowait(),
_ => Err(self.err_unexpected("io/module statement")),
}
}
/// Grouped: variable-related (local/outbox)
fn parse_variable_declaration_statement(&mut self) -> Result<ASTNode, ParseError> {
match &self.current_token().token_type {
TokenType::LOCAL => self.parse_local(),
TokenType::OUTBOX => self.parse_outbox(),
_ => Err(self.err_unexpected("variable declaration")),
}
}
/// Grouped: exception (try/throw) with gate checks preserved
fn parse_exception_statement(&mut self) -> Result<ASTNode, ParseError> {
match &self.current_token().token_type {
TokenType::TRY => {
if crate::config::env::parser_stage3() {
self.parse_try_catch()
} else {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "enable NYASH_PARSER_STAGE3=1 to use 'try'".to_string(),
line: self.current_token().line,
})
}
}
TokenType::THROW => {
if crate::config::env::parser_stage3() {
self.parse_throw()
} else {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "enable NYASH_PARSER_STAGE3=1 to use 'throw'".to_string(),
line: self.current_token().line,
})
}
}
_ => Err(self.err_unexpected("try/throw")),
}
}
/// Error helpers for standalone postfix keywords (catch/cleanup)
fn parse_postfix_catch_cleanup_error(&mut self) -> Result<ASTNode, ParseError> {
match &self.current_token().token_type {
TokenType::CATCH => {
if crate::config::env::block_postfix_catch() {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "postfix 'catch' is only allowed immediately after a standalone block: { ... } catch (...) { ... } (wrap if/else/loop in a standalone block)".to_string(),
line: self.current_token().line,
})
} else {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "enable NYASH_BLOCK_CATCH=1 (or NYASH_PARSER_STAGE3=1) to use postfix 'catch' after a standalone block".to_string(),
line: self.current_token().line,
})
}
}
TokenType::CLEANUP => {
if crate::config::env::block_postfix_catch() {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "postfix 'cleanup' is only allowed immediately after a standalone block: { ... } cleanup { ... }".to_string(),
line: self.current_token().line,
})
} else {
Err(ParseError::UnexpectedToken {
found: self.current_token().token_type.clone(),
expected: "enable NYASH_BLOCK_CATCH=1 (or NYASH_PARSER_STAGE3=1) to use postfix 'cleanup' after a standalone block".to_string(),
line: self.current_token().line,
})
}
}
_ => unreachable!(),
}
}
/// Helper: parse catch parameter inside parentheses (after '(' consumed)
/// Forms: (Type ident) | (ident) | ()
pub(super) fn parse_catch_param(&mut self) -> Result<(Option<String>, Option<String>), ParseError> {
if self.match_token(&TokenType::RPAREN) {
return Ok((None, None));
}
match &self.current_token().token_type {
TokenType::IDENTIFIER(first) => {
let first_str = first.clone();
let two_idents = matches!(self.peek_token(), TokenType::IDENTIFIER(_));
if two_idents {
self.advance(); // consume type ident
if let TokenType::IDENTIFIER(var_name) = &self.current_token().token_type {
let var = var_name.clone();
self.advance();
Ok((Some(first_str), Some(var)))
} else {
Err(self.err_unexpected("exception variable name"))
}
} else {
self.advance();
Ok((None, Some(first_str)))
}
}
_ => {
if self.match_token(&TokenType::RPAREN) {
Ok((None, None))
} else {
Err(self.err_unexpected(") or identifier"))
}
}
}
}
/// 文をパース
pub(super) fn parse_statement(&mut self) -> Result<ASTNode, ParseError> {
// For grammar diff: capture starting token to classify statement keyword
let start_tok = self.current_token().token_type.clone();
let result = match &start_tok {
TokenType::LBRACE => self.parse_standalone_block_statement(),
TokenType::BOX
| TokenType::IMPORT
| TokenType::INTERFACE
| TokenType::GLOBAL
| TokenType::FUNCTION
| TokenType::STATIC => self.parse_declaration_statement(),
TokenType::IF
| TokenType::LOOP
| TokenType::BREAK
| TokenType::CONTINUE
| TokenType::RETURN => self.parse_control_flow_statement(),
TokenType::PRINT | TokenType::NOWAIT => self.parse_io_module_statement(),
TokenType::LOCAL | TokenType::OUTBOX => self.parse_variable_declaration_statement(),
TokenType::TRY | TokenType::THROW => self.parse_exception_statement(),
TokenType::CATCH | TokenType::CLEANUP => self.parse_postfix_catch_cleanup_error(),
TokenType::USING => self.parse_using(),
TokenType::FROM => {
// 🔥 from構文: from Parent.method(args) または from Parent.constructor(args)
self.parse_from_call_statement()
}
TokenType::IDENTIFIER(_name) => {
// function宣言 または 代入文 または 関数呼び出し
self.parse_assignment_or_function_call()
}
TokenType::THIS | TokenType::ME => {
// this/me で始まる文も通常の代入文または関数呼び出しとして処理
self.parse_assignment_or_function_call()
}
_ => {
// Fallback: treat as expression statement
// Allows forms like: print("x") or a bare literal as the last value in a block
// Thin-adapt with Cursor in stmt mode (env on) to normalize leading newlines.
self.with_stmt_cursor(|p| Ok(p.parse_expression()?))
}
};
// Non-invasive syntax rule check
if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") {
if let Some(k) = Self::grammar_keyword_for(&start_tok) {
let ok = crate::grammar::engine::get().syntax_is_allowed_statement(k);
if !ok {
eprintln!(
"[GRAMMAR-DIFF][Parser] statement '{}' not allowed by syntax rules",
k
);
}
}
}
result
}
/// import文をパース: import "path" (as Alias)?
pub(super) fn parse_import(&mut self) -> Result<ASTNode, ParseError> {
self.advance(); // consume 'import'
let path = if let TokenType::STRING(s) = &self.current_token().token_type {
let v = s.clone();
self.advance();
v
} else {
return Err(self.err_unexpected("string literal"));
};
// Optional: 'as' Alias (treat 'as' as identifier literal)
let mut alias: Option<String> = None;
if let TokenType::IDENTIFIER(w) = &self.current_token().token_type {
if w == "as" {
self.advance();
if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
alias = Some(name.clone());
self.advance();
} else {
return Err(self.err_unexpected("alias name"));
}
}
}
Ok(ASTNode::ImportStatement {
path,
alias,
span: Span::unknown(),
})
}
/// if文をパース: if (condition) { body } else if ... else { body }
pub(super) fn parse_if(&mut self) -> Result<ASTNode, ParseError> {
// Thin-adapt statement start when Cursor route is enabled
if Self::cursor_enabled() {
let mut cursor = TokenCursor::new(&self.tokens);
cursor.set_position(self.current);
cursor.with_stmt_mode(|c| c.skip_newlines());
self.current = cursor.position();
}
self.advance(); // consume 'if'
// 条件部分を取得
let condition = Box::new(self.parse_expression()?);
// then部分を取得共通ブロックヘルパー
let then_body = self.parse_block_statements()?;
// else if/else部分を処理
let else_body = if self.match_token(&TokenType::ELSE) {
self.advance(); // consume 'else'
if self.match_token(&TokenType::IF) {
// else if を ネストしたifとして処理
let nested_if = self.parse_if()?;
Some(vec![nested_if])
} else {
// plain else共通ブロックヘルパー
Some(self.parse_block_statements()?)
}
} else {
None
};
Ok(ASTNode::If {
condition,
then_body,
else_body,
span: Span::unknown(),
})
}
/// loop文をパース
pub(super) fn parse_loop(&mut self) -> Result<ASTNode, ParseError> {
if Self::cursor_enabled() {
let mut cursor = TokenCursor::new(&self.tokens);
cursor.set_position(self.current);
cursor.with_stmt_mode(|c| c.skip_newlines());
self.current = cursor.position();
}
self.advance(); // consume 'loop'
// 条件部分を取得(省略可: `loop { ... }` は無条件ループとして扱う)
let condition = if self.match_token(&TokenType::LPAREN) {
self.advance(); // consume '('
let cond = Box::new(self.parse_expression()?);
self.consume(TokenType::RPAREN)?;
cond
} else {
// default: true
Box::new(ASTNode::Literal {
value: crate::ast::LiteralValue::Bool(true),
span: Span::unknown(),
})
};
// body部分を取得共通ブロックヘルパー
let body = self.parse_block_statements()?;
Ok(ASTNode::Loop {
condition,
body,
span: Span::unknown(),
})
}
/// break文をパース
pub(super) fn parse_break(&mut self) -> Result<ASTNode, ParseError> {
self.advance(); // consume 'break'
Ok(ASTNode::Break {
span: Span::unknown(),
})
}
/// continue文をパース
pub(super) fn parse_continue(&mut self) -> Result<ASTNode, ParseError> {
self.advance(); // consume 'continue'
Ok(ASTNode::Continue {
span: Span::unknown(),
})
}
/// return文をパース
pub(super) fn parse_return(&mut self) -> Result<ASTNode, ParseError> {
if Self::cursor_enabled() {
let mut cursor = TokenCursor::new(&self.tokens);
cursor.set_position(self.current);
cursor.with_stmt_mode(|c| c.skip_newlines());
self.current = cursor.position();
}
self.advance(); // consume 'return'
// returnの後に式があるかチェックRBRACE/EOFなら値なし
let value = if self.is_at_end() || self.match_token(&TokenType::RBRACE) {
None
} else {
Some(Box::new(self.parse_expression()?))
};
Ok(ASTNode::Return {
value,
span: Span::unknown(),
})
}
/// print文をパース
pub(super) fn parse_print(&mut self) -> Result<ASTNode, ParseError> {
if Self::cursor_enabled() {
let mut cursor = TokenCursor::new(&self.tokens);
cursor.set_position(self.current);
cursor.with_stmt_mode(|c| c.skip_newlines());
self.current = cursor.position();
}
self.advance(); // consume 'print'
self.consume(TokenType::LPAREN)?;
let value = Box::new(self.parse_expression()?);
self.consume(TokenType::RPAREN)?;
Ok(ASTNode::Print {
expression: value,
span: Span::unknown(),
})
}
/// nowait文をパース: nowait variable = expression
pub(super) fn parse_nowait(&mut self) -> Result<ASTNode, ParseError> {
self.advance(); // consume 'nowait'
// 変数名を取得
let variable = self.expect_identifier("variable name")?;
self.consume(TokenType::ASSIGN)?;
let expression = Box::new(self.parse_expression()?);
Ok(ASTNode::Nowait {
variable,
expression,
span: Span::unknown(),
})
}
// include文は廃止usingを使用
/// local変数宣言をパース: local var1, var2, var3 または local x = 10
pub(super) fn parse_local(&mut self) -> Result<ASTNode, ParseError> {
if Self::cursor_enabled() {
let mut cursor = TokenCursor::new(&self.tokens);
cursor.set_position(self.current);
cursor.with_stmt_mode(|c| c.skip_newlines());
self.current = cursor.position();
}
self.advance(); // consume 'local'
let mut names = Vec::new();
let mut initial_values = Vec::new();
// 最初の変数名を取得
if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
names.push(name.clone());
self.advance();
// = があれば初期値を設定
if self.match_token(&TokenType::ASSIGN) {
self.advance(); // consume '='
initial_values.push(Some(Box::new(self.parse_expression()?)));
// 初期化付きlocalは単一変数のみカンマ区切り不可
Ok(ASTNode::Local {
variables: names,
initial_values,
span: Span::unknown(),
})
} else {
// 初期化なしの場合はカンマ区切りで複数変数可能
initial_values.push(None);
// カンマ区切りで追加の変数名を取得
while self.match_token(&TokenType::COMMA) {
self.advance(); // consume ','
if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
names.push(name.clone());
initial_values.push(None);
self.advance();
} else {
return Err(self.err_unexpected("identifier"));
}
}
Ok(ASTNode::Local {
variables: names,
initial_values,
span: Span::unknown(),
})
}
} else {
Err(self.err_unexpected("identifier"))
}
}
/// outbox変数宣言をパース: outbox var1, var2, var3
pub(super) fn parse_outbox(&mut self) -> Result<ASTNode, ParseError> {
self.advance(); // consume 'outbox'
let mut names = Vec::new();
// 最初の変数名を取得
if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
names.push(name.clone());
self.advance();
// カンマ区切りで追加の変数名を取得
while self.match_token(&TokenType::COMMA) {
self.advance(); // consume ','
if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
names.push(name.clone());
self.advance();
} else {
return Err(self.err_unexpected("identifier"));
}
}
let num_vars = names.len();
Ok(ASTNode::Outbox {
variables: names,
initial_values: vec![None; num_vars],
span: Span::unknown(),
})
} else {
Err(self.err_unexpected("identifier"))
}
}
/// try-catch文をパース
pub(super) fn parse_try_catch(&mut self) -> Result<ASTNode, ParseError> {
self.advance(); // consume 'try'
let try_body = self.parse_block_statements()?;
let mut catch_clauses = Vec::new();
// catch節をパース
while self.match_token(&TokenType::CATCH) {
self.advance(); // consume 'catch'
self.consume(TokenType::LPAREN)?;
let (exception_type, exception_var) = self.parse_catch_param()?;
self.consume(TokenType::RPAREN)?;
let catch_body = self.parse_block_statements()?;
catch_clauses.push(CatchClause {
exception_type,
variable_name: exception_var,
body: catch_body,
span: Span::unknown(),
});
}
// cleanup節をパース (オプション)
let finally_body = if self.match_token(&TokenType::CLEANUP) {
self.advance(); // consume 'cleanup'
Some(self.parse_block_statements()?)
} else {
None
};
Ok(ASTNode::TryCatch {
try_body,
catch_clauses,
finally_body,
span: Span::unknown(),
})
}
/// throw文をパース
pub(super) fn parse_throw(&mut self) -> Result<ASTNode, ParseError> {
self.advance(); // consume 'throw'
let value = Box::new(self.parse_expression()?);
Ok(ASTNode::Throw {
expression: value,
span: Span::unknown(),
})
}
/// 🔥 from構文を文としてパース: from Parent.method(args)
pub(super) fn parse_from_call_statement(&mut self) -> Result<ASTNode, ParseError> {
// 既存のparse_from_call()を使用してFromCall ASTードを作成
let from_call_expr = self.parse_from_call()?;
// FromCallは式でもあるが、文としても使用可能
// 例: from Animal.constructor() (戻り値を使わない)
Ok(from_call_expr)
}
/// using文をパース: using namespace_name
pub(super) fn parse_using(&mut self) -> Result<ASTNode, ParseError> {
self.advance(); // consume 'using'
// 名前空間名を取得
if let TokenType::IDENTIFIER(namespace_name) = &self.current_token().token_type {
let name = namespace_name.clone();
self.advance();
// Phase 0では "nyashstd" のみ許可
if name != "nyashstd" {
return Err(ParseError::UnsupportedNamespace {
name,
line: self.current_token().line,
});
}
Ok(ASTNode::UsingStatement {
namespace_name: name,
span: Span::unknown(),
})
} else {
Err(ParseError::ExpectedIdentifier {
line: self.current_token().line,
})
}
}
}

View File

@ -141,7 +141,21 @@ pub fn strip_using_and_register(
// Two forms:
// - using path "..." [as Alias]
// - using namespace.with.dots [as Alias]
let resolved_path = if let Some(alias) = alias_opt {
let resolved_path = if let Some(alias_or_path) = alias_opt {
// Disambiguate: when alias_opt looks like a file path, treat it as direct path.
let is_path_hint = alias_or_path.ends_with(".nyash")
|| alias_or_path.contains('/')
|| alias_or_path.contains('\\');
if is_path_hint {
// Direct path provided (e.g., using "path/file.nyash" as Name)
let value = alias_or_path.clone();
// Register: Name -> path
let sb = crate::box_trait::StringBox::new(value.clone());
crate::runtime::modules_registry::set(ns.clone(), Box::new(sb));
Some(value)
} else {
// alias string for a namespace (e.g., using ns.token as Alias)
let alias = alias_or_path;
// alias case: resolve namespace to a concrete path
let mut found: Option<String> = using_ctx
.pending_modules
@ -199,6 +213,7 @@ pub fn strip_using_and_register(
eprintln!("[using] still unresolved: {} as {}", ns, alias);
}
found
}
} else {
// direct namespace without alias
match crate::runner::pipeline::resolve_using_target(
@ -397,6 +412,9 @@ pub fn strip_using_and_register(
combined.push('\n');
crate::runner::modes::common_util::resolve::seam::fix_prelude_braces_if_enabled(prelude_clean, &mut combined, trace);
combined.push_str(&out);
if std::env::var("NYASH_RESOLVE_SEAM_DEBUG").ok().as_deref() == Some("1") {
let _ = std::fs::write("/tmp/nyash_using_combined.nyash", &combined);
}
Ok(combined)
}