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:
@ -1,11 +1,56 @@
|
|||||||
# Current Task — Phase 15: Nyashセルフホスティング実行器統一化
|
# Current Task — Phase 15 (Revised): Self‑Hosting Focus, JSON→Ny Executor
|
||||||
|
|
||||||
Updated: 2025‑09‑26
|
Updated: 2025‑09‑26
|
||||||
|
|
||||||
Quick execution summary (local)
|
Quick status
|
||||||
- Build: cargo build --release → OK
|
- Build: `cargo build --release` → OK(警告のみ)
|
||||||
- v2 smokes (quick): core set PASS/expected SKIP only
|
- Smokes v2: quick/core PASS、integration/parity PASS(Python LLVM harness)
|
||||||
- v2 smokes (plugins): Fixture dylib autoload PASS(環境に応じて一部 SKIP 設計)
|
- Parser: TokenCursor 統一 Step‑2/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/encoder(Nyash)
|
||||||
|
- 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/phi(parity: PyVM/LLVM harness)
|
||||||
|
3) 呼び出し最小(MVP)
|
||||||
|
- call/externcall/boxcall(Console/String/Array/Map の P0)
|
||||||
|
- 代表スモーク: print/concat/len/has 基本
|
||||||
|
4) 監視期間(数日)→ 旧 depth/skip 残骸の完全削除と警告掃除(任意)
|
||||||
|
|
||||||
|
受け入れゲート
|
||||||
|
- quick/core + integration/parity 緑(env ON/OFF 双方)
|
||||||
|
- 既定挙動を変えない(新経路はすべて env トグルで opt‑in)
|
||||||
|
- 変更は小さくロールバック容易
|
||||||
|
|
||||||
|
主要トグル(統一)
|
||||||
|
- `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`(任意)
|
||||||
|
|
||||||
|
ドキュメント更新(本日)
|
||||||
|
- phase‑15/README.md: 2025‑09‑26 更新ノートを追加(JSON→Self‑Host への舵切り、TokenCursor/PHI/Loop PHI 統合の反映)
|
||||||
|
- phase‑15/ROADMAP.md: Now/Next を刷新(JSON ライブラリを Next1 に昇格、Cranelift 記述は凍結注記)
|
||||||
|
- selfhosting‑ny‑executor.md: Stage‑1 に Ny JSON 依存を明記
|
||||||
|
- README.md: Phase‑15(2025‑09)アップデートのクイックノート追記(Python ハーネス・トグル案内)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
以下は履歴ノート(必要時の参照用)。最新の計画は上記ブロックを正とする。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
Include → Using 移行状況(2025‑09‑26)
|
Include → Using 移行状況(2025‑09‑26)
|
||||||
- コード一式を `using` に統一(apps/examples/selfhost/JSON Native 等)。
|
- コード一式を `using` に統一(apps/examples/selfhost/JSON Native 等)。
|
||||||
@ -56,12 +101,38 @@ Addendum (2025‑09‑26 2nd half)
|
|||||||
- [x] PHI 修正: incoming pred を then/else の exit ブロックに統一(VM 未定義値を根治)
|
- [x] PHI 修正: incoming pred を then/else の exit ブロックに統一(VM 未定義値を根治)
|
||||||
- [x] PHI 検証(dev): 重複 pred/自己参照/CFG preds 含有の debug アサート追加
|
- [x] PHI 検証(dev): 重複 pred/自己参照/CFG preds 含有の debug アサート追加
|
||||||
- [x] テストランナー: 出力ノイズの共通フィルタ化(filter_noise)
|
- [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] Step‑2: primary/postfix/new/unary(−/not/await) を TokenCursor 経路へ寄せる(env トグル配下)
|
- [x] Step‑2: primary/postfix/new/unary(−/not/await) を TokenCursor 経路へ寄せる(env トグル配下)
|
||||||
- [x] Step‑2: parity 代表(優先順位/単項)を追加し VM↔LLVM 整合を確認
|
- [x] Step‑2: parity 代表(優先順位/単項)を追加し VM↔LLVM 整合を確認
|
||||||
- [ ] Step‑3: statements 側の薄いラッパ導入(env トグル時のみ Cursor を用いた if/loop/print/local/return の最小経路)
|
- [x] Step‑3: statements 側の薄いラッパ導入(env トグル時のみ Cursor を用いた if/loop/print/local/return の最小経路)
|
||||||
- [ ] Step‑3: 旧来 skip 系(common.rs/depth_tracking.rs/parser_enhanced.rs)参照ゼロ確認→段階撤去
|
- [x] Step‑3: 旧来 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` に段階委譲(仕様不変)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,13 @@ AST JSON v0(マクロ/ブリッジ): `docs/reference/ir/ast-json-v0.md`
|
|||||||
セルフホスト1枚ガイド: `docs/how-to/self-hosting.md`
|
セルフホスト1枚ガイド: `docs/how-to/self-hosting.md`
|
||||||
ExternCall(env.*)と println 正規化: `docs/reference/runtime/externcall.md`
|
ExternCall(env.*)と println 正規化: `docs/reference/runtime/externcall.md`
|
||||||
|
|
||||||
|
Phase‑15(2025‑09)アップデート
|
||||||
|
- 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`
|
- 必須不変条件(Invariants): `docs/reference/invariants.md`
|
||||||
- 制約(既知/一時/解消済み): `docs/reference/constraints.md`
|
- 制約(既知/一時/解消済み): `docs/reference/constraints.md`
|
||||||
|
|||||||
@ -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/`).
|
- 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`.
|
- Run PyVM: `NYASH_VM_USE_PY=1 ./target/release/nyash --backend vm apps/APP/main.nyash`.
|
||||||
|
|
||||||
|
Phase‑15 (2025‑09) 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`.
|
Developer quickstart: see `docs/DEV_QUICKSTART.md`. Changelog highlights: `CHANGELOG.md`.
|
||||||
User Macros (Phase 2): `docs/guides/user-macros.md`
|
User Macros (Phase 2): `docs/guides/user-macros.md`
|
||||||
Exceptions (postfix catch/cleanup): `docs/guides/exception-handling.md`
|
Exceptions (postfix catch/cleanup): `docs/guides/exception-handling.md`
|
||||||
|
|||||||
108
apps/lib/json_native/core/compat.nyash
Normal file
108
apps/lib/json_native/core/compat.nyash
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -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"
|
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)
|
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() {
|
read_number() {
|
||||||
local start_pos = me.position
|
local start_pos = me.position
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using "apps/lib/json_native/lexer/scanner.nyash" as JsonScanner
|
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/lexer/token.nyash" as JsonToken
|
||||||
|
using "apps/lib/json_native/utils/escape.nyash" as EscapeUtils
|
||||||
// Removed other dependencies - using self-contained methods
|
// Removed other dependencies - using self-contained methods
|
||||||
|
|
||||||
// 🎯 高精度JSONトークナイザー(Everything is Box)
|
// 🎯 高精度JSONトークナイザー(Everything is Box)
|
||||||
@ -109,8 +110,8 @@ box JsonTokenizer {
|
|||||||
return new JsonToken("ERROR", "Unterminated string literal", start_pos, me.scanner.get_position())
|
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) {
|
if not me.validate_string(unescaped) {
|
||||||
@ -141,8 +142,8 @@ box JsonTokenizer {
|
|||||||
tokenize_keyword() {
|
tokenize_keyword() {
|
||||||
local start_pos = me.scanner.get_position()
|
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)
|
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) {
|
validate_number_format(num_str) {
|
||||||
|
|||||||
@ -14,6 +14,8 @@ static box JsonParserModule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
box JsonParser {
|
box JsonParser {
|
||||||
tokens: ArrayBox // トークン配列
|
tokens: ArrayBox // トークン配列
|
||||||
position: IntegerBox // 現在のトークン位置
|
position: IntegerBox // 現在のトークン位置
|
||||||
@ -356,7 +358,8 @@ box JsonParser {
|
|||||||
out.set("value", StringUtils.parse_integer(number_str))
|
out.set("value", StringUtils.parse_integer(number_str))
|
||||||
return out
|
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("kind", "float")
|
||||||
out.set("value", StringUtils.parse_float(number_str))
|
out.set("value", StringUtils.parse_float(number_str))
|
||||||
return out
|
return out
|
||||||
|
|||||||
13
apps/lib/json_native/tests/compat_smoke.nyash
Normal file
13
apps/lib/json_native/tests/compat_smoke.nyash
Normal 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")
|
||||||
@ -237,6 +237,14 @@ static box StringUtils {
|
|||||||
start = 1
|
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
|
local i = start
|
||||||
loop(i < s.length()) {
|
loop(i < s.length()) {
|
||||||
if not this.is_digit(s.substring(i, i + 1)) {
|
if not this.is_digit(s.substring(i, i + 1)) {
|
||||||
@ -253,83 +261,29 @@ static box StringUtils {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// 超簡易実装: よく使われる数値のみ対応
|
// 汎用整数パース(符号と任意桁に対応)
|
||||||
// JSON処理に必要な数値パース実装
|
local neg = false
|
||||||
// 基本数字 0-9
|
local i = 0
|
||||||
if s == "0" { return 0 } if s == "1" { return 1 } if s == "2" { return 2 }
|
if s.substring(0, 1) == "-" {
|
||||||
if s == "3" { return 3 } if s == "4" { return 4 } if s == "5" { return 5 }
|
neg = true
|
||||||
if s == "6" { return 6 } if s == "7" { return 7 } if s == "8" { return 8 } if s == "9" { return 9 }
|
i = 1
|
||||||
|
|
||||||
// よく使われる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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文字列が浮動小数点数表現かどうか(簡易版: 10進/指数部のみ対応)
|
local acc = 0
|
||||||
// 許容: [-+]? 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
|
|
||||||
loop(i < s.length()) {
|
loop(i < s.length()) {
|
||||||
|
// 各桁の数値を計算
|
||||||
local ch = s.substring(i, i + 1)
|
local ch = s.substring(i, i + 1)
|
||||||
if this.is_digit(ch) {
|
// '0'..'9' 前提(is_integer で検証済み)
|
||||||
has_digit = true
|
// 文字を数値へ: (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
|
i = i + 1
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if ch == "." {
|
|
||||||
if has_dot or has_exp { return false }
|
if neg { return 0 - acc } else { return acc }
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 浮動小数点の簡易パース(現段階は正規化のみ。数値演算は行わない)
|
// 浮動小数点の簡易パース(現段階は正規化のみ。数値演算は行わない)
|
||||||
@ -360,3 +314,4 @@ static box StringUtils {
|
|||||||
return s.substring(s.length() - suffix.length(), s.length()) == suffix
|
return s.substring(s.length() - suffix.length(), s.length()) == suffix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -6,6 +6,20 @@ NyashでNyashコンパイラを書く、完全なセルフホスティングの
|
|||||||
MIR 13命令の美しさを最大限に活かし、外部コンパイラ依存から完全に解放される。
|
MIR 13命令の美しさを最大限に活かし、外部コンパイラ依存から完全に解放される。
|
||||||
**究極の目標:80,000行→20,000行(75%削減)→ さらなる最適化へ**
|
**究極の目標:80,000行→20,000行(75%削減)→ さらなる最適化へ**
|
||||||
|
|
||||||
|
## 🔄 2025‑09‑26 Update(方針の明確化)
|
||||||
|
- 実行系の優先順位: LLVM は Python/llvmlite ハーネスを主経路に固定(llvm_sys 依存は前提にしない)。Rust VM/JIT は保守最小・比較用。
|
||||||
|
- パーサ: TokenCursor 統一を env ゲート下で進行。Step‑2/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 トグルで opt‑in。
|
||||||
|
|
||||||
|
推奨トグル
|
||||||
|
- `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で実装
|
1. **完全なセルフホスティング**: NyashコンパイラをNyashで実装
|
||||||
|
|||||||
@ -27,60 +27,23 @@ This roadmap is a living checklist to advance Phase 15 with small, safe boxes. U
|
|||||||
|
|
||||||
## Next (small boxes)
|
## Next (small boxes)
|
||||||
|
|
||||||
1) EXE-first: Selfhost Parser → EXE(Phase 15.2)🚀
|
1) Ny JSON ライブラリ(最小 DOM / JSON v0 対応)
|
||||||
- tools/build_compiler_exe.sh で EXE をビルド(同梱distパッケージ作成)
|
- Nyash 製の parse/stringify(object/array/string/number/bool/null)。
|
||||||
- dist/nyash_compiler/{nyash_compiler,nyash.toml,plugins/...} で独立実行
|
- Env: `NYASH_JSON_PROVIDER=ny`(既定OFF)。
|
||||||
- 入力: Nyソース → 出力: JSON v0(stdout)
|
- Smokes: roundtrip/エラー位置検証(quick 任意; CI非ブロック)。
|
||||||
- Smokes: sample.nyash→JSON 行生成(JSONのみ出力)
|
2) Ny Executor(最小命令セット)
|
||||||
- リスク: プラグイン解決(FileBox)をnyash.tomlで固定
|
- ops: const/binop/compare/branch/jump/ret/phi(Box 呼び出しは後段)。
|
||||||
2) LLVM Native EXE Generation(AOTパイプライン継続)
|
- Env: `NYASH_SELFHOST_EXEC=1`(既定OFF)。
|
||||||
- Python/llvmlite implementation as primary path (2400 lines, 10x faster development)
|
- Parity: PyVM/LLVM harness と stdout/exit の一致。
|
||||||
- LLVM backend object → executable pipeline completion
|
3) 呼び出し最小(Console/String/Array/Map P0)
|
||||||
- Separate `nyash-llvm-compiler` crate (reduce main build weight)
|
- call/externcall/boxcall の最小を接続。未知 extern は STRICT で拒否。
|
||||||
- Input: MIR (JSON/binary) → Output: native executable
|
4) Selfhost Parser の EXE 化(任意・後回し可)
|
||||||
- Link with nyrt runtime (static/dynamic options)
|
- `tools/build_compiler_exe.sh` により JSON v0 emit の単体配布(開発者向け)。
|
||||||
- Plugin all-direction build strategy (.so/.o/.a simultaneous generation)
|
5) PHI 自動化は Phase‑15 後(LoopForm = MIR18)
|
||||||
- Integration: `nyash --backend llvm --emit exe program.nyash -o program.exe`
|
- Phase‑15: 現行の Bridge‑PHI を維持(規約は「incoming pred=実際の遷移元」)。
|
||||||
3) Standard Ny std impl (P0→実体化)
|
- MIR18: LoopForm 強化+逆Loweringでの自動化に委譲(設計のみ先行)。
|
||||||
- Implement P0 methods for string/array/map in Nyash (keep NyRT primitives minimal)
|
6) Plugins CI split(継続)
|
||||||
- Enable via `nyash.toml` `[ny_plugins]` (opt‑in); extend `tools/jit_smoke.sh`
|
- Plugins は任意ジョブ(strict off)を維持。Core は軽量 quick を常時。
|
||||||
4) Ny compiler MVP (Ny→MIR on JIT path) (Phase 15.3) 🎯
|
|
||||||
- Ny tokenizer + recursive‑descent 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
|
|
||||||
- Stage‑1 checklist:
|
|
||||||
- [ ] return/int/string/arithmetic/paren JSON v0 emit
|
|
||||||
- [ ] Minimal ASI(newline separator + continuation tokens)
|
|
||||||
- [ ] Smokes: `return 1+2*3` / grouping / string literal
|
|
||||||
- Stage‑2 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 Unification(3層→2層革命)🎯
|
|
||||||
- コアBox(nyrt内蔵)削除、プラグイン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 自動化は Phase‑15 後(LoopForm = MIR18)
|
|
||||||
- Phase‑15: 現行の Bridge‑PHI を維持し、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 always‑on (JIT, plugins disabled); Plugins as optional job (strict off by default)
|
|
||||||
|
|
||||||
## Later (incremental)
|
## 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`
|
- JSON dump: `NYASH_DUMP_JSON_IR=1`
|
||||||
- (予告)LoopForm: MIR18 で仕様化予定
|
- (予告)LoopForm: MIR18 で仕様化予定
|
||||||
- Selfhost compiler: `NYASH_USE_NY_COMPILER=1`, child quiet: `NYASH_JSON_ONLY=1`
|
- 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/`
|
- EXE-first bundle: `tools/build_compiler_exe.sh` → `dist/nyash_compiler/`
|
||||||
- Load Ny plugins: `NYASH_LOAD_NY_PLUGINS=1` / `--load-ny-plugins`
|
- Load Ny plugins: `NYASH_LOAD_NY_PLUGINS=1` / `--load-ny-plugins`
|
||||||
- AOT smoke: `CLIF_SMOKE_RUN=1`
|
- AOT smoke: `CLIF_SMOKE_RUN=1`
|
||||||
|
|||||||
@ -37,6 +37,7 @@ Rust ランナー側は PyVM 経路にて `NYASH_SELFHOST_EXEC=1` を検出し
|
|||||||
|
|
||||||
### Stage 1 — MIR ローダ(2–3日)
|
### Stage 1 — MIR ローダ(2–3日)
|
||||||
- `mir_loader.nyash` で JSON v0 を読み込み、関数/ブロック/命令の構造体に展開(最初は要約のみ)。
|
- `mir_loader.nyash` で JSON v0 を読み込み、関数/ブロック/命令の構造体に展開(最初は要約のみ)。
|
||||||
|
- 依存: Nyash 製 JSON ライブラリ(`NYASH_JSON_PROVIDER=ny`)で DOM を提供(既定OFF、開発時のみON)。
|
||||||
- 受け入れ: ロードのみのスモーク(構文要素の個数検証)。
|
- 受け入れ: ロードのみのスモーク(構文要素の個数検証)。
|
||||||
- 備考: 立ち上げ初期は PyVM ハーネス用 MIR JSON(`{"functions":…}`)も受理し、要約(functions数)だけ行う(既定OFF)。
|
- 備考: 立ち上げ初期は PyVM ハーネス用 MIR JSON(`{"functions":…}`)も受理し、要約(functions数)だけ行う(既定OFF)。
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,15 @@ NYASH_DEV_AT_LOCAL = "1"
|
|||||||
[using]
|
[using]
|
||||||
paths = ["apps", "lib", "."]
|
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]
|
[modules]
|
||||||
# Map logical namespaces to Nyash source paths (consumed by runner)
|
# Map logical namespaces to Nyash source paths (consumed by runner)
|
||||||
selfhost.compiler.debug = "apps/selfhost/compiler/boxes/debug_box.nyash"
|
selfhost.compiler.debug = "apps/selfhost/compiler/boxes/debug_box.nyash"
|
||||||
|
|||||||
@ -336,12 +336,12 @@ pub fn enable_using() -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn resolve_fix_braces() -> bool {
|
pub fn resolve_fix_braces() -> bool {
|
||||||
// Phase 15: デフォルトON(using時のブレース均等修正が必須)
|
// Safer default: OFF(誤補正の副作用を避ける)
|
||||||
// NYASH_RESOLVE_FIX_BRACES=0 で明示的に無効化可能
|
// 明示ON: NYASH_RESOLVE_FIX_BRACES=1
|
||||||
match std::env::var("NYASH_RESOLVE_FIX_BRACES").ok().as_deref() {
|
matches!(
|
||||||
Some("0") | Some("false") | Some("off") => false,
|
std::env::var("NYASH_RESOLVE_FIX_BRACES").ok().as_deref(),
|
||||||
_ => enable_using(), // using有効時は自動でON
|
Some("1") | Some("true") | Some("on")
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
pub fn vm_use_py() -> bool {
|
pub fn vm_use_py() -> bool {
|
||||||
std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1")
|
std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1")
|
||||||
|
|||||||
@ -93,6 +93,8 @@ pub struct MirBuilder {
|
|||||||
plugin_method_sigs: HashMap<(String, String), super::MirType>,
|
plugin_method_sigs: HashMap<(String, String), super::MirType>,
|
||||||
/// Current static box name when lowering a static box body (e.g., "Main")
|
/// Current static box name when lowering a static box body (e.g., "Main")
|
||||||
current_static_box: Option<String>,
|
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
|
// include guards removed
|
||||||
|
|
||||||
@ -148,6 +150,7 @@ impl MirBuilder {
|
|||||||
value_types: HashMap::new(),
|
value_types: HashMap::new(),
|
||||||
plugin_method_sigs,
|
plugin_method_sigs,
|
||||||
current_static_box: None,
|
current_static_box: None,
|
||||||
|
static_method_index: std::collections::HashMap::new(),
|
||||||
|
|
||||||
loop_header_stack: Vec::new(),
|
loop_header_stack: Vec::new(),
|
||||||
loop_exit_stack: Vec::new(),
|
loop_exit_stack: Vec::new(),
|
||||||
|
|||||||
@ -296,6 +296,13 @@ impl super::MirBuilder {
|
|||||||
name: String,
|
name: String,
|
||||||
args: Vec<ASTNode>,
|
args: Vec<ASTNode>,
|
||||||
) -> Result<ValueId, String> {
|
) -> 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")
|
// Minimal TypeOp wiring via function-style: isType(value, "Type"), asType(value, "Type")
|
||||||
if (name == "isType" || name == "asType") && args.len() == 2 {
|
if (name == "isType" || name == "asType") && args.len() == 2 {
|
||||||
if let Some(type_name) = special_handlers::extract_string_literal(&args[1]) {
|
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; }
|
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();
|
let mut arg_values = Vec::new();
|
||||||
for a in args {
|
for a in args {
|
||||||
arg_values.push(self.build_expression(a)?);
|
arg_values.push(self.build_expression(a)?);
|
||||||
@ -344,9 +351,30 @@ impl super::MirBuilder {
|
|||||||
let dst = self.value_gen.next();
|
let dst = self.value_gen.next();
|
||||||
|
|
||||||
// === ChatGPT5 Pro Design: Type-safe function call resolution ===
|
// === ChatGPT5 Pro Design: Type-safe function call resolution ===
|
||||||
|
// Resolve call target using new type-safe system; if it fails, try static-method fallback
|
||||||
// Resolve call target using new type-safe system
|
let callee = match self.resolve_call_target(&name) {
|
||||||
let callee = 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
|
// Legacy compatibility: Create dummy func value for old systems
|
||||||
let fun_val = self.value_gen.next();
|
let fun_val = self.value_gen.next();
|
||||||
|
|||||||
@ -46,7 +46,17 @@ pub fn resolve_call_target(
|
|||||||
return Ok(Callee::Extern(name.to_string()));
|
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)))
|
Err(format!("Unresolved function: '{}'. {}", name, suggest_resolution(name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -137,6 +137,11 @@ impl super::MirBuilder {
|
|||||||
params.clone(),
|
params.clone(),
|
||||||
body.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
|
// Return void for declaration context
|
||||||
|
|||||||
@ -71,10 +71,10 @@ impl MirBuilder {
|
|||||||
self.push_if_merge(merge_block);
|
self.push_if_merge(merge_block);
|
||||||
|
|
||||||
// Pre-analysis: identify then/else assigned var for skip and hints
|
// 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
|
let assigned_else_pre = else_ast_for_analysis
|
||||||
.as_ref()
|
.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
|
let pre_then_var_value = assigned_then_pre
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|name| pre_if_var_map.get(name).copied());
|
.and_then(|name| pre_if_var_map.get(name).copied());
|
||||||
|
|||||||
@ -3,6 +3,28 @@ use crate::ast::ASTNode;
|
|||||||
|
|
||||||
// Lifecycle routines extracted from builder.rs
|
// Lifecycle routines extracted from builder.rs
|
||||||
impl super::MirBuilder {
|
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> {
|
pub(super) fn prepare_module(&mut self) -> Result<(), String> {
|
||||||
let module = MirModule::new("main".to_string());
|
let module = MirModule::new("main".to_string());
|
||||||
let main_signature = FunctionSignature {
|
let main_signature = FunctionSignature {
|
||||||
@ -35,6 +57,9 @@ impl super::MirBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn lower_root(&mut self, ast: ASTNode) -> Result<ValueId, String> {
|
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)
|
self.build_expression(ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +99,7 @@ impl super::MirBuilder {
|
|||||||
inferred = Some(mt);
|
inferred = Some(mt);
|
||||||
break 'outer;
|
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,
|
&function,
|
||||||
*v,
|
*v,
|
||||||
&self.value_types,
|
&self.value_types,
|
||||||
@ -89,7 +114,7 @@ impl super::MirBuilder {
|
|||||||
inferred = Some(mt);
|
inferred = Some(mt);
|
||||||
break;
|
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,
|
&function,
|
||||||
*v,
|
*v,
|
||||||
&self.value_types,
|
&self.value_types,
|
||||||
|
|||||||
@ -1,109 +1,11 @@
|
|||||||
use super::MirBuilder;
|
use super::MirBuilder;
|
||||||
use crate::ast::ASTNode;
|
use crate::ast::ASTNode;
|
||||||
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, MirType, ValueId};
|
use crate::mir::{BasicBlockId, MirInstruction, ValueId};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
// PHI-based return type inference helper
|
// Local helper has moved to phi_core::if_phi; keep call sites minimal
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MirBuilder {
|
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.
|
/// 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.
|
/// 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).
|
/// `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>>,
|
else_map_end_opt: &Option<std::collections::HashMap<String, super::ValueId>>,
|
||||||
skip_var: Option<&str>,
|
skip_var: Option<&str>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
use std::collections::HashSet;
|
let changed = crate::mir::phi_core::if_phi::compute_modified_names(
|
||||||
let mut names: HashSet<&str> = HashSet::new();
|
pre_if_snapshot,
|
||||||
for k in then_map_end.keys() { names.insert(k.as_str()); }
|
then_map_end,
|
||||||
if let Some(emap) = else_map_end_opt.as_ref() {
|
else_map_end_opt,
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for name in changed {
|
for name in changed {
|
||||||
if skip_var.map(|s| s == name).unwrap_or(false) {
|
if skip_var.map(|s| s == name).unwrap_or(false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let pre = match pre_if_snapshot.get(name) {
|
let pre = match pre_if_snapshot.get(name.as_str()) {
|
||||||
Some(v) => *v,
|
Some(v) => *v,
|
||||||
None => continue, // unknown before-if; skip
|
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
|
let else_v = else_map_end_opt
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|m| m.get(name).copied())
|
.and_then(|m| m.get(name.as_str()).copied())
|
||||||
.unwrap_or(pre);
|
.unwrap_or(pre);
|
||||||
// フェーズM: 常にPHI命令を使用(no_phi_mode撤廃)
|
// フェーズM: 常にPHI命令を使用(no_phi_mode撤廃)
|
||||||
// incoming の predecessor は "実際に merge に遷移してくる出口ブロック" を使用する
|
// incoming の predecessor は "実際に merge に遷移してくる出口ブロック" を使用する
|
||||||
@ -156,9 +44,11 @@ impl MirBuilder {
|
|||||||
let else_pred = else_exit_block_opt.unwrap_or(else_block);
|
let else_pred = else_exit_block_opt.unwrap_or(else_block);
|
||||||
let merged = self.value_gen.next();
|
let merged = self.value_gen.next();
|
||||||
let inputs = vec![(then_pred, then_v), (else_pred, else_v)];
|
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.emit_instruction(MirInstruction::Phi { dst: merged, inputs })?;
|
||||||
self.variable_map.insert(name.to_string(), merged);
|
self.variable_map.insert(name, merged);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -181,10 +71,10 @@ impl MirBuilder {
|
|||||||
) -> Result<ValueId, String> {
|
) -> Result<ValueId, String> {
|
||||||
// If only the then-branch assigns a variable (e.g., `if c { x = ... }`) and the else
|
// 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).
|
// 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
|
let assigned_var_else = else_ast_for_analysis
|
||||||
.as_ref()
|
.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();
|
let result_val = self.value_gen.next();
|
||||||
|
|
||||||
// フェーズM: no_phi_mode分岐削除(常にPHI命令を使用)
|
// フェーズM: no_phi_mode分岐削除(常にPHI命令を使用)
|
||||||
@ -213,7 +103,9 @@ impl MirBuilder {
|
|||||||
let else_pred = else_exit_block_opt.unwrap_or(else_block);
|
let else_pred = else_exit_block_opt.unwrap_or(else_block);
|
||||||
// Emit Phi for the assigned variable and bind it
|
// Emit Phi for the assigned variable and bind it
|
||||||
let inputs = vec![(then_pred, then_value_for_var), (else_pred, else_value_for_var)];
|
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.emit_instruction(MirInstruction::Phi { dst: result_val, inputs })?;
|
||||||
self.variable_map = pre_if_var_map.clone();
|
self.variable_map = pre_if_var_map.clone();
|
||||||
self.variable_map.insert(var_name, result_val);
|
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 then_pred = then_exit_block_opt.unwrap_or(then_block);
|
||||||
let else_pred = else_exit_block_opt.unwrap_or(else_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)];
|
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 })?;
|
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs })?;
|
||||||
// Merge variable map conservatively to pre-if snapshot (no new bindings)
|
// Merge variable map conservatively to pre-if snapshot (no new bindings)
|
||||||
self.variable_map = pre_if_var_map.clone();
|
self.variable_map = pre_if_var_map.clone();
|
||||||
|
|||||||
@ -13,20 +13,7 @@ impl MirBuilder {
|
|||||||
self.lower_if_form(condition, then_branch, else_branch)
|
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.
|
// Assigned variable extraction is centralized in phi_core::if_phi now.
|
||||||
/// 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build a loop statement: loop(condition) { body }
|
/// Build a loop statement: loop(condition) { body }
|
||||||
///
|
///
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use super::{BasicBlockId, ConstValue, MirInstruction, ValueId};
|
use super::{BasicBlockId, ConstValue, MirInstruction, ValueId};
|
||||||
|
use crate::mir::phi_core::loop_phi::IncompletePhi;
|
||||||
use crate::ast::ASTNode;
|
use crate::ast::ASTNode;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@ -15,16 +16,7 @@ use super::utils::{
|
|||||||
capture_actual_predecessor_and_jump,
|
capture_actual_predecessor_and_jump,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// 不完全なPhi nodeの情報
|
// IncompletePhi has moved to phi_core::loop_phi
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct IncompletePhi {
|
|
||||||
/// Phi nodeの結果ValueId
|
|
||||||
phi_id: ValueId,
|
|
||||||
/// 変数名
|
|
||||||
var_name: String,
|
|
||||||
/// 既知の入力値 (predecessor block id, value)
|
|
||||||
known_inputs: Vec<(BasicBlockId, ValueId)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ループビルダー - SSA形式でのループ構築を管理
|
/// ループビルダー - SSA形式でのループ構築を管理
|
||||||
pub struct LoopBuilder<'a> {
|
pub struct LoopBuilder<'a> {
|
||||||
@ -52,6 +44,8 @@ pub struct LoopBuilder<'a> {
|
|||||||
|
|
||||||
|
|
||||||
impl<'a> LoopBuilder<'a> {
|
impl<'a> LoopBuilder<'a> {
|
||||||
|
// Implement phi_core LoopPhiOps on LoopBuilder for in-place delegation
|
||||||
|
|
||||||
// =============================================================
|
// =============================================================
|
||||||
// Control Helpers — break/continue/jumps/unreachable handling
|
// Control Helpers — break/continue/jumps/unreachable handling
|
||||||
// =============================================================
|
// =============================================================
|
||||||
@ -125,33 +119,9 @@ impl<'a> LoopBuilder<'a> {
|
|||||||
body: Vec<ASTNode>,
|
body: Vec<ASTNode>,
|
||||||
) -> Result<ValueId, String> {
|
) -> Result<ValueId, String> {
|
||||||
// Pre-scan body for simple carrier pattern (up to 2 assigned variables, no break/continue)
|
// 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 assigned_vars: Vec<String> = Vec::new();
|
||||||
let mut has_ctrl = false;
|
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 {
|
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)
|
// 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());
|
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();
|
let latch_snapshot = self.get_current_variable_map();
|
||||||
// 以前は body_id に保存していたが、複数ブロックのボディや continue 混在時に不正確になるため
|
// 以前は body_id に保存していたが、複数ブロックのボディや continue 混在時に不正確になるため
|
||||||
// 実際の latch_id に対してスナップショットを紐づける
|
// 実際の 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
|
// Only jump back to header if the latch block is not already terminated
|
||||||
{
|
{
|
||||||
let need_jump = {
|
let need_jump = {
|
||||||
@ -266,113 +240,49 @@ impl<'a> LoopBuilder<'a> {
|
|||||||
header_id: BasicBlockId,
|
header_id: BasicBlockId,
|
||||||
preheader_id: BasicBlockId,
|
preheader_id: BasicBlockId,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
// 現在の変数マップから、ループで使用される可能性のある変数を取得
|
|
||||||
let current_vars = self.get_current_variable_map();
|
let current_vars = self.get_current_variable_map();
|
||||||
// preheader時点のスナップショット(後でphi入力の解析に使う)
|
crate::mir::phi_core::loop_phi::save_block_snapshot(
|
||||||
self.block_var_maps
|
&mut self.block_var_maps,
|
||||||
.insert(preheader_id, current_vars.clone());
|
preheader_id,
|
||||||
|
¤t_vars,
|
||||||
// 各変数に対して不完全なPhi nodeを作成
|
);
|
||||||
let mut incomplete_phis = Vec::new();
|
let incs = crate::mir::phi_core::loop_phi::prepare_loop_variables_with(
|
||||||
for (var_name, &value_before) in ¤t_vars {
|
self,
|
||||||
let phi_id = self.new_value();
|
header_id,
|
||||||
|
preheader_id,
|
||||||
// 不完全なPhi nodeを作成(preheaderからの値のみ設定)
|
¤t_vars,
|
||||||
let incomplete_phi = IncompletePhi {
|
)?;
|
||||||
phi_id,
|
self.incomplete_phis.insert(header_id, incs);
|
||||||
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);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ブロックをシールし、不完全なPhi nodeを完成させる
|
/// ブロックをシールし、不完全なPhi nodeを完成させる
|
||||||
fn seal_block(&mut self, block_id: BasicBlockId, latch_id: BasicBlockId) -> Result<(), String> {
|
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) {
|
if let Some(incomplete_phis) = self.incomplete_phis.remove(&block_id) {
|
||||||
for mut phi in incomplete_phis {
|
let cont_snaps = self.continue_snapshots.clone();
|
||||||
for (cid, snapshot) in &self.continue_snapshots {
|
crate::mir::phi_core::loop_phi::seal_incomplete_phis_with(
|
||||||
if let Some(v) = snapshot.get(&phi.var_name) {
|
self,
|
||||||
phi.known_inputs.push((*cid, *v));
|
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)?;
|
self.mark_block_sealed(block_id)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Exitブロックで変数のPHIを生成(breakポイントでの値を統一)
|
/// Exitブロックで変数のPHIを生成(breakポイントでの値を統一)
|
||||||
fn create_exit_phis(&mut self, header_id: BasicBlockId, exit_id: BasicBlockId) -> Result<(), String> {
|
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();
|
let header_vars = self.get_current_variable_map();
|
||||||
for var_name in header_vars.keys() {
|
let exit_snaps = self.exit_snapshots.clone();
|
||||||
all_vars.insert(var_name.clone());
|
crate::mir::phi_core::loop_phi::build_exit_phis_with(
|
||||||
}
|
self,
|
||||||
|
header_id,
|
||||||
// break時点の変数を収集
|
exit_id,
|
||||||
for (_, snapshot) in &self.exit_snapshots {
|
&header_vars,
|
||||||
for var_name in snapshot.keys() {
|
&exit_snaps,
|
||||||
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(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- ヘルパーメソッド(親ビルダーへの委譲) ---
|
// --- ヘルパーメソッド(親ビルダーへの委譲) ---
|
||||||
@ -549,7 +459,7 @@ impl<'a> LoopBuilder<'a> {
|
|||||||
|
|
||||||
// Capture pre-if variable map (used for phi normalization)
|
// Capture pre-if variable map (used for phi normalization)
|
||||||
let pre_if_var_map = self.get_current_variable_map();
|
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
|
// then branch
|
||||||
self.set_current_block(then_bb)?;
|
self.set_current_block(then_bb)?;
|
||||||
@ -588,60 +498,74 @@ impl<'a> LoopBuilder<'a> {
|
|||||||
|
|
||||||
// Continue at merge
|
// Continue at merge
|
||||||
self.set_current_block(merge_bb)?;
|
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 mut vars: std::collections::HashSet<String> = std::collections::HashSet::new();
|
||||||
let then_prog = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() };
|
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 {
|
if let Some(es) = &else_body {
|
||||||
let else_prog = ASTNode::Program { statements: es.clone(), span: crate::ast::Span::unknown() };
|
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
|
// Reset to pre-if map before rebinding to ensure a clean environment
|
||||||
self.parent_builder.variable_map = pre_if_var_map.clone();
|
self.parent_builder.variable_map = pre_if_var_map.clone();
|
||||||
for var_name in vars.into_iter() {
|
// Use shared helper to merge modified variables at merge block
|
||||||
let then_val = then_var_map_end.get(&var_name).copied().or_else(|| pre_then_var_value.get(&var_name).copied());
|
struct Ops<'b, 'a>(&'b mut LoopBuilder<'a>);
|
||||||
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());
|
impl<'b, 'a> crate::mir::phi_core::if_phi::PhiMergeOps for Ops<'b, 'a> {
|
||||||
|
fn new_value(&mut self) -> ValueId { self.0.new_value() }
|
||||||
if let (Some(tv), Some(ev)) = (then_val, else_val) {
|
fn emit_phi_at_block_start(
|
||||||
let mut incomings: Vec<(BasicBlockId, ValueId)> = Vec::new();
|
&mut self,
|
||||||
if let Some(pred) = then_pred_to_merge { incomings.push((pred, tv)); }
|
block: BasicBlockId,
|
||||||
if let Some(pred) = else_pred_to_merge { incomings.push((pred, ev)); }
|
dst: ValueId,
|
||||||
match incomings.len() {
|
inputs: Vec<(BasicBlockId, ValueId)>,
|
||||||
0 => {}
|
) -> Result<(), String> { self.0.emit_phi_at_block_start(block, dst, inputs) }
|
||||||
1 => {
|
fn update_var(&mut self, name: String, value: ValueId) { self.0.parent_builder.variable_map.insert(name, value); }
|
||||||
let (_pred, v) = incomings[0];
|
fn debug_verify_phi_inputs(&mut self, merge_bb: BasicBlockId, inputs: &[(BasicBlockId, ValueId)]) {
|
||||||
self.parent_builder.variable_map.insert(var_name, v);
|
if let Some(ref func) = self.0.parent_builder.current_function {
|
||||||
}
|
crate::mir::phi_core::common::debug_verify_phi_inputs(func, merge_bb, inputs);
|
||||||
_ => {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 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();
|
let void_id = self.new_value();
|
||||||
self.emit_const(void_id, ConstValue::Void)?;
|
self.emit_const(void_id, ConstValue::Void)?;
|
||||||
Ok(void_id)
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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 loop_builder; // SSA loop construction with phi nodes
|
||||||
pub mod optimizer;
|
pub mod optimizer;
|
||||||
pub mod utils; // Phase 15 control flow utilities for root treatment
|
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_passes; // optimizer passes (normalize/diagnostics)
|
||||||
pub mod optimizer_stats; // extracted stats struct
|
pub mod optimizer_stats; // extracted stats struct
|
||||||
pub mod passes;
|
pub mod passes;
|
||||||
|
|||||||
49
src/mir/phi_core/common.rs
Normal file
49
src/mir/phi_core/common.rs
Normal 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
222
src/mir/phi_core/if_phi.rs
Normal 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,
|
||||||
|
)
|
||||||
|
}
|
||||||
181
src/mir/phi_core/loop_phi.rs
Normal file
181
src/mir/phi_core/loop_phi.rs
Normal 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
17
src/mir/phi_core/mod.rs
Normal 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.
|
||||||
723
src/parser/statements_backup.rs
Normal file
723
src/parser/statements_backup.rs
Normal 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(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Single‑catch 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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -141,7 +141,21 @@ pub fn strip_using_and_register(
|
|||||||
// Two forms:
|
// Two forms:
|
||||||
// - using path "..." [as Alias]
|
// - using path "..." [as Alias]
|
||||||
// - using namespace.with.dots [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
|
// alias case: resolve namespace to a concrete path
|
||||||
let mut found: Option<String> = using_ctx
|
let mut found: Option<String> = using_ctx
|
||||||
.pending_modules
|
.pending_modules
|
||||||
@ -199,6 +213,7 @@ pub fn strip_using_and_register(
|
|||||||
eprintln!("[using] still unresolved: {} as {}", ns, alias);
|
eprintln!("[using] still unresolved: {} as {}", ns, alias);
|
||||||
}
|
}
|
||||||
found
|
found
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// direct namespace without alias
|
// direct namespace without alias
|
||||||
match crate::runner::pipeline::resolve_using_target(
|
match crate::runner::pipeline::resolve_using_target(
|
||||||
@ -397,6 +412,9 @@ pub fn strip_using_and_register(
|
|||||||
combined.push('\n');
|
combined.push('\n');
|
||||||
crate::runner::modes::common_util::resolve::seam::fix_prelude_braces_if_enabled(prelude_clean, &mut combined, trace);
|
crate::runner::modes::common_util::resolve::seam::fix_prelude_braces_if_enabled(prelude_clean, &mut combined, trace);
|
||||||
combined.push_str(&out);
|
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)
|
Ok(combined)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user