diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 5f8252e6..b6841ccc 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -101,6 +101,20 @@ - `CalleeResolverBox::classify_box_kind` に `BreakFinderBox` / `PhiInjectorBox` / `LoopSSA` を追加し、 Stage‑1/Stage‑B 用 LoopSSA 箱を `CalleeBoxKind::StaticCompiler` として明示。 +**IfForm / empty else-branch の SSA fix(Stage‑1 UsingResolverFull 対応)** +- `src/mir/builder/if_form.rs`: + - `if cond { then }`(else なし)のパターンで、 + - else-entry 用に pre_if の `variable_map` から PHI ノードを生成したあと、 + - その PHI 適用後の `variable_map` を `else_var_map_end_opt=Some(...)` として merge フェーズに渡すように修正。 + - 以前は empty else の場合に `else_var_map_end_opt` が `None` になっており、 + `merge_modified_vars` が pre_if の古い ValueId にフォールバックして、 + merge ブロックで未定義の `%0` などを参照するケースがあった(`Stage1UsingResolverFull.main/0` の UndefinedValue)。 + - 修正後は then/else 両ブランチで「PHI 適用後の variable_map」が merge に渡されるため、 + empty else でも header/merge の SSA が崩れない。 +- 検証: + - `src/tests/mir_stage1_using_resolver_verify.rs::mir_stage1_using_resolver_full_collect_entries_verifies` が + `MirVerifier` 緑になり、`Stage1UsingResolverFull.main/0()` の merge ブロックで PHI 後の値(例: `%24`)を正しく参照していることを MIR dump で確認済み。 + **.hako 側の今後(25.1k 後半)** - `LoopSSA.stabilize_merges(json)` を Rust LoopForm v2 の Carrier/Pinned 規約に合わせて実装する(現在はほぼ stub)。 - Stage‑B Test2(`tools/test_stageb_min.sh`)で得られる Program(JSON v0) に対し、 @@ -173,6 +187,33 @@ - pinned 変数の扱い、 - exit PHI の構築 を Rust LoopForm v2 に合わせて実装。 + - 2025-11-19 追記: + - `BreakFinderBox._find_loops/2` については、まず .hako 側を「region box」的に整理した。 + - `header_pos` / `header_id` / `exit_pos` / `exit_id` まわりの異常系を `continue` ではなく + `next_i` ローカルへの代入で表現し、1 イテレーションの末尾で `i = next_i` に合流させる形に変更。 + - これにより、LoopForm v2 / LoopSSA 側から見ると「単一 region 内での分岐+最後に合流」という構造になり、 + carrier/pinned 検出や今後の SSA 解析が行いやすくなった(挙動は従来と同じ)。 + +### 2-5. static box / me セマンティクス(観測タスクへ移行) + +- 現状: + - `static box StringHelpers` のようなユーティリティ箱で、`me.starts_with(src, i, kw)` のように + 同一箱内のヘルパー(`starts_with`)を `me.` 経由で呼んでいたため、Stage‑3 降下時に引数ずれが発生していた。 + - 具体的には、`StringHelpers.starts_with_kw/3` → `StringHelpers.starts_with/3` の降下で + 実際の呼び出しが `starts_with("StringHelpers", src, i, kw)` のような 4 引数形になり、 + `starts_with(src, i, pat)` 側では `src="StringHelpers"` / `i=<ソース全文>` となって、 + `if i + m > n` が `String > Integer(13)` 比較に化けていた。 +- 対応(完了済み・局所修正): + - `lang/src/shared/common/string_helpers.hako` の `starts_with_kw` を、 + `if me.starts_with(src, i, kw) == 0` から `if starts_with(src, i, kw) == 0` に書き換え、 + static box ユーティリティに対する `me` 依存を除去した。 + - これにより、`starts_with` 内でのガード比較 `i + m > n` はすべて整数同士となり、 + Stage‑B fib ケースで発生していた `String("...") > Integer(13)` の TypeError は解消済み。 +- 今後(Phase 25.1p 以降): + - static box 全般における `me` セマンティクス(本当に「シングルトンインスタンス」として扱う箱と、 + 純粋な名前空間箱をどう区別するか)は、25.1p の DebugLog フェーズで観測しながら設計を詰める。 + - 実際に Rust 層(`build_me_expression` / `lower_static_method_as_function` / `FunctionDefBuilder::is_instance_method`)を + 統一規約に寄せる作業は、25.1p 以降のサブタスクとして扱う(現時点では局所修正でバグのみ解消)。 --- @@ -191,9 +232,13 @@ ここから先は「どのフェーズを進めるか」をそのときの優先度で選ぶ感じだよ。 -1. **Stage‑B 型エラーの根治(Phase 25.1c か新フェーズ 25.1n)** - - `Main.main` 内の `String(...) > Integer(13)` 比較を最小ケースで再現。 - - Stage‑B BodyExtractor / ParserBox / TestBox のどこで型が崩れているかを箱単位で切り分け。 +1. **Stage‑B / BreakFinder / FuncScanner ラインの SSA 根治(Phase 25.1m 続き)** + - JSON v0 bridge の Loop lowering で、`backedge_to_cond` が `Jump` だけでなく `Branch(cond→header/exit)` も認識するように修正(`loop_.rs`)。 + → BreakFinderBox._find_loops/2 のような break を含むループでも header PHI が正しく張られるようにした。 + - BreakFinderBox は解析用の static box として扱い、`me._find_loops` / `me._jumps_to` を `BreakFinderBox._find_loops` / `_jumps_to` に正規化。 + → `me` 未定義による Undefined ValueId を避ける。 + - Stage‑B → Stage‑1 パーサ経路は、`ParserBox.parse_program2` のトップレベルループではなく、`parse_block2` で `Main.main` の本文をブロックとしてパースし、 + Program(JSON v0) を自前で組み立てる構造に変更(Stage‑B 固まり問題を回避)。 2. **Stage‑B 再入ガード `env.set/2` の整理** - 再入ガードを Box 内 state で持つ方向か、Rust 側に dev 専用 extern を追加するかを決める。 @@ -221,3 +266,34 @@ 以上が 2025-11-18 時点の Phase 21.8 / 25 / 25.1 / 25.2 ラインの「いまどこ」「なに済み」「なに残り」だよ。 次にどの箱から攻めるか決めたら、ここに箇条書きで足していこうね。 + + +## 2-? Stage‑B FuncScanner (in progress) + +- StageBFuncScannerBox を compiler_stageb.hako 内に追加し、StageBDriverBox.main からは FuncScannerBox ではなくこちらを呼ぶように変更。 +- VM 側の Undefined value (BreakFinderBox._find_loops/2, FuncScannerBox.scan_all_boxes/1) は解消済みで、Stage‑B 自体は rc=0 まで到達する。 +- ただし現時点では StageBFuncScannerBox.scan_all_boxes(src) が fib サンプルに対しても defs=[] を返しており、Program(JSON) に TestBox.fib が載っていない状態。 +- 一度 StageBDriverBox から直接 FuncScannerBox.scan_all_boxes を呼ぶ構成も試したが、この経路では FuncScannerBox.scan_all_boxes/1 で再び Undefined value が発生したため、現状は StageBFuncScannerBox 経由に戻している(Stage‑B rc は 0、defs は空)。 +- StageBFuncScannerBox._find_matching_brace は FuncScannerBox._find_matching_brace と同じ balanced scan アルゴリズムに揃えつつ、`[funcscan/debug] _find_matching_brace enter open_idx=... n=...` ログで挙動を観測できるようにしてある(close_idx=-1 になる原因調査用)。 +- Rust Stage‑3 パーサ側の `using` パースを拡張し、`using "lang.compiler.parser.box" as ParserBox` 形式を受理するようにしたうえで、FuncScannerBox 自身もこの形に正規化した。 + - これにより `lang/src/compiler/entry/func_scanner.hako` 単体に対して `--dump-mir` が通るようになり、FuncScannerBox.scan_all_boxes/1 の MIR を直接観測できる。 +- 次の担当者は StageBFuncScannerBox.scan_all_boxes/1 と FuncScannerBox.scan_all_boxes/1 の MIR を比較しつつ、Stage‑B body 抽出→FuncScanner→defs 注入の導線と LoopBuilder 側の exit PHI を突き合わせてほしい。 + + +## 3. Static Box フィールド仕様ドキュメント(完了) + +- docs/reference/language/LANGUAGE_REFERENCE_2025.md: 3.4 Static Boxパターンに「static box 内のフィールドはすべて static フィールドとして扱われる」旨を明記した。 +- 言語仕様としては、static box の中では `PI: FloatBox` のような宣言で十分であり、追加の `static` キーワードは不要であることをドキュメント上で固定。 +- 次タスク候補(Claude Code 向け): static box / box のフィールド挙動に関するサンプルとテスト(VM/JSON v0 両方)の整理、および static box 上の `me` セマンティクス(25.1p DebugLog フェーズと連動)の仕様化。 + + +## 3. Phase 25.1q — LoopForm Front Unification (planning) + +- 目的: Rust AST ルート (LoopBuilder) と JSON v0 ルート (json_v0_bridge::lower_loop_stmt) のループ lowering フロントを整理し、PHI/LoopForm の SSOT を phi_core + LoopFormBuilder に明示的に寄せる。 +- 現状: + - Rust AST → MIR は LoopBuilder + LoopFormBuilder で統一済み。 + - JSON v0 → MIR は loop_.rs から同じ phi_core を呼んでいるが、ファイルが分かれており、LLM/人間が誤って JSON 側だけを触るケースがあった。 +- 25.1q でやること(設計メモレベル): + - docs に「LoopForm/PHI の意味論は phi_core が SSOT」と明記。 + - loop_.rs を「JSON から LoopForm に渡す薄いアダプタ」に限定し、余計なデバッグや独自分岐を増やさない方針を固定。 + - 将来的に JSON v0 → AST → MirBuilder に寄せる統合案を設計メモとして整理(実装は 25.2 以降)。 diff --git a/docs/development/roadmap/phases/phase-25.1b/README.md b/docs/development/roadmap/phases/phase-25.1b/README.md index 419ea0e6..31092a07 100644 --- a/docs/development/roadmap/phases/phase-25.1b/README.md +++ b/docs/development/roadmap/phases/phase-25.1b/README.md @@ -451,6 +451,22 @@ Status: Step0〜3 実装済み・Step4(Method/Extern)実装フェーズ - これにより Stage‑B / Stage‑1 側で `_build_module_map()` のような「params: [] だが `me` を使う」メソッドでも、 Rust VM 実行時に `me` 未定義にならず、BoxCall が正しく解決されるようになった。 +### IfForm / empty else-branch の SSA fix(Stage‑1 UsingResolverFull 対応) + +- `src/mir/builder/if_form.rs`: + - `if cond { then }` のように else ブランチが省略されたケースでも、 + - `else` の入口で pre_if の `variable_map` を使って PHI ノードを生成し、 + - その結果の `variable_map` を `else_var_map_end_opt = Some(...)` として merge フェーズに渡すように修正した。 + - 以前は empty else の場合に `else_var_map_end_opt` が `None` になり、`merge_modified_vars` が pre_if 時点の ValueId にフォールバックしていたため、 + - merge ブロックで古い ValueId(PHI 適用前の値)を参照し、`Undefined value %0` などの SSA violation を引き起こしていた。 + - 修正後は、then/else 両ブランチで「PHI 適用後の variable_map」が merge に渡されるため、 + empty else でもヘッダ/merge の SSA が崩れない。 + +- 検証: + - `src/tests/mir_stage1_using_resolver_verify.rs::mir_stage1_using_resolver_full_collect_entries_verifies` が、 + - `MirVerifier` 緑(UndefinedValue/InvalidPhi なし)、 + - `Stage1UsingResolverFull.main/0()` の merge ブロックで PHI 後の値(例: `%24`)を正しく参照していることを MIR dump で確認。 + - Selfhost への移植指針(Rust SSOT に沿った箱設計): - `MethodCall`: - Hako 側では「どの Box のどのメソッドを MIR の `mir_call(Method)` に落とすか」を Box 単位の helper で管理する(`LoopOptsBox` や `Cli*Box` と同様に)。 diff --git a/docs/development/roadmap/phases/phase-25.1c/README.md b/docs/development/roadmap/phases/phase-25.1c/README.md index 97a4b731..ac8d5309 100644 --- a/docs/development/roadmap/phases/phase-25.1c/README.md +++ b/docs/development/roadmap/phases/phase-25.1c/README.md @@ -48,7 +48,7 @@ Status: planning(構造整理フェーズ・挙動は変えない) が 1 関数に詰め込まれており、MIR 上でも巨大な `Main.main` になっている。 - 25.1c ではこれを「箱理論」に沿って分割する方針を立てており、Phase 25.1c 冒頭でまず Stage‑B 側を 4 箱構造にリファクタした: - `Main`(エントリ薄箱): `main(args){ return StageBDriverBox.main(args) }` のみを担当。 - - `StageBDriverBox`(オーケストレーション): `StageBArgsBox.resolve_src` → `StageBBodyExtractorBox.build_body_src` → `ParserBox.parse_program2` → defs 挿入 → `print(ast_json)` だけを見る。 + - `StageBDriverBox`(オーケストレーション): `StageBArgsBox.resolve_src` → `StageBBodyExtractorBox.build_body_src` → `ParserBox.parse_block2` → defs 挿入 → `print(ast_json)` だけを見る。 - `StageBArgsBox`(CLI 引数と bundle/require の扱いだけを担当): もともとの「args/src/src_file/HAKO_SOURCE_FILE_CONTENT/return 0」ロジックを完全移動。 - `StageBBodyExtractorBox`(`body_src` 抽出ロジック+bundle/using/trim を担当): もともとの `body_src` 抽出〜コメント削除〜BundleResolver/Stage1UsingResolverBox〜前後 trim までを丸ごとカプセル化。 - いずれもロジックはそのまま移動であり、コメント・using・ログを含めて挙動は完全に不変(同じ Program(JSON v0)、同じログ、同じ `VM error: Invalid value`)であることを selfhost CLI サンプルで確認済み。エラーの発生箇所は `Main.main` から `StageBArgsBox.resolve_src/1` に関数名だけ変わっており、SSA/Loop 側の根本修正はこの後のタスク(LoopBuilder / LocalSSA 整理)で扱う。 @@ -92,11 +92,11 @@ Status: planning(構造整理フェーズ・挙動は変えない) - 代表 canary: - `tools/smokes/v2/profiles/quick/core/phase251/stageb_fib_program_defs_canary_vm.sh` - `tools/smokes/v2/profiles/quick/core/phase251/selfhost_cli_run_basic_vm.sh` - - まずは `compiler_stageb.hako` の流れを箱ごとに分解してログする: - - `StageBArgsBox.resolve_src` - - `StageBBodyExtractorBox.build_body_src` - - `ParserBox.parse_program2` - - `FuncScannerBox.scan_all_boxes` + - まずは `compiler_stageb.hako` の流れを箱ごとに分解してログする: + - `StageBArgsBox.resolve_src` + - `StageBBodyExtractorBox.build_body_src` + - `ParserBox.parse_program2` / `ParserBox.parse_block2` + - `StageBFuncScannerBox.scan_all_boxes`(Phase 25.1c 時点では Stage‑B ローカル実装) - 各箱の入口/出口に `[stageb/trace:.:enter|leave]` のような軽いタグを置き、どの箱が rc=1 の直前で止まっているかを特定する(挙動は変えない)。 - 2) Rust Region レイヤを「正解ビュー」として .hako 側を寄せる diff --git a/docs/development/roadmap/phases/phase-25.1k/README.md b/docs/development/roadmap/phases/phase-25.1k/README.md index 82684934..3d4891ce 100644 --- a/docs/development/roadmap/phases/phase-25.1k/README.md +++ b/docs/development/roadmap/phases/phase-25.1k/README.md @@ -63,6 +63,28 @@ Status: in progress(.hako 側 LoopSSA v2 本体実装/Rust 側は既存 SSA/ - ゴール: - LoopSSA が「本来そのループに属さない block」を body に含めないようにする(ValueId(50) のような飛び火を防ぐ)。 +### K‑B2: BreakFinderBox._find_loops/2 ループ構造の整理(region box 化の一歩) + +- 背景: + - `BreakFinderBox._find_loops/2` のループ内で `header_pos` / `header_id` / `exit_pos` / `exit_id` を扱う際、 + `header_id == null` や `exit_pos` 異常系で `continue` を多用していたため、 + LoopForm v2 / LoopSSA 側から見ると「ループ本体が細かい early-continue に分割された」形になっていた。 + - Stage‑B Test2 では、この経路で `header_pos` や `i` まわりの ValueId が観測しづらくなり、 + SSA バグ調査の足場としても扱いづらかった。 +- 対応: + - `lang/src/compiler/builder/ssa/exit_phi/break_finder.hako::_find_loops` をリファクタし、 + `next_i` ローカルを導入して「1 イテレーション中のすべての分岐が最後に `i = next_i` に合流する」形に整理した。 + - `header_id == null` / `exit_pos < 0` / 範囲外 / `exit_id == null` の各ケースは、 + `next_i` の更新だけで表現し、`continue` を使わない構造に変更。 + - 正常系(`header_id` / `exit_pos` / `exit_id` が全て有効)の場合のみ、 + `body_blocks` / `ControlFormBox` / `loop_info` の構築と `loops.push(loop_info)` を行い、 + そのうえで `next_i = exit_pos + 12` を設定。 + - これにより: + - LoopForm v2 から見ると「ループ本体が単一の region(`next_i` による合流)」として観測できるようになり、 + break/continue 経路の SSA 構造が単純化された。 + - ループの意味論自体は従来どおり(ヘッダ/出口検出ロジックや body_blocks の定義は不変)で、 + 既存の JSON v0 / LoopSSA の挙動には影響しない。 + ### K‑C: PhiInjectorBox の v2 への一歩(Carrier/Pinned の入口だけ作る) - 目的: diff --git a/docs/development/roadmap/phases/phase-25.1p/README.md b/docs/development/roadmap/phases/phase-25.1p/README.md index cdc33cc9..efd90960 100644 --- a/docs/development/roadmap/phases/phase-25.1p/README.md +++ b/docs/development/roadmap/phases/phase-25.1p/README.md @@ -150,6 +150,24 @@ _build_module_map() { static box メソッドに対しても暗黙 receiver をパラメータとして扱うべきかどうかを設計レベルで判断する。 - [ ] 必要であれば、別フェーズ(例: 25.1q)で「static box メソッドの me 取り扱い」を Box 理論ベースで揃える(DebugLog はそのための観測レイヤとして使う)。 +### 6. Static box / me セマンティクス統一(部分完了メモ) + +- 25.1c/25.1m までに判明したこと: + - `static box StringHelpers` のような「純粋ユーティリティ箱」で、`me.starts_with(src, i, kw)` のように + 同一箱内ヘルパーを receiver 経由で呼ぶと、Stage‑3 降下で引数ずれ(`i` にソース全文が入る)が発生しうる。 + - 実際、`StringHelpers.starts_with_kw/3` → `StringHelpers.starts_with/3` 経路で + `StringHelpers.starts_with("StringHelpers", src, i, kw)` のような形になり、 + `i + m > n` が `String > Integer(13)` の比較に化けていた。 +- 25.1m での暫定対応(完了済み): + - `StringHelpers.starts_with_kw` 内を `me.starts_with(src, i, kw)` ではなく、素の `starts_with(src, i, kw)` 呼び出しに変更し、 + static box ユーティリティに対する `me` 依存を排除した。 + - これにより、`starts_with` 内の比較は全て整数同士となり、`String > Integer` 型エラーは解消済み。 +- 25.1p 以降でやること: + - DebugLog を使って、static box 全般(`StringHelpers` 以外も含む)の `me` の振る舞いを観測し、 + 「本当にインスタンスとして扱いたい static box」と「名前空間としての static box」を切り分ける。 + - 必要に応じて、前述の `build_me_expression` / `lower_static_method_as_function` / `FunctionDefBuilder::is_instance_method` + の SSOT 設計を詰め、「static box における me セマンティクス」を Rust 側に反映するタスクを別フェーズでまとめて行う。 + ## いつやるか(優先度メモ) - 今回は **フォルダ+設計メモだけ** で、実装はまだ行わない。 diff --git a/docs/development/roadmap/phases/phase-25.1q/README.md b/docs/development/roadmap/phases/phase-25.1q/README.md new file mode 100644 index 00000000..dd01d831 --- /dev/null +++ b/docs/development/roadmap/phases/phase-25.1q/README.md @@ -0,0 +1,92 @@ +# Phase 25.1q — LoopForm Front Unification (AST / JSON v0) + +Status: planning-only(Phase 25.1 ラインの安定化が終わったあとに着手) + +## ゴール + +- ループ lowering / PHI 構築の **SSOT を `phi_core + LoopFormBuilder` に完全に寄せる**。 + - Rust AST → MIR(`MirBuilder` / `LoopBuilder`)と JSON v0 → MIR(`json_v0_bridge::lower_loop_stmt`)のフロントを整理し、 + 「どこを直せば loop/PHI の意味論が変わるか」を 1 箇所に明示する。 +- 実装者・LLM の混乱ポイントを減らす: + - Stage‑B / FuncScanner のようなループバグ調査時に、「Rust AST 側を触るべきか」「JSON v0 側を触るべきか」で迷わない構造にする。 + - 今回のように `loop_.rs` 側だけにログ・修正を入れてしまう誤りを防ぐ。 + +## 現状(25.1m / 25.1p 時点の構造) + +- **バックエンド(SSOT)** + - `src/mir/phi_core/loop_phi.rs` / `src/mir/phi_core/loopform_builder.rs` + - LoopForm v2 / LoopSSA v2 の本体。 + - `LoopFormBuilder` + `LoopFormOps` として、ヘッダ PHI / exit PHI / continue スナップショットなどを一元的に扱う。 + +- **Rust AST → MIR 経路** + - `ASTNode::Loop` + - `src/mir/builder_modularized/control_flow.rs::build_loop_statement` + - `src/mir/builder/control_flow.rs::cf_loop` + - `src/mir/loop_builder.rs::LoopBuilder::build_loop_with_loopform` + - こちらはすでに LoopForm v2 / ControlForm v2 に統一済みで、「Rust パーサで読んだ .hako」を MIR に落とす主経路。 + +- **JSON v0 → MIR 経路** + - `Program(JSON v0)`(`ProgramV0`): + - `src/runner/json_v0_bridge/lowering.rs::lower_stmt_with_vars` + - `StmtV0::Loop { .. } => loop_::lower_loop_stmt(...)` + - `src/runner/json_v0_bridge/lowering/loop_.rs::lower_loop_stmt` + - ここも LoopForm v2 / phi_core を呼ぶ構造にはなっているが、 + - ファイルが AST ルートとは別に分かれている + - 追加ログや一時的なデバッグコードが入りやすく、「どの経路でループが下りているか」分かりづらい状態になりがち。 + +- 結果として: + - Stage‑B / FuncScannerBox のような「Rust AST 経路」を見たいときに、誤って `loop_.rs` 側だけを触る、といった混乱が起きやすい。 + - 一方で JSON v0 経路は provider (`env.mirbuilder.emit` / `--program-json-to-mir`) で重要なので、急に削除はできない。 + +## スコープ(25.1q でやること) + +1. **LoopForm / phi_core を SSOT として明文化(ドキュメント整理)** + - `docs/development/roadmap/phases/phase-25.1b/` / `phase-25.1m/` / 本 `phase-25.1q` で: + - ループ意味論(preheader/header/body/latch/exit、continue/break スナップショット、PHI)の SSOT を + `phi_core::loop_phi` / `LoopFormBuilder` に一本化すると明言する。 + - `LoopBuilder`(Rust AST フロント)と `json_v0_bridge::lower_loop_stmt`(JSON フロント)は「薄いアダプタ」に留める方針を書いておく。 + +2. **json_v0_bridge::lower_loop_stmt の責務縮小(薄いフロント化)** + - 目標: `loop_.rs` は「JSON から LoopForm に渡すための最低限の橋渡し」に限定する。 + - 具体案: + - 余計なデバッグログや独自判定を段階的に削り、やることを + - preheader/header/body/latch/exit のブロック ID を用意する + - ループ開始時点の `vars` を LoopPhiOps 実装に渡す + - break / continue のスナップショット記録を呼び出す + に絞る。 + - ループ構造・PHI の仕様変更は **phi_core 側だけ** に集約し、`loop_.rs` 側には分岐や条件を増やさない。 + +3. **ログ・デバッグ経路の整理** + - `HAKO_LOOP_PHI_TRACE` / `NYASH_LOOPFORM_DEBUG` などのトグルについて: + - どのフロント(Rust AST / JSON)からでも同じタグで観測できるようにし、ログの出し場所を整理する。 + - `loop_.rs` に残っている「一時的な ALWAYS LOG」などはすでに削除済みだが、今後も dev トレースは必ず env ガード越しに行う。 + +4. **JSON v0 → AST → MirBuilder 統合の検討(設計レベルのみ)** + - 将来案として: + - `ProgramV0` を一度 Nyash AST 相当の構造体に変換し、`MirBuilder` の `build_loop_statement` を再利用する形に寄せる。 + - これが実現すると、`loop_.rs` 自体を削除しても LoopForm/PHI の意味論は完全に一箇所(LoopBuilder + phi_core)に集約される。 + - 25.1q ではここまでは踏み込まず、「やるならどのフェーズで、どの単位の差分にするか」を設計メモとして残す。 + +## スコープ外(25.1q ではやらないこと) + +- ループ意味論そのものの変更: + - `loop(cond){...}` の評価順序や break/continue の意味論を変えない。 + - Stage‑B / Stage‑1 / 自己ホストルートで既に green な LoopForm/SSA テストの挙動は不変とする。 +- 新しいループ構文・最適化の追加: + - `while` / `for` / range loop など、新構文の導入は別フェーズ(言語拡張側)に任せる。 +- JSON v0 スキーマの変更: + - `StmtV0::Loop` などの JSON 形は既存のまま(schema v0/v1 は維持)。 + +## 他フェーズとの関係 + +- 25.1m(Static Method / LoopForm v2 continue + PHI Fix): + - ここで LoopForm v2 / continue + header PHI は Rust AST 経路でほぼ安定している。 + - 25.1q では、その成果を JSON v0 経路にも構造的に反映し、「LoopForm v2 がどこから使われているか」を明示する役割を担う。 + +- 25.1p(MIR DebugLog 命令): + - DebugLog を使って LoopForm/PHI の ValueId を観測しやすくすることで、25.1q での統一作業時に「AST ルートと JSON ルートの差」を追いやすくする。 + - 25.1q は DebugLog 基盤が整っていることを前提に、小さな JSON v0 → MIR のテストケースで CFG/PHI を比較するフェーズとする。 + +- 25.2(Numeric Microbench / EXE Tuning): + - JSON v0 → MIR → EXE 経路は numeric_core / AotPrep と強く結びついているため、25.1q で LoopForm front を整理しておくと、25.2 でのパフォーマンス解析やバグ調査がやりやすくなる。*** + diff --git a/docs/reference/language/LANGUAGE_REFERENCE_2025.md b/docs/reference/language/LANGUAGE_REFERENCE_2025.md index 500fc3b8..c2621282 100644 --- a/docs/reference/language/LANGUAGE_REFERENCE_2025.md +++ b/docs/reference/language/LANGUAGE_REFERENCE_2025.md @@ -419,6 +419,14 @@ box Cat from Animal interface Playful { ### **3.4 Static Boxパターン** +Static Box は「インスタンスを作らないモジュール箱」です。 + +- `static box` 自体が「この箱のフィールド/メソッドはすべて static(プロセス共有)」であることを表します。 +- `static box` 内のフィールド宣言は、追加で `static` を付けなくても **すべて static フィールド** として扱われます。 + - `box` … フィールドはインスタンスごとの状態。 + - `static box` … フィールドはすべて Box 全体で共有される状態(シングルトン・モジュール相当)。 +- そのため、`MathUtils.PI` のような「定数/共有キャッシュ」や、`Main` のようなアプリケーションエントリの状態を持たせるのに向いています。 + #### **名前空間・ユーティリティ** ```nyash static box MathUtils { diff --git a/lang/src/compiler/builder/ssa/exit_phi/break_finder.hako b/lang/src/compiler/builder/ssa/exit_phi/break_finder.hako index fafe82c6..2abf936d 100644 --- a/lang/src/compiler/builder/ssa/exit_phi/break_finder.hako +++ b/lang/src/compiler/builder/ssa/exit_phi/break_finder.hako @@ -31,7 +31,10 @@ using selfhost.shared.mir.control_form as ControlFormBox local breaks = new ArrayBox() // 1) Find all loops (header blocks with back-edges) - local loops = me._find_loops(json_str, trace) + // Phase 25.1m: static helper呼び出しは明示的な箱名で行う。 + // BreakFinderBox は状態を持たない解析用箱なので、インスタンスではなく + // モジュール的な静的メソッドとして扱う。 + local loops = BreakFinderBox._find_loops(json_str, trace) if trace == 1 { print("[break-finder] found " + loops.length() + " loops") @@ -55,7 +58,7 @@ using selfhost.shared.mir.control_form as ControlFormBox local block_id = "" + body_blocks.get(j) // Check if this block jumps to exit - if me._jumps_to(json_str, block_id, exit_id) == 1 { + if BreakFinderBox._jumps_to(json_str, block_id, exit_id) == 1 { local break_info = new MapBox() break_info.set("block_id", block_id) break_info.set("exit_id", exit_id) @@ -84,8 +87,8 @@ using selfhost.shared.mir.control_form as ControlFormBox // Find all loops in JSON (simple version: look for header/exit pattern) _find_loops(json_str, trace) { - local loops = new ArrayBox() - local s = "" + json_str + local loops = new ArrayBox() + local s = "" + json_str // trace=1 のときは Program(JSON v0) の先頭だけ観測用に出力する if trace == 1 { @@ -98,55 +101,66 @@ using selfhost.shared.mir.control_form as ControlFormBox } // Simple pattern: find "loop_header":NNN, "loop_exit":MMM - // This is a simplified version - just finds explicit loop markers - local i = 0 - local n = s.length() - loop(i < n) { - // Look for "loop_header" - local header_pos = me._indexOf(s, "\"loop_header\":", i) - if header_pos < 0 { break } + // This is a simplified version - just finds explicit loop markers + local i = 0 + local n = s.length() + loop(i < n) { + // Look for "loop_header" + // Phase 25.1m (static helper normalization): + // BreakFinderBox は状態を持たない解析用の static box なので、 + // 内部のヘルパ呼び出しも `me` ではなく明示的な箱名で呼び出す。 + // これにより Rust 側で「static box 上の暗黙 me」を特別扱いしなくて済む。 + local header_pos = BreakFinderBox._indexOf(s, "\"loop_header\":", i) + if header_pos < 0 { break } - // Extract header id - local header_id = me._extract_number(s, header_pos + 14) - if header_id == null { i = header_pos + 14 continue } + // 次の探索位置のデフォルトは header_pos + 14(header キーの終端以降) + local next_i = header_pos + 14 - // Look for corresponding exit (assume it's nearby) - local exit_pos = me._indexOf(s, "\"loop_exit\":", header_pos) - if exit_pos < 0 || exit_pos > header_pos + 500 { - i = header_pos + 14 - continue - } + // Extract header id + local header_id = BreakFinderBox._extract_number(s, header_pos + 14) + if header_id != null { + // Look for corresponding exit (assume it's nearby) + local exit_pos = BreakFinderBox._indexOf(s, "\"loop_exit\":", header_pos) + if exit_pos >= 0 && exit_pos <= header_pos + 500 { + // Extract exit id + local exit_id = BreakFinderBox._extract_number(s, exit_pos + 12) + if exit_id != null { + // Find body blocks (blocks between header and exit) + local body_blocks = BreakFinderBox._find_loop_body(json_str, header_id, exit_id) - // Extract exit id - local exit_id = me._extract_number(s, exit_pos + 12) - if exit_id == null { i = exit_pos + 12 continue } + // ControlFormBox 経由で loop 形を構造化しておく(MVP: 観測専用) + local cf = new ControlFormBox() + cf.from_loop(header_id, exit_id, body_blocks) - // Find body blocks (blocks between header and exit) - local body_blocks = me._find_loop_body(json_str, header_id, exit_id) + local loop_info = new MapBox() + loop_info.set("header", header_id) + loop_info.set("exit", exit_id) + loop_info.set("body", body_blocks) + // 将来の v2 実装で利用できるよう、ControlForm も添えておく + loop_info.set("control", cf) + loops.push(loop_info) - // ControlFormBox 経由で loop 形を構造化しておく(MVP: 観測専用) - local cf = new ControlFormBox() - cf.from_loop(header_id, exit_id, body_blocks) - - local loop_info = new MapBox() - loop_info.set("header", header_id) - loop_info.set("exit", exit_id) - loop_info.set("body", body_blocks) - // 将来の v2 実装で利用できるよう、ControlForm も添えておく - loop_info.set("control", cf) - loops.push(loop_info) - - // trace=1 のときだけ JSON 風にログ出力するよ - if trace == 1 { - local msg = "[loopssa/control] {\"kind\":\"loop\",\"header\":" + header_id + ",\"exit\":" + exit_id + ",\"body_size\":" + body_blocks.length() + "}" - print(msg) + // trace=1 のときだけ JSON 風にログ出力するよ + if trace == 1 { + local msg = "[loopssa/control] {\"kind\":\"loop\",\"header\":" + header_id + ",\"exit\":" + exit_id + ",\"body_size\":" + body_blocks.length() + "}" + print(msg) + } + + // 正常に header/exit を検出できた場合は exit_pos の後ろから再開 + next_i = exit_pos + 12 + } else { + // exit_id が取れなかった場合は、出口キーの終端以降から再開 + next_i = exit_pos + 12 + } + } } - i = exit_pos + 12 - } + // いずれの場合も next_i から次の探索を続ける + i = next_i + } - return loops - } + return loops + } // Find body blocks of a loop (blocks that can reach exit but not header) _find_loop_body(json_str, header_id, exit_id) { @@ -155,8 +169,8 @@ using selfhost.shared.mir.control_form as ControlFormBox // Simple approach: find all blocks with id between header and exit // This is approximate but works for simple cases - local header_num = me._parse_int(header_id) - local exit_num = me._parse_int(exit_id) + local header_num = BreakFinderBox._parse_int(header_id) + local exit_num = BreakFinderBox._parse_int(exit_id) if header_num < 0 || exit_num < 0 { return body } @@ -174,21 +188,21 @@ using selfhost.shared.mir.control_form as ControlFormBox local s = "" + json_str // Find block definition: "id":block_id - local block_start = me._find_block(s, block_id) + local block_start = BreakFinderBox._find_block(s, block_id) if block_start < 0 { return 0 } // Find terminator in this block - local term_pos = me._indexOf(s, "\"terminator\":", block_start) + local term_pos = BreakFinderBox._indexOf(s, "\"terminator\":", block_start) if term_pos < 0 { return 0 } // Check if it's a jump with target matching target_id - local jump_pos = me._indexOf(s, "\"op\":\"jump\"", term_pos) + local jump_pos = BreakFinderBox._indexOf(s, "\"op\":\"jump\"", term_pos) if jump_pos < 0 || jump_pos > term_pos + 200 { return 0 } - local target_pos = me._indexOf(s, "\"target\":", jump_pos) + local target_pos = BreakFinderBox._indexOf(s, "\"target\":", jump_pos) if target_pos < 0 || target_pos > jump_pos + 100 { return 0 } - local actual_target = me._extract_number(s, target_pos + 9) + local actual_target = BreakFinderBox._extract_number(s, target_pos + 9) if actual_target == null { return 0 } if actual_target == target_id { return 1 } @@ -199,7 +213,7 @@ using selfhost.shared.mir.control_form as ControlFormBox _find_block(json_str, block_id) { local s = "" + json_str local pattern = "\"id\":" + block_id - return me._indexOf(s, pattern, 0) + return BreakFinderBox._indexOf(s, pattern, 0) } // Simple indexOf implementation @@ -260,7 +274,7 @@ using selfhost.shared.mir.control_form as ControlFormBox loop(i < n) { local ch = s.substring(i, i + 1) if ch >= "0" && ch <= "9" { - result = result * 10 + (me._char_to_digit(ch)) + result = result * 10 + (BreakFinderBox._char_to_digit(ch)) } else { return -1 } diff --git a/lang/src/compiler/entry/compiler_stageb.hako b/lang/src/compiler/entry/compiler_stageb.hako index 7bcc2310..ae4c897a 100644 --- a/lang/src/compiler/entry/compiler_stageb.hako +++ b/lang/src/compiler/entry/compiler_stageb.hako @@ -300,7 +300,7 @@ static box StageBBodyExtractorBox { { local trc = env.get("HAKO_STAGEB_TRACE") if trc != null && ("" + trc) == "1" { - print("[stageb/body/substr-pre] #" + ("" + iter_count) + " calling s.substring(" + ("" + i) + ", " + ("" + (i+1)) + ")") + print("[stageb/body/substr-pre] #" + ("" + iter_count) + " calling s.substring(" + ("" + i) + ", " + ("" + (i + 1)) + ")") } } local ch = s.substring(i, i + 1) @@ -509,7 +509,7 @@ static box StageBBodyExtractorBox { local bundles = new ArrayBox() // Named bundles (name:src) and requirements local bundle_names = new ArrayBox() - local bundle_srcs = new ArrayBox() + local bundle_srcs = new ArrayBox() local require_mods = new ArrayBox() if args != null { @@ -530,7 +530,7 @@ static box StageBBodyExtractorBox { local pos = -1 { local j = 0 - loop(j < m) { if pair.substring(j, j + 1) == ":" { pos = j break } j = j + 1 } + loop(j < m) { if pair.substring(j, j + 1) == ":" { pos = j break } j = j + 1 } } if pos >= 0 { @@ -593,8 +593,8 @@ static box StageBBodyExtractorBox { { local s2 = merged_prefix if s2 == null { total = 0 } else { - local i=0; local n=(""+s2).length(); local c=1 - loop(i { method (params) { ... } } +// - Output: ArrayBox of MapBox: +// { "name": , "params": ArrayBox, "body_json": , "box": } +static box StageBFuncScannerBox { + scan_all_boxes(source) { + if source == null { return new ArrayBox() } + local defs = new ArrayBox() + local s = "" + source + local n = s.length() + local i = 0 + local in_str = 0 + local esc = 0 + local in_line = 0 + local in_block = 0 + + local in_block = 0 + + print("[funcscan/debug] Calling StageBHelperBox.test_loop") + StageBHelperBox.test_loop(123) + + loop(i < n) { + if i == 0 { + print("[funcscan/debug] Calling StageBHelperBox.test_loop (inner)") + StageBHelperBox.test_loop(123) + } + local ch = s.substring(i, i + 1) + if in_line == 1 { + if ch == "\n" { in_line = 0 } + i = i + 1 + continue + } + if in_block == 1 { + if ch == "*" && i + 1 < n && s.substring(i + 1, i + 2) == "/" { + in_block = 0 + i = i + 2 + continue + } + i = i + 1 + continue + } + if in_str == 1 { + if esc == 1 { esc = 0 i = i + 1 continue } + if ch == "\\" { esc = 1 i = i + 1 continue } + if ch == "\"" { in_str = 0 i = i + 1 continue } + i = i + 1 + continue + } + if ch == "\"" { in_str = 1 i = i + 1 continue } + if ch == "/" && i + 1 < n { + local ch2 = s.substring(i + 1, i + 2) + if ch2 == "/" { in_line = 1 i = i + 2 continue } + if ch2 == "*" { in_block = 1 i = i + 2 continue } + } + + if s.substring(i, i + 3) == "box" { + local before = StageBFuncScannerBox._kw_boundary_before(s, i) + local after = StageBFuncScannerBox._kw_boundary_after(s, i + 3) + print("[funcscan/debug] found 'box' at " + i + " before=" + before + " after=" + after) + } + if s.substring(i, i + 3) == "box" && StageBFuncScannerBox._kw_boundary_before(s, i) == 1 && StageBFuncScannerBox._kw_boundary_after(s, i + 3) == 1 { + print("[funcscan/debug] detected 'box' at " + i) + print("[funcscan/debug] context: '" + s.substring(i, i + 20) + "'") + + local cursor = i + 3 + local cursor_before = cursor + cursor = StageBFuncScannerBox._skip_whitespace(s, cursor) + print("[funcscan/debug] skip_whitespace: " + cursor_before + " -> " + cursor + " char='" + s.substring(cursor, cursor + 1) + "'") + + local name_start = cursor + loop(cursor < n) { + local ch_name = s.substring(cursor, cursor + 1) + if ch_name == " " || ch_name == "\t" || ch_name == "\n" || ch_name == "\r" || ch_name == "{" { break } + cursor = cursor + 1 + } + local box_name = s.substring(name_start, cursor) + print("[funcscan/debug] box_name=" + box_name) + + cursor = StageBFuncScannerBox._skip_whitespace(s, cursor) + if s.substring(cursor, cursor + 1) != "{" { + print("[funcscan/debug] expected '{' but found " + s.substring(cursor, cursor + 1)) + i = i + 3 + continue + } + + local open_idx = cursor + local close_idx = StageBFuncScannerBox._find_matching_brace(s, open_idx) + print("[funcscan/debug] open_idx=" + open_idx + " close_idx=" + close_idx) + + if close_idx < 0 { + i = i + 3 + continue + } + + local body = s.substring(open_idx + 1, close_idx) + print("[funcscan/box] name=" + box_name + " body_len=" + ("" + body.length())) + local skip_main = 0 + if box_name == "Main" { skip_main = 1 } + local include_me = 0 + if box_name != "Main" { include_me = 1 } + local defs_box = StageBFuncScannerBox._scan_methods(body, box_name, skip_main, include_me) + StageBFuncScannerBox._append_defs(defs, defs_box) + i = close_idx + 1 + continue + } + + i = i + 1 + } + return defs + } + + // Dev-only: minimal brace / defs harness for fib-like source. + // Enabled via StageBDriverBox.main when HAKO_STAGEB_FUNCSCAN_TEST=1. + test_fib_scan() { + // Same shape as stageb_fib_program_defs_canary_vm.sh の入力 + local src = "static box TestBox {\n method fib(n) {\n local i = 0\n local a = 0\n local b = 1\n loop(i < n) {\n local t = a + b\n a = b\n b = t\n i = i + 1\n }\n return b\n }\n}\n\nstatic box Main {\n method main(args) {\n local t = new TestBox()\n return t.fib(6)\n }\n}\n" + local n = src.length() + print("[funcscan/test] src_len=" + ("" + n)) + + // Open brace positions matching "box TestBox {" と "box Main {" + local open1 = 19 + local close1 = StageBFuncScannerBox._find_matching_brace(src, open1) + print("[funcscan/test] brace1 open=" + ("" + open1) + " close=" + ("" + close1)) + + local open2 = 209 + local close2 = StageBFuncScannerBox._find_matching_brace(src, open2) + print("[funcscan/test] brace2 open=" + ("" + open2) + " close=" + ("" + close2)) + + // Full scan_all_boxes on the same src to observe defs + local defs = StageBFuncScannerBox.scan_all_boxes(src) + local cnt = defs.length() + print("[funcscan/test] defs_len=" + ("" + cnt)) + local i = 0 + loop(i < cnt) { + local d = defs.get(i) + print("[funcscan/test] def[" + ("" + i) + "] box=" + ("" + d.get("box")) + " name=" + ("" + d.get("name"))) + i = i + 1 + } + return 0 + } + + _scan_methods(source, box_name, skip_main, include_me) { + // print("[funcscan/debug] _scan_methods box=" + box_name + " src_len=" + source.length()) + local methods = new ArrayBox() + local s = "" + source + local n = s.length() + local i = 0 + + loop(i < n) { + // Search for "method " + local k = -1 + { + local pat = "method " + local m = pat.length() + local j = i + loop(j + m <= n) { + if s.substring(j, j + m) == pat { k = j break } + j = j + 1 + } + } + if k < 0 { break } + // print("[funcscan/debug] found 'method ' at " + k) + i = k + 7 + + // Extract method name (alphanumeric until '(') + local name_start = i + local name_end = -1 + { + local j = i + loop(j < n) { + local ch = s.substring(j, j + 1) + if ch == "(" { name_end = j break } + j = j + 1 + } + } + if name_end < 0 { break } + local method_name = s.substring(name_start, name_end) + print("[funcscan/method] box=" + box_name + " name=" + method_name) + print("[funcscan/method] raw_body_head=" + s.substring(lbrace + 1, lbrace + 1 + 40)) + + if skip_main == 1 && box_name == "Main" && method_name == "main" { i = name_end continue } + + // Find '(' after name + local lparen = name_end + + // Find matching ')' for params (skip strings) + local rparen = -1 + { + local j = lparen + 1 + local in_str = 0 + local esc = 0 + loop(j < n) { + local ch = s.substring(j, j + 1) + if in_str == 1 { + if esc == 1 { esc = 0 j = j + 1 continue } + if ch == "\\" { esc = 1 j = j + 1 continue } + if ch == "\"" { in_str = 0 j = j + 1 continue } + j = j + 1 + continue + } + if ch == "\"" { in_str = 1 j = j + 1 continue } + if ch == ")" { rparen = j break } + j = j + 1 + } + } + if rparen < 0 { break } + + // Extract params (minimal: comma-separated names) + local params_str = s.substring(lparen + 1, rparen) + local params = StageBFuncScannerBox._parse_params(params_str) + if include_me == 1 { + local first = "" + if params.length() > 0 { first = "" + params.get(0) } + if first != "me" { + local merged = new ArrayBox() + merged.push("me") + local pi2 = 0 + local pn2 = params.length() + loop(pi2 < pn2) { + merged.push(params.get(pi2)) + pi2 = pi2 + 1 + } + params = merged + } + } + + // Find opening '{' after ')' + local lbrace = -1 + { + local j = rparen + 1 + local in_str = 0 + local esc = 0 + loop(j < n) { + local ch = s.substring(j, j + 1) + if in_str == 1 { + if esc == 1 { esc = 0 j = j + 1 continue } + if ch == "\\" { esc = 1 j = j + 1 continue } + if ch == "\"" { in_str = 0 j = j + 1 continue } + j = j + 1 + continue + } + if ch == "\"" { in_str = 1 j = j + 1 continue } + if ch == "{" { lbrace = j break } + j = j + 1 + } + } + if lbrace < 0 { break } + + // Find matching '}' (balanced) + local rbrace = -1 + { + local depth = 0 + local j = lbrace + local in_str = 0 + local esc = 0 + loop(j < n) { + local ch = s.substring(j, j + 1) + if in_str == 1 { + if esc == 1 { esc = 0 j = j + 1 continue } + if ch == "\\" { esc = 1 j = j + 1 continue } + if ch == "\"" { in_str = 0 j = j + 1 continue } + j = j + 1 + continue + } + if ch == "\"" { in_str = 1 j = j + 1 continue } + if ch == "{" { depth = depth + 1 j = j + 1 continue } + if ch == "}" { + depth = depth - 1 + j = j + 1 + if depth == 0 { rbrace = j - 1 break } + continue + } + j = j + 1 + } + } + if rbrace < 0 { break } + + // Extract method body (inside braces) + local method_body = s.substring(lbrace + 1, rbrace) + + method_body = StageBFuncScannerBox._strip_comments(method_body) + method_body = StageBFuncScannerBox._trim(method_body) + + // Parse method body to JSON (statement list) using block parser + local body_json = null + if method_body.length() > 0 { + local p = new ParserBox() + p.stage3_enable(1) + local block_src = "{" + method_body + "}" + local block_res = p.parse_block2(block_src, 0) + local at = block_res.lastIndexOf("@") + if at >= 0 { + body_json = block_res.substring(0, at) + } + } + + if body_json != null && body_json != "" { + print("[funcscan/body] box=" + box_name + " name=" + method_name + " parsed_len=" + ("" + body_json.length())) + local def = new MapBox() + def.set("name", method_name) + def.set("params", params) + def.set("body_json", body_json) + def.set("box", box_name) + methods.push(def) + } + i = rbrace + } + + return methods + } + + _append_defs(defs, new_defs) { + if new_defs == null { return 0 } + local n = new_defs.length() + local i = 0 + loop(i < n) { + defs.push(new_defs.get(i)) + i = i + 1 + } + return 0 + } + + _skip_whitespace(s, idx) { + local n = s.length() + if idx >= n { return idx } + local ch = s.substring(idx, idx + 1) + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { + return StageBFuncScannerBox._skip_whitespace(s, idx + 1) + } + return idx + } + + _kw_boundary_before(s, idx) { + if idx <= 0 { return 1 } + local ch = s.substring(idx - 1, idx) + if StageBFuncScannerBox._is_ident_char(ch) == 1 { return 0 } + return 1 + } + + _kw_boundary_after(s, idx) { + if idx >= s.length() { return 1 } + local ch = s.substring(idx, idx + 1) + if StageBFuncScannerBox._is_ident_char(ch) == 1 { return 0 } + return 1 + } + + _is_ident_char(ch) { + if ch == null || ch == "" { return 0 } + if ch >= "0" && ch <= "9" { return 1 } + if ch >= "A" && ch <= "Z" { return 1 } + if ch >= "a" && ch <= "z" { return 1 } + if ch == "_" { return 1 } + return 0 + } + + test_nested_loop_int(n) { + print("[funcscan/debug] entering test_nested_loop_int with n=" + n) + local i = 0 + loop(i < 3) { + print("[funcscan/debug] test_nested_loop_int i=" + i) + i = i + 1 + } + print("[funcscan/debug] exiting test_nested_loop_int") + } + + test_nested_loop_str(s) { + print("[funcscan/debug] entering test_nested_loop_str with len=" + s.length()) + local i = 0 + loop(i < 3) { + print("[funcscan/debug] test_nested_loop_str i=" + i) + i = i + 1 + } + print("[funcscan/debug] exiting test_nested_loop_str") + } + + _find_matching_brace(source_str, open_idx) { + local s = "" + source_str + local n = s.length() + local i = open_idx + local depth = 0 + local in_str = 0 + local esc = 0 + local in_line = 0 + local in_block = 0 + + print("[funcscan/debug] _find_matching_brace enter open_idx=" + open_idx + " n=" + n) + + // Use the same balanced-brace scan as FuncScannerBox._find_matching_brace + loop(i < n) { + local ch = s.substring(i, i + 1) + if in_line == 1 { + if ch == "\n" { in_line = 0 } + i = i + 1 + continue + } + if in_block == 1 { + if ch == "*" && i + 1 < n && s.substring(i + 1, i + 2) == "/" { + in_block = 0 + i = i + 2 + continue + } + i = i + 1 + continue + } + if in_str == 1 { + if esc == 1 { esc = 0 i = i + 1 continue } + if ch == "\\" { esc = 1 i = i + 1 continue } + if ch == "\"" { in_str = 0 i = i + 1 continue } + i = i + 1 + continue + } + if ch == "\"" { in_str = 1 i = i + 1 continue } + if ch == "/" && i + 1 < n { + local ch2 = s.substring(i + 1, i + 2) + if ch2 == "/" { in_line = 1 i = i + 2 continue } + if ch2 == "*" { in_block = 1 i = i + 2 continue } + } + if ch == "{" { + depth = depth + 1 + i = i + 1 + continue + } + if ch == "}" { + depth = depth - 1 + i = i + 1 + if depth == 0 { + return i - 1 + } + continue + } + i = i + 1 + } + print("[funcscan/debug] _find_matching_brace exit no-match") + return -1 + } + + _parse_params(params_str) { + local params = new ArrayBox() + local pstr = "" + params_str + local pn = pstr.length() + local pstart = 0 + + loop(pstart < pn) { + // Skip whitespace + loop(pstart < pn) { + local ch = pstr.substring(pstart, pstart + 1) + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { pstart = pstart + 1 } else { break } + } + if pstart >= pn { break } + + // Find next comma or end + local pend = pstart + loop(pend < pn) { + local ch = pstr.substring(pend, pend + 1) + if ch == "," { break } + pend = pend + 1 + } + + // Extract param name (trim) + local pname = pstr.substring(pstart, pend) + pname = StageBFuncScannerBox._trim(pname) + if pname.length() > 0 { params.push(pname) } + pstart = pend + 1 + } + + return params + } + + _strip_comments(source) { + if source == null { return "" } + local s = "" + source + local out = "" + local i = 0 + local n = s.length() + local in_str = 0 + local esc = 0 + local in_line = 0 + local in_block = 0 + loop(i < n) { + local ch = s.substring(i, i + 1) + if in_line == 1 { + if ch == "\n" { in_line = 0 out = out + ch } + i = i + 1 + continue + } + if in_block == 1 { + if ch == "*" && i + 1 < n && s.substring(i + 1, i + 2) == "/" { in_block = 0 i = i + 2 continue } + i = i + 1 + continue + } + if in_str == 1 { + if esc == 1 { out = out + ch esc = 0 i = i + 1 continue } + if ch == "\\" { out = out + ch esc = 1 i = i + 1 continue } + if ch == "\"" { out = out + ch in_str = 0 i = i + 1 continue } + out = out + ch + i = i + 1 + continue + } + if ch == "\"" { out = out + ch in_str = 1 i = i + 1 continue } + if ch == "/" && i + 1 < n { + local ch2 = s.substring(i + 1, i + 2) + if ch2 == "/" { in_line = 1 i = i + 2 continue } + if ch2 == "*" { in_block = 1 i = i + 2 continue } + } + out = out + ch + i = i + 1 + } + return out + } + + _trim(s) { + if s == null { return "" } + local str = "" + s + local n = str.length() + local b = 0 + loop(b < n) { + local ch = str.substring(b, b + 1) + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { b = b + 1 } else { break } + } + local e = n + loop(e > b) { + local ch = str.substring(e - 1, e) + if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { e = e - 1 } else { break } + } + if e > b { return str.substring(b, e) } + return "" + } +} + +static box StageBHelperBox { + test_loop(n) { + print("[funcscan/debug] entering StageBHelperBox.test_loop with n=" + n) + local i = 0 + loop(i < 3) { + print("[funcscan/debug] StageBHelperBox.test_loop i=" + i) + i = i + 1 + } + print("[funcscan/debug] exiting StageBHelperBox.test_loop") + } +} + // Phase 25.1c: Main driver logic static box StageBDriverBox { main(args) { @@ -739,6 +1280,17 @@ static box StageBDriverBox { env.set("HAKO_STAGEB_DRIVER_DEPTH", "1") } + // Dev-only: direct FuncScanner harness + { + local test_flag = env.get("HAKO_STAGEB_FUNCSCAN_TEST") + if test_flag != null && ("" + test_flag) == "1" { + StageBFuncScannerBox.test_fib_scan() + // Clear depth guard before returning + env.set("HAKO_STAGEB_DRIVER_DEPTH", "0") + return 0 + } + } + { local tracer = new StageBTraceBox() tracer.log("StageBDriverBox.main:enter") @@ -773,7 +1325,17 @@ static box StageBDriverBox { // 6) Parse and emit Stage‑1 JSON v0 (Program) // Bridge(JSON v0) が Program v0 を受け取り MIR に lowering するため、ここでは AST(JSON v0) を出力する。 // 既定で MIR 直出力は行わない(重い経路を避け、一行出力を保証)。 - local ast_json = p.parse_program2(body_src) + // Stage‑B/selfhost: body_src は Main.main 内のブロック本文なので、ParserBox の block パーサ + // を直接使って Program(JSON) を構成する(トップレベル parser_loop は使わない)。 + local block_res = p.parse_block2(body_src, 0) + local ast_json = "{\"version\":0,\"kind\":\"Program\",\"body\":[]}" + { + local at = block_res.lastIndexOf("@") + if at >= 0 { + local body_json = block_res.substring(0, at) + ast_json = "{\"version\":0,\"kind\":\"Program\",\"body\":" + body_json + "}" + } + } { // AST(JSON v0) の長さを軽く観測 local la = 0 @@ -816,14 +1378,35 @@ static box StageBDriverBox { // 明示的に "0" のときだけ OFF 扱い。それ以外(null/1/true/on)は ON。 if func_scan_env != null && ("" + func_scan_env) == "0" { func_scan_on = 0 } if func_scan_on == 1 { - // Use FuncScannerBox to extract method definitions from all boxes - local methods = FuncScannerBox.scan_all_boxes(src) + // Use StageBFuncScannerBox (Stage‑B local implementation) to extract method definitions from all boxes + // Dev trace: observe source head used for FuncScanner (Stage‑B fib など) + { + local tracer = new StageBTraceBox() + local head = "" + src + local max_len = 80 + if head.length() > max_len { head = head.substring(0, max_len) } + tracer.log("StageBDriverBox.main:func_scan src_head=" + head) + } + + local methods = StageBFuncScannerBox.scan_all_boxes(src) { local cnt = 0 if methods != null { cnt = methods.length() } local tracer = new StageBTraceBox() tracer.log("StageBDriverBox.main:func_scan methods=" + ("" + cnt)) + // Optional detailed trace when HAKO_STAGEB_TRACE=1: + // dump each def entry (box/name) to確認 how FuncScannerBox saw the source. + if cnt > 0 { + local mi = 0 + loop(mi < cnt) { + local def = methods.get(mi) + local mbox = "" + def.get("box") + local mname = "" + def.get("name") + tracer.log("StageBDriverBox.main:func_scan def box=" + mbox + " name=" + mname) + mi = mi + 1 + } + } } // Build defs JSON array diff --git a/lang/src/compiler/entry/func_scanner.hako b/lang/src/compiler/entry/func_scanner.hako index 2bd50249..2732ccea 100644 --- a/lang/src/compiler/entry/func_scanner.hako +++ b/lang/src/compiler/entry/func_scanner.hako @@ -4,7 +4,7 @@ // Scope: method (params) { ... } outside of main (same box Main) // Output: [{"name":"","params":[...],"body_json":"","box":"Main"}] -using lang.compiler.parser.box as ParserBox +using "lang.compiler.parser.box" as ParserBox static box FuncScannerBox { // Scan source for method definitions (excluding Main.main) @@ -19,6 +19,8 @@ static box FuncScannerBox { local defs = new ArrayBox() local s = "" + source local n = s.length() + // DEBUG: 一時的に常時トレースを有効化(Stage‑B fib 用の挙動観測) + print("[funcscan/scan_all_boxes] source_len=" + ("" + n)) local i = 0 local in_str = 0 local esc = 0 @@ -55,6 +57,13 @@ static box FuncScannerBox { if ch2 == "*" { in_block = 1 i = i + 2 continue } } + // DEBUG: "box" キーワード候補をチェック(Stage‑B fib 用) + if i + 3 <= n { + local candidate = s.substring(i, i + 3) + if candidate == "box" { + print("[funcscan/box_candidate] i=" + ("" + i) + " candidate=" + candidate) + } + } if s.substring(i, i + 3) == "box" && me._kw_boundary_before(s, i) == 1 && me._kw_boundary_after(s, i + 3) == 1 { local cursor = i + 3 cursor = me._skip_whitespace(s, cursor) diff --git a/lang/src/compiler/parser/expr/parser_expr_box.hako b/lang/src/compiler/parser/expr/parser_expr_box.hako index b44e6288..96312c97 100644 --- a/lang/src/compiler/parser/expr/parser_expr_box.hako +++ b/lang/src/compiler/parser/expr/parser_expr_box.hako @@ -88,7 +88,7 @@ static box ParserExprBox { // Parenthesized if ch == "(" { - local inner = me.parse_expr2(src, j + 1, ctx) + local inner = ParserExprBox.parse_expr2(src, j + 1, ctx) local k = ctx.gpos_get() k = ctx.skip_ws(src, k) if src.substring(k, k+1) == ")" { k = k + 1 } @@ -98,7 +98,7 @@ static box ParserExprBox { // String literal if ch == "\"" { - return me.parse_string2(src, j, ctx) + return ParserExprBox.parse_string2(src, j, ctx) } // Map literal: delegate to ParserLiteralBox @@ -120,7 +120,7 @@ static box ParserExprBox { local k = ctx.to_int(idp.substring(at+1, idp.length())) k = ctx.skip_ws(src, k) if src.substring(k, k+1) == "(" { k = k + 1 } - local args_and_pos = me.parse_args2(src, k, ctx) + local args_and_pos = ParserExprBox.parse_args2(src, k, ctx) local at2 = args_and_pos.lastIndexOf("@") local args_json = args_and_pos.substring(0, at2) k = ctx.to_int(args_and_pos.substring(at2+1, args_and_pos.length())) @@ -143,7 +143,7 @@ static box ParserExprBox { k = ctx.to_int(midp.substring(at3+1, midp.length())) k = ctx.skip_ws(src, k) if src.substring(k, k+1) == "(" { k = k + 1 } - local args2 = me.parse_args2(src, k, ctx) + local args2 = ParserExprBox.parse_args2(src, k, ctx) local at4 = args2.lastIndexOf("@") local args_json2 = args2.substring(0, at4) k = ctx.to_int(args2.substring(at4+1, args2.length())) @@ -171,9 +171,9 @@ static box ParserExprBox { k = ctx.skip_ws(src, k) local tch = src.substring(k, k+1) - if tch == "(" { + if tch == "(" { k = k + 1 - local args_and_pos = me.parse_args2(src, k, ctx) + local args_and_pos = ParserExprBox.parse_args2(src, k, ctx) local at2 = args_and_pos.lastIndexOf("@") local args_json = args_and_pos.substring(0, at2) k = ctx.to_int(args_and_pos.substring(at2+1, args_and_pos.length())) @@ -190,7 +190,7 @@ static box ParserExprBox { k = ctx.to_int(midp.substring(at3+1, midp.length())) k = ctx.skip_ws(src, k) if src.substring(k, k+1) == "(" { k = k + 1 } - local args2 = me.parse_args2(src, k, ctx) + local args2 = ParserExprBox.parse_args2(src, k, ctx) local at4 = args2.lastIndexOf("@") local args_json2 = args2.substring(0, at4) k = ctx.to_int(args2.substring(at4+1, args2.length())) @@ -208,23 +208,23 @@ static box ParserExprBox { } // Fallback: number - return me.parse_number2(src, j, ctx) + return ParserExprBox.parse_number2(src, j, ctx) } parse_unary2(src, i, ctx) { local j = ctx.skip_ws(src, i) if src.substring(j, j+1) == "-" { - local rhs = me.parse_factor2(src, j + 1, ctx) + local rhs = ParserExprBox.parse_factor2(src, j + 1, ctx) j = ctx.gpos_get() local zero = "{\"type\":\"Int\",\"value\":0}" ctx.gpos_set(j) return "{\"type\":\"Binary\",\"op\":\"-\",\"lhs\":" + zero + ",\"rhs\":" + rhs + "}" } - return me.parse_factor2(src, j, ctx) + return ParserExprBox.parse_factor2(src, j, ctx) } parse_term2(src, i, ctx) { - local lhs = me.parse_unary2(src, i, ctx) + local lhs = ParserExprBox.parse_unary2(src, i, ctx) local j = ctx.gpos_get() local cont = 1 @@ -237,7 +237,7 @@ static box ParserExprBox { if op != "*" && op != "/" { cont = 0 } else { - local rhs = me.parse_unary2(src, j+1, ctx) + local rhs = ParserExprBox.parse_unary2(src, j+1, ctx) j = ctx.gpos_get() lhs = "{\"type\":\"Binary\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" } @@ -249,7 +249,7 @@ static box ParserExprBox { } parse_sum2(src, i, ctx) { - local lhs = me.parse_term2(src, i, ctx) + local lhs = ParserExprBox.parse_term2(src, i, ctx) local j = ctx.gpos_get() local cont = 1 @@ -262,7 +262,7 @@ static box ParserExprBox { if op != "+" && op != "-" { cont = 0 } else { - local rhs = me.parse_term2(src, j+1, ctx) + local rhs = ParserExprBox.parse_term2(src, j+1, ctx) j = ctx.gpos_get() lhs = "{\"type\":\"Binary\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" } @@ -274,7 +274,7 @@ static box ParserExprBox { } parse_compare2(src, i, ctx) { - local lhs = me.parse_sum2(src, i, ctx) + local lhs = ParserExprBox.parse_sum2(src, i, ctx) local j = ctx.gpos_get() j = ctx.skip_ws(src, j) local two = src.substring(j, j+2) @@ -296,14 +296,14 @@ static box ParserExprBox { return lhs } - local rhs = me.parse_sum2(src, j, ctx) + local rhs = ParserExprBox.parse_sum2(src, j, ctx) j = ctx.gpos_get() ctx.gpos_set(j) return "{\"type\":\"Compare\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" } parse_expr2(src, i, ctx) { - local lhs = me.parse_compare2(src, i, ctx) + local lhs = ParserExprBox.parse_compare2(src, i, ctx) local j = ctx.gpos_get() local cont = 1 @@ -313,7 +313,7 @@ static box ParserExprBox { if two != "&&" && two != "||" { cont = 0 } else { - local rhs = me.parse_compare2(src, j+2, ctx) + local rhs = ParserExprBox.parse_compare2(src, j+2, ctx) j = ctx.gpos_get() lhs = "{\"type\":\"Logical\",\"op\":\"" + two + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" } @@ -323,12 +323,12 @@ static box ParserExprBox { if src.substring(j, j+1) == "?" { j = j + 1 j = ctx.skip_ws(src, j) - local then_expr = me.parse_expr2(src, j, ctx) + local then_expr = ParserExprBox.parse_expr2(src, j, ctx) j = ctx.gpos_get() j = ctx.skip_ws(src, j) if src.substring(j, j+1) == ":" { j = j + 1 } j = ctx.skip_ws(src, j) - local else_expr = me.parse_expr2(src, j, ctx) + local else_expr = ParserExprBox.parse_expr2(src, j, ctx) j = ctx.gpos_get() if else_expr.length() == 0 { else_expr = "{\"type\":\"Int\",\"value\":0}" } ctx.gpos_set(j) @@ -350,7 +350,7 @@ static box ParserExprBox { } // first argument - local e = me.parse_expr2(src, j, ctx) + local e = ParserExprBox.parse_expr2(src, j, ctx) j = ctx.gpos_get() out = out + e @@ -367,7 +367,7 @@ static box ParserExprBox { if j < n && src.substring(j, j+1) == "," { j = j + 1 j = ctx.skip_ws(src, j) - e = me.parse_expr2(src, j, ctx) + e = ParserExprBox.parse_expr2(src, j, ctx) j = ctx.gpos_get() out = out + "," + e } else { diff --git a/lang/src/compiler/parser/parser_box.hako b/lang/src/compiler/parser/parser_box.hako index 110b1724..f4bbeeed 100644 --- a/lang/src/compiler/parser/parser_box.hako +++ b/lang/src/compiler/parser/parser_box.hako @@ -361,6 +361,16 @@ box ParserBox { } } print("[parser/trace:program2] pos=" + ("" + i) + " kind=" + kind + " stage3=" + ("" + me.stage3)) + // Dev-only: small preview of remaining source to debug top-level progress + { + local head = "" + if i < n { + local end = i + 40 + if end > n { end = n } + head = src.substring(i, end) + } + print("[parser/trace:program2] head=\"" + head + "\"") + } } // Inline skip_ws instead of calling me.skip_ws(src, i) if i < n { @@ -378,8 +388,14 @@ box ParserBox { cont_prog = 0 } else { local start_i = i + if trace == 1 { + print("[parser/trace:program2] before_stmt i=" + ("" + i)) + } local s = me.parse_stmt2(src, i) i = me.gpos_get() + if trace == 1 { + print("[parser/trace:program2] after_stmt i=" + ("" + i) + " stmt_len=" + ("" + s.length())) + } // Progress guard if i <= start_i { diff --git a/lang/src/compiler/parser/stmt/parser_stmt_box.hako b/lang/src/compiler/parser/stmt/parser_stmt_box.hako index 3a3df5f1..9fe39b48 100644 --- a/lang/src/compiler/parser/stmt/parser_stmt_box.hako +++ b/lang/src/compiler/parser/stmt/parser_stmt_box.hako @@ -21,10 +21,24 @@ static box ParserStmtBox { env.set("HAKO_STAGEB_STMT_DEPTH", "1") } local j = ctx.skip_ws(src, i) + { + local tr = env.get("NYASH_PARSER_TRACE") + if tr != null && ("" + tr) == "1" { + local ch0 = "" + if j < src.length() { ch0 = src.substring(j, j+1) } + print("[parser/stmt] enter j=" + ("" + j) + " ch=\"" + ch0 + "\"") + } + } local stmt_start = j // annotation: @extern_c("c_symbol","Func/Arity"); if ctx.starts_with(src, j, "@extern_c") == 1 { + { + local tr = env.get("NYASH_PARSER_TRACE") + if tr != null && ("" + tr) == "1" { + print("[parser/stmt] kind=extern_c j=" + ("" + j)) + } + } j = j + 9 // len("@extern_c") j = ctx.skip_ws(src, j) if j < src.length() && src.substring(j, j+1) == "(" { j = j + 1 } @@ -60,6 +74,12 @@ static box ParserStmtBox { // using statement if ctx.starts_with_kw(src, j, "using") == 1 { + { + local tr = env.get("NYASH_PARSER_TRACE") + if tr != null && ("" + tr) == "1" { + print("[parser/stmt] kind=using j=" + ("" + j)) + } + } local out_using = me.parse_using(src, j, stmt_start, ctx) // Reset recursion guard before returning env.set("HAKO_STAGEB_STMT_DEPTH", "0") @@ -68,6 +88,12 @@ static box ParserStmtBox { // assignment: IDENT '=' expr if j < src.length() && ctx.is_alpha(src.substring(j, j+1)) { + { + local tr = env.get("NYASH_PARSER_TRACE") + if tr != null && ("" + tr) == "1" { + print("[parser/stmt] kind=assign-or-local j=" + ("" + j)) + } + } local idp0 = ctx.read_ident2(src, j) local at0 = idp0.lastIndexOf("@") if at0 > 0 { @@ -105,6 +131,12 @@ static box ParserStmtBox { // return statement if ctx.starts_with_kw(src, j, "return") == 1 { + { + local tr = env.get("NYASH_PARSER_TRACE") + if tr != null && ("" + tr) == "1" { + print("[parser/stmt] kind=return j=" + ("" + j)) + } + } j = j + 6 j = ctx.skip_ws(src, j) local default_ret = "{\"type\":\"Int\",\"value\":0}" @@ -142,6 +174,12 @@ static box ParserStmtBox { } if is_local_kw == 1 { + { + local tr = env.get("NYASH_PARSER_TRACE") + if tr != null && ("" + tr) == "1" { + print("[parser/stmt] kind=local j=" + ("" + j)) + } + } j = j + kw_len j = ctx.skip_ws(src, j) local idp = ctx.read_ident2(src, j) @@ -173,6 +211,12 @@ static box ParserStmtBox { // Delegate to specialized boxes if ctx.starts_with_kw(src, j, "if") == 1 { + { + local tr = env.get("NYASH_PARSER_TRACE") + if tr != null && ("" + tr) == "1" { + print("[parser/stmt] kind=if j=" + ("" + j)) + } + } local out_if = ParserControlBox.parse_if(src, j, stmt_start, ctx) // Reset recursion guard before returning env.set("HAKO_STAGEB_STMT_DEPTH", "0") @@ -180,6 +224,12 @@ static box ParserStmtBox { } if ctx.starts_with_kw(src, j, "loop") == 1 { + { + local tr = env.get("NYASH_PARSER_TRACE") + if tr != null && ("" + tr) == "1" { + print("[parser/stmt] kind=loop j=" + ("" + j)) + } + } local out_loop = ParserControlBox.parse_loop(src, j, stmt_start, ctx) // Reset recursion guard before returning env.set("HAKO_STAGEB_STMT_DEPTH", "0") @@ -187,6 +237,12 @@ static box ParserStmtBox { } if ctx.starts_with_kw(src, j, "break") == 1 { + { + local tr = env.get("NYASH_PARSER_TRACE") + if tr != null && ("" + tr) == "1" { + print("[parser/stmt] kind=break j=" + ("" + j)) + } + } local out_break = ParserControlBox.parse_break(src, j, stmt_start, ctx) // Reset recursion guard before returning env.set("HAKO_STAGEB_STMT_DEPTH", "0") @@ -194,6 +250,12 @@ static box ParserStmtBox { } if ctx.starts_with_kw(src, j, "continue") == 1 { + { + local tr = env.get("NYASH_PARSER_TRACE") + if tr != null && ("" + tr) == "1" { + print("[parser/stmt] kind=continue j=" + ("" + j)) + } + } local out_cont = ParserControlBox.parse_continue(src, j, stmt_start, ctx) // Reset recursion guard before returning env.set("HAKO_STAGEB_STMT_DEPTH", "0") @@ -201,6 +263,12 @@ static box ParserStmtBox { } if ctx.starts_with_kw(src, j, "throw") == 1 { + { + local tr = env.get("NYASH_PARSER_TRACE") + if tr != null && ("" + tr) == "1" { + print("[parser/stmt] kind=throw j=" + ("" + j)) + } + } local out_throw = ParserExceptionBox.parse_throw(src, j, stmt_start, ctx) // Reset recursion guard before returning env.set("HAKO_STAGEB_STMT_DEPTH", "0") @@ -208,6 +276,12 @@ static box ParserStmtBox { } if ctx.starts_with_kw(src, j, "try") == 1 { + { + local tr = env.get("NYASH_PARSER_TRACE") + if tr != null && ("" + tr) == "1" { + print("[parser/stmt] kind=try j=" + ("" + j)) + } + } local out_try = ParserExceptionBox.parse_try(src, j, stmt_start, ctx) // Reset recursion guard before returning env.set("HAKO_STAGEB_STMT_DEPTH", "0") @@ -215,6 +289,12 @@ static box ParserStmtBox { } // Fallback: expression or unknown token + { + local tr = env.get("NYASH_PARSER_TRACE") + if tr != null && ("" + tr) == "1" { + print("[parser/stmt] kind=expr j=" + ("" + j)) + } + } local expr_start = j local e = ctx.parse_expr2(src, j) j = ctx.gpos_get() diff --git a/lang/src/shared/common/string_helpers.hako b/lang/src/shared/common/string_helpers.hako index 95432877..18b0aade 100644 --- a/lang/src/shared/common/string_helpers.hako +++ b/lang/src/shared/common/string_helpers.hako @@ -97,6 +97,14 @@ static box StringHelpers { starts_with(src, i, pat) { local n = src.length() local m = pat.length() + { + // Dev-only trace for Stage‑B / parser調査用 + local dbg = env.get("HAKO_STAGEB_DEBUG") + if dbg != null && ("" + dbg) == "1" { + print("[string_helpers/starts_with] src=\"" + src + "\"") + print("[string_helpers/starts_with] i=" + ("" + i) + " m=" + ("" + m) + " n=" + ("" + n)) + } + } if i + m > n { return 0 } local k = 0 loop(k < m) { @@ -108,7 +116,15 @@ static box StringHelpers { // Keyword match with word boundary (next char not [A-Za-z0-9_]) starts_with_kw(src, i, kw) { - if me.starts_with(src, i, kw) == 0 { return 0 } + { + local dbg = env.get("HAKO_STAGEB_DEBUG") + if dbg != null && ("" + dbg) == "1" { + print("[string_helpers/starts_with_kw] src=\"" + src + "\" i=" + ("" + i) + " kw=\"" + kw + "\"") + } + } + // 同一箱内ヘルパーとして、receiver 経由ではなく + // 明示的な箱名付きの静的呼び出しにする(引数順のずれ防止)。 + if StringHelpers.starts_with(src, i, kw) == 0 { return 0 } local n = src.length() local j = i + kw.length() if j >= n { return 1 } diff --git a/src/backend/mir_interpreter/handlers/boxes_plugin.rs b/src/backend/mir_interpreter/handlers/boxes_plugin.rs index 896eab62..af23c3a5 100644 --- a/src/backend/mir_interpreter/handlers/boxes_plugin.rs +++ b/src/backend/mir_interpreter/handlers/boxes_plugin.rs @@ -55,16 +55,17 @@ pub(super) fn invoke_plugin_box( &format!("{:?}", e) )), } - } else if let Some(string_box) = recv_box - .as_any() - .downcast_ref::() - { - // Handle builtin StringBox methods + } else if recv_box.type_name() == "StringBox" { + // Handle builtin StringBox methods via to_string_box() so it works + // for both basic and plugin-backed StringBox implementations. + let s_box = recv_box.to_string_box(); + let s = s_box.value; match method { "lastIndexOf" => { if let Some(arg_id) = args.get(0) { let needle = this.reg_load(*arg_id)?.to_string(); - let result_box = string_box.lastIndexOf(&needle); + let helper = crate::boxes::string_box::StringBox::new(s); + let result_box = helper.lastIndexOf(&needle); this.write_from_box(dst, result_box); Ok(()) } else { @@ -74,13 +75,40 @@ pub(super) fn invoke_plugin_box( "indexOf" | "find" => { if let Some(arg_id) = args.get(0) { let needle = this.reg_load(*arg_id)?.to_string(); - let result_box = string_box.find(&needle); + let helper = crate::boxes::string_box::StringBox::new(s); + let result_box = helper.find(&needle); this.write_from_box(dst, result_box); Ok(()) } else { Err(this.err_invalid("indexOf/find requires 1 argument")) } } + // Phase 25.1m: minimal builtin support for StringBox.is_space(ch) + "is_space" => { + if let Some(arg_id) = args.get(0) { + let ch = this.reg_load(*arg_id)?.to_string(); + let is_ws = ch == " " || ch == "\t" || ch == "\n" || ch == "\r"; + this.write_result(dst, VMValue::Bool(is_ws)); + Ok(()) + } else { + Err(this.err_invalid("is_space requires 1 argument")) + } + } + // Phase 25.1m: minimal builtin support for StringBox.is_alpha(ch) + "is_alpha" => { + if let Some(arg_id) = args.get(0) { + let ch = this.reg_load(*arg_id)?.to_string(); + let c = ch.chars().next().unwrap_or('\0'); + let is_alpha = + ('A'..='Z').contains(&c) || + ('a'..='z').contains(&c) || + c == '_'; + this.write_result(dst, VMValue::Bool(is_alpha)); + Ok(()) + } else { + Err(this.err_invalid("is_alpha requires 1 argument")) + } + } _ => Err(this.err_method_not_found("StringBox", method)), } } else { diff --git a/src/backend/mir_interpreter/handlers/calls/method.rs b/src/backend/mir_interpreter/handlers/calls/method.rs index 15bf623e..d5b4d263 100644 --- a/src/backend/mir_interpreter/handlers/calls/method.rs +++ b/src/backend/mir_interpreter/handlers/calls/method.rs @@ -178,16 +178,17 @@ impl MirInterpreter { _ => Err(self.err_method_not_found("String", method)), }, VMValue::BoxRef(box_ref) => { - // Try builtin StringBox first - if let Some(string_box) = box_ref - .as_any() - .downcast_ref::() - { + // StringBox builtin handling based on type_name; works for both basic and plugin-backed StringBox. + if box_ref.type_name() == "StringBox" { + let s_box = box_ref.to_string_box(); + let s = s_box.value; match method { "lastIndexOf" => { if let Some(arg_id) = args.get(0) { let needle = self.reg_load(*arg_id)?.to_string(); - let result_box = string_box.lastIndexOf(&needle); + // Reuse advanced StringBox helper for semantics (NYASH_STR_CP, etc.). + let helper = crate::boxes::string_box::StringBox::new(s); + let result_box = helper.lastIndexOf(&needle); Ok(VMValue::from_nyash_box(result_box)) } else { Err(self.err_invalid("lastIndexOf requires 1 argument")) @@ -196,12 +197,39 @@ impl MirInterpreter { "indexOf" | "find" => { if let Some(arg_id) = args.get(0) { let needle = self.reg_load(*arg_id)?.to_string(); - let result_box = string_box.find(&needle); + let helper = crate::boxes::string_box::StringBox::new(s); + let result_box = helper.find(&needle); Ok(VMValue::from_nyash_box(result_box)) } else { Err(self.err_invalid("indexOf/find requires 1 argument")) } } + // Phase 25.1m: minimal builtin support for StringBox.is_space(ch) + // to match nyash-string-plugin semantics and unblock parser/Stage‑B. + "is_space" => { + if let Some(arg_id) = args.get(0) { + let ch = self.reg_load(*arg_id)?.to_string(); + let is_ws = + ch == " " || ch == "\t" || ch == "\n" || ch == "\r"; + Ok(VMValue::Bool(is_ws)) + } else { + Err(self.err_invalid("is_space requires 1 argument")) + } + } + // Phase 25.1m: minimal builtin support for StringBox.is_alpha(ch) + "is_alpha" => { + if let Some(arg_id) = args.get(0) { + let ch = self.reg_load(*arg_id)?.to_string(); + let c = ch.chars().next().unwrap_or('\0'); + let is_alpha = + ('A'..='Z').contains(&c) || + ('a'..='z').contains(&c) || + c == '_'; + Ok(VMValue::Bool(is_alpha)) + } else { + Err(self.err_invalid("is_alpha requires 1 argument")) + } + } _ => Err(self.err_method_not_found("StringBox", method)), } } else if let Some(p) = box_ref diff --git a/src/backend/mir_interpreter/handlers/mod.rs b/src/backend/mir_interpreter/handlers/mod.rs index ea2e8099..1472ee53 100644 --- a/src/backend/mir_interpreter/handlers/mod.rs +++ b/src/backend/mir_interpreter/handlers/mod.rs @@ -88,6 +88,20 @@ impl MirInterpreter { MirInstruction::Debug { message, value } => { self.handle_debug(message, *value)?; } + MirInstruction::DebugLog { message, values } => { + // Dev-only: MIR-level debug logging (no new values defined). + if std::env::var("NYASH_MIR_DEBUG_LOG") + .ok() + .as_deref() == Some("1") + { + eprint!("[MIR-LOG] {}:", message); + for vid in values { + let v = self.reg_load(*vid).unwrap_or(VMValue::Void); + eprint!(" %{}={:?}", vid.0, v); + } + eprintln!(); + } + } MirInstruction::Print { value, .. } => self.handle_print(*value)?, MirInstruction::BarrierRead { .. } | MirInstruction::BarrierWrite { .. } diff --git a/src/mir/instruction.rs b/src/mir/instruction.rs index ed5f4155..89052bce 100644 --- a/src/mir/instruction.rs +++ b/src/mir/instruction.rs @@ -196,6 +196,14 @@ pub enum MirInstruction { /// `debug %value "message"` Debug { value: ValueId, message: String }, + /// Dev-only debug logging (MIR-level) + /// `debug_log "message" %v1 %v2 ...` + /// Executes only when NYASH_MIR_DEBUG_LOG=1; otherwise behaves as no-op. + DebugLog { + message: String, + values: Vec, + }, + /// Print instruction for console output /// `print %value` Print { value: ValueId, effects: EffectMask }, diff --git a/src/mir/instruction/display.rs b/src/mir/instruction/display.rs index f51d0689..e136447f 100644 --- a/src/mir/instruction/display.rs +++ b/src/mir/instruction/display.rs @@ -131,7 +131,14 @@ impl fmt::Display for MirInstruction { ) } } + MirInstruction::DebugLog { message, values } => { + write!(f, "debug_log \"{}\"", message)?; + for v in values { + write!(f, " {}", v)?; + } + Ok(()) + } _ => write!(f, "{:?}", self), // Fallback for other instructions } } -} \ No newline at end of file +} diff --git a/src/mir/instruction/methods.rs b/src/mir/instruction/methods.rs index c2a868aa..fb1ecff1 100644 --- a/src/mir/instruction/methods.rs +++ b/src/mir/instruction/methods.rs @@ -48,7 +48,9 @@ impl MirInstruction { MirInstruction::NewBox { .. } => EffectMask::PURE.add(Effect::Alloc), // Debug has debug effect - MirInstruction::Debug { .. } => EffectMask::PURE.add(Effect::Debug), + MirInstruction::Debug { .. } | MirInstruction::DebugLog { .. } => { + EffectMask::PURE.add(Effect::Debug) + } // Print has external write effect MirInstruction::Print { effects, .. } => *effects, @@ -126,6 +128,7 @@ impl MirInstruction { | MirInstruction::Return { .. } | MirInstruction::ArraySet { .. } | MirInstruction::Debug { .. } + | MirInstruction::DebugLog { .. } | MirInstruction::Print { .. } | MirInstruction::Throw { .. } | MirInstruction::RefSet { .. } @@ -176,6 +179,7 @@ impl MirInstruction { index, value, } => vec![*array, *index, *value], + MirInstruction::DebugLog { values, .. } => values.clone(), MirInstruction::Branch { condition, .. } => vec![*condition], @@ -263,4 +267,4 @@ impl ConstValue { } } */ -} \ No newline at end of file +} diff --git a/src/mir/loop_builder.rs b/src/mir/loop_builder.rs index 83c8b997..22ab4936 100644 --- a/src/mir/loop_builder.rs +++ b/src/mir/loop_builder.rs @@ -340,18 +340,124 @@ impl<'a> LoopBuilder<'a> { self.set_current_block(latch_id)?; // Update variable map with body end values for sealing - for (name, value) in body_end_vars { - self.update_variable(name, value); + for (name, value) in &body_end_vars { + self.update_variable(name.clone(), *value); } self.emit_jump(header_id)?; // 📦 Hotfix 6: Add CFG predecessor for header from latch (same as legacy version) crate::mir::builder::loops::add_predecessor(self.parent_builder, header_id, latch_id)?; + // Phase 25.1c/k: body-local 変数の PHI 生成 + // BreakFinderBox / FuncScannerBox 等で、loop body 内で新規宣言された local 変数が + // loop header に戻った時に undefined になる問題を修正 + let trace_loop_phi = std::env::var("HAKO_LOOP_PHI_TRACE").ok().as_deref() == Some("1"); + if trace_loop_phi { + eprintln!("[loop-phi/body-local] Checking for body-local variables"); + eprintln!(" current_vars.len()={}, body_end_vars.len()={}", current_vars.len(), body_end_vars.len()); + } + + // Detect body-local variables (variables declared in body, not in preheader) + let body_local_vars: Vec<(String, ValueId)> = body_end_vars + .iter() + .filter(|(name, _value)| !current_vars.contains_key(name.as_str())) + .map(|(name, value)| (name.clone(), *value)) + .collect(); + + if !body_local_vars.is_empty() { + if trace_loop_phi { + eprintln!("[loop-phi/body-local] Found {} body-local variables", body_local_vars.len()); + } + + // Add PHI nodes for body-local variables at header + for (var_name, _latch_value) in &body_local_vars { + if trace_loop_phi { + eprintln!("[loop-phi/body-local] Adding PHI for body-local var: {}", var_name); + } + + let phi_id = self.new_value(); + + // Insert empty PHI at header (will be sealed by seal_phis or later) + if let Some(ref mut func) = self.parent_builder.current_function { + if let Some(header_block) = func.blocks.get_mut(&header_id) { + // Find position after existing PHIs + let phi_count = header_block.phi_instructions().count(); + header_block.instructions.insert( + phi_count, + MirInstruction::Phi { + dst: phi_id, + inputs: vec![], // Empty PHI, will be filled by seal_phis + }, + ); + } + } + + // Rebind variable to PHI + self.update_variable(var_name.clone(), phi_id); + } + + if trace_loop_phi { + eprintln!("[loop-phi/body-local] Added {} body-local PHIs", body_local_vars.len()); + } + } + // Pass 4: Seal PHIs with latch + continue values let continue_snaps = self.continue_snapshots.clone(); loopform.seal_phis(self, actual_latch_id, &continue_snaps)?; + // Phase 25.1c/k: seal body-local PHIs (complete the inputs) + if !body_local_vars.is_empty() { + if trace_loop_phi { + eprintln!("[loop-phi/body-local] Sealing {} body-local PHIs", body_local_vars.len()); + } + + for (var_name, _) in &body_local_vars { + // Get the PHI we created earlier from current variable map + let current_map = self.get_current_variable_map(); + let phi_id = current_map.get(var_name).copied() + .ok_or_else(|| format!("Body-local variable '{}' not found in variable map", var_name))?; + + // Build inputs: no preheader input (variable doesn't exist there), + // add latch input and continue inputs + let mut inputs: Vec<(BasicBlockId, ValueId)> = vec![]; + + // Add latch input + let latch_value = self.get_variable_at_block(var_name, actual_latch_id) + .unwrap_or(phi_id); // Fallback to phi_id if not found + inputs.push((actual_latch_id, latch_value)); + + // Add continue inputs + for (cid, snapshot) in &continue_snaps { + if let Some(&value) = snapshot.get(var_name) { + inputs.push((*cid, value)); + } + } + + // Update PHI inputs + if let Some(ref mut func) = self.parent_builder.current_function { + if let Some(header_block) = func.blocks.get_mut(&header_id) { + // Find the PHI instruction for this variable + for instr in &mut header_block.instructions { + if let MirInstruction::Phi { dst, inputs: phi_inputs } = instr { + if *dst == phi_id { + *phi_inputs = inputs.clone(); + if trace_loop_phi { + eprintln!("[loop-phi/body-local] Sealed '{}' phi={:?} inputs={:?}", + var_name, phi_id, inputs); + } + break; + } + } + } + } + } + } + + if trace_loop_phi { + eprintln!("[loop-phi/body-local] Sealed {} body-local PHIs", body_local_vars.len()); + } + } + // Exit block self.set_current_block(exit_id)?; diff --git a/src/mir/printer_helpers.rs b/src/mir/printer_helpers.rs index 7b424d39..19309cc9 100644 --- a/src/mir/printer_helpers.rs +++ b/src/mir/printer_helpers.rs @@ -260,6 +260,15 @@ pub fn format_instruction( ) } + MirInstruction::DebugLog { message, values } => { + let mut s = format!("debug_log \"{}\"", message); + for v in values { + s.push(' '); + s.push_str(&format!("{}", v)); + } + s + } + MirInstruction::Cast { dst, value, target_type } => { format!( "{} cast {} to {:?}", diff --git a/src/parser/declarations/box_def/mod.rs b/src/parser/declarations/box_def/mod.rs index b5393704..0e494fa0 100644 --- a/src/parser/declarations/box_def/mod.rs +++ b/src/parser/declarations/box_def/mod.rs @@ -1,29 +1,254 @@ -//! Box Definition parser (scaffold) -#![allow(dead_code)] +//! Box Definition Parser Module //! -//! This module will progressively take over parsing of large `parse_box_declaration` -//! by splitting header and member parsing into focused units. -//! For now, it provides only type skeletons to stage the refactor safely. +//! Box宣言(box, interface box, static box)の解析を担当 +//! Nyashの中核概念「Everything is Box」を実現する重要モジュール -use crate::ast::ASTNode; +use crate::ast::{ASTNode, Span}; use crate::parser::{NyashParser, ParseError}; +use crate::parser::common::ParserUtils; +use crate::tokenizer::TokenType; +use std::collections::HashMap; pub mod header; pub mod members; pub mod validators; pub mod interface; -/// Facade to host the staged migration. -pub(crate) struct BoxDefParserFacade; - -impl BoxDefParserFacade { - /// Entry planned: parse full box declaration (header + members). - /// Not wired yet; use NyashParser::parse_box_declaration for now. - pub(crate) fn parse_box(_p: &mut NyashParser) -> Result { - Err(ParseError::UnexpectedToken { - found: crate::tokenizer::TokenType::EOF, - expected: "box declaration (facade not wired)".to_string(), - line: 0, - }) - } +/// Thin wrappers to keep the main loop tidy (behavior-preserving) +fn box_try_block_first_property( + p: &mut NyashParser, + methods: &mut HashMap, + birth_once_props: &mut Vec, +) -> Result { + members::properties::try_parse_block_first_property( + p, methods, birth_once_props, + ) +} + +fn box_try_method_postfix_after_last( + p: &mut NyashParser, + methods: &mut HashMap, + last_method_name: &Option, +) -> Result { + members::postfix::try_parse_method_postfix_after_last_method( + p, methods, last_method_name, + ) +} + +fn box_try_init_block( + p: &mut NyashParser, + init_fields: &mut Vec, + weak_fields: &mut Vec, +) -> Result { + members::fields::parse_init_block_if_any( + p, init_fields, weak_fields, + ) +} + +fn box_try_constructor( + p: &mut NyashParser, + is_override: bool, + constructors: &mut HashMap, +) -> Result { + if let Some((key, node)) = members::constructors::try_parse_constructor(p, is_override)? { + constructors.insert(key, node); + return Ok(true); + } + Ok(false) +} + +fn box_try_visibility( + p: &mut NyashParser, + visibility: &str, + methods: &mut HashMap, + fields: &mut Vec, + public_fields: &mut Vec, + private_fields: &mut Vec, + last_method_name: &mut Option, +) -> Result { + members::fields::try_parse_visibility_block_or_single( + p, + visibility, + methods, + fields, + public_fields, + private_fields, + last_method_name, + ) +} + +/// Parse either a method or a header-first field/property starting with `name`. +/// Updates `methods`/`fields` and `last_method_name` as appropriate. +fn box_try_method_or_field( + p: &mut NyashParser, + name: String, + is_override: bool, + methods: &mut HashMap, + fields: &mut Vec, + birth_once_props: &Vec, + last_method_name: &mut Option, +) -> Result { + if let Some(method) = members::methods::try_parse_method( + p, + name.clone(), + is_override, + birth_once_props, + )? { + *last_method_name = Some(name.clone()); + methods.insert(name, method); + return Ok(true); + } + // Fallback: header-first field/property (computed/once/birth_once handled inside) + members::fields::try_parse_header_first_field_or_property( + p, + name, + methods, + fields, + ) +} + +/// box宣言をパース: box Name { fields... methods... } +pub fn parse_box_declaration(p: &mut NyashParser) -> Result { + // Accept either 'box' or 'flow' (flow is syntactic sugar for static box) + if !p.match_token(&TokenType::BOX) && !p.match_token(&TokenType::FLOW) { + return Err(ParseError::UnexpectedToken { + found: p.current_token().token_type.clone(), + expected: "'box' or 'flow'".to_string(), + line: p.current_token().line, + }); + } + p.advance(); // consume BOX or FLOW + let (name, type_parameters, extends, implements) = + header::parse_header(p)?; + + p.consume(TokenType::LBRACE)?; + + let mut fields = Vec::new(); + let mut methods = HashMap::new(); + let mut public_fields: Vec = Vec::new(); + let mut private_fields: Vec = Vec::new(); + let mut constructors = HashMap::new(); + let mut init_fields = Vec::new(); + let mut weak_fields = Vec::new(); // 🔗 Track weak fields + // Track birth_once properties to inject eager init into birth() + let mut birth_once_props: Vec = Vec::new(); + + let mut last_method_name: Option = None; + while !p.match_token(&TokenType::RBRACE) && !p.is_at_end() { + // 分類(段階移行用の観測): 将来の分岐移譲のための前処理 + if crate::config::env::parser_stage3() { + if let Ok(kind) = members::common::classify_member(p) { + let _ = kind; // 現段階では観測のみ(無副作用) + } + } + + // nyashモード(block-first): { body } as (once|birth_once)? name : Type + if box_try_block_first_property(p, &mut methods, &mut birth_once_props)? { continue; } + + // Fallback: method-level postfix catch/cleanup after a method (non-static box) + if box_try_method_postfix_after_last(p, &mut methods, &last_method_name)? { continue; } + + // RBRACEに到達していればループを抜ける + if p.match_token(&TokenType::RBRACE) { + break; + } + + // initブロックの処理(initメソッドではない場合のみ) + if box_try_init_block(p, &mut init_fields, &mut weak_fields)? { continue; } + + // overrideキーワードをチェック + let mut is_override = false; + if p.match_token(&TokenType::OVERRIDE) { + is_override = true; + p.advance(); + } + + // constructor parsing moved to members::constructors + if box_try_constructor(p, is_override, &mut constructors)? { continue; } + + // 🚨 birth()統一システム: Box名コンストラクタ無効化 + validators::forbid_box_named_constructor(p, &name)?; + + // 通常のフィールド名またはメソッド名、または unified members の先頭キーワードを読み取り + if let TokenType::IDENTIFIER(field_or_method) = &p.current_token().token_type { + let field_or_method = field_or_method.clone(); + p.advance(); + + // 可視性: public/private ブロック/単行 + if box_try_visibility( + p, + &field_or_method, + &mut methods, + &mut fields, + &mut public_fields, + &mut private_fields, + &mut last_method_name, + )? { continue; } + + // Unified Members (header-first) gate: support once/birth_once via members::properties + if crate::config::env::unified_members() && (field_or_method == "once" || field_or_method == "birth_once") { + if members::properties::try_parse_unified_property( + p, + &field_or_method, + &mut methods, + &mut birth_once_props, + )? { + last_method_name = None; // do not attach method-level postfix here + continue; + } + } + + // メソッド or フィールド(委譲) + if box_try_method_or_field( + p, + field_or_method, + is_override, + &mut methods, + &mut fields, + &birth_once_props, + &mut last_method_name, + )? { continue; } + } else { + return Err(ParseError::UnexpectedToken { + expected: "method or field name".to_string(), + found: p.current_token().token_type.clone(), + line: p.current_token().line, + }); + } + } + + p.consume(TokenType::RBRACE)?; + // 🚫 Disallow method named same as the box (constructor-like confusion) + validators::validate_no_ctor_like_name(p, &name, &methods)?; + + // 🔥 Override validation + for parent in &extends { + p.validate_override_methods(&name, parent, &methods)?; + } + + // birth_once 相互依存の簡易検出(宣言間の循環) + validators::validate_birth_once_cycles(p, &methods)?; + + Ok(ASTNode::BoxDeclaration { + name, + fields, + public_fields, + private_fields, + methods, + constructors, + init_fields, + weak_fields, // 🔗 Add weak fields to AST + is_interface: false, + extends, + implements, + type_parameters, + is_static: false, // 通常のboxはnon-static + static_init: None, // 通常のboxはstatic初期化ブロックなし + span: Span::unknown(), + }) +} + +/// interface box宣言をパース: interface box Name { methods... } +pub fn parse_interface_box_declaration(p: &mut NyashParser) -> Result { + interface::parse_interface_box(p) } diff --git a/src/parser/declarations/box_definition.rs b/src/parser/declarations/box_definition.rs deleted file mode 100644 index bd1bb7b2..00000000 --- a/src/parser/declarations/box_definition.rs +++ /dev/null @@ -1,257 +0,0 @@ -/*! - * Box Definition Parser Module - * - * Box宣言(box, interface box, static box)の解析を担当 - * Nyashの中核概念「Everything is Box」を実現する重要モジュール - */ - -use crate::ast::{ASTNode, Span}; -use crate::parser::declarations::box_def::header as box_header; -use crate::parser::common::ParserUtils; -use crate::parser::{NyashParser, ParseError}; -use crate::tokenizer::TokenType; -use std::collections::HashMap; - -impl NyashParser { - /// Thin wrappers to keep the main loop tidy (behavior-preserving) - fn box_try_block_first_property( - &mut self, - methods: &mut HashMap, - birth_once_props: &mut Vec, - ) -> Result { - crate::parser::declarations::box_def::members::properties::try_parse_block_first_property( - self, methods, birth_once_props, - ) - } - - fn box_try_method_postfix_after_last( - &mut self, - methods: &mut HashMap, - last_method_name: &Option, - ) -> Result { - crate::parser::declarations::box_def::members::postfix::try_parse_method_postfix_after_last_method( - self, methods, last_method_name, - ) - } - - fn box_try_init_block( - &mut self, - init_fields: &mut Vec, - weak_fields: &mut Vec, - ) -> Result { - crate::parser::declarations::box_def::members::fields::parse_init_block_if_any( - self, init_fields, weak_fields, - ) - } - - fn box_try_constructor( - &mut self, - is_override: bool, - constructors: &mut HashMap, - ) -> Result { - if let Some((key, node)) = crate::parser::declarations::box_def::members::constructors::try_parse_constructor(self, is_override)? { - constructors.insert(key, node); - return Ok(true); - } - Ok(false) - } - - fn box_try_visibility( - &mut self, - visibility: &str, - methods: &mut HashMap, - fields: &mut Vec, - public_fields: &mut Vec, - private_fields: &mut Vec, - last_method_name: &mut Option, - ) -> Result { - crate::parser::declarations::box_def::members::fields::try_parse_visibility_block_or_single( - self, - visibility, - methods, - fields, - public_fields, - private_fields, - last_method_name, - ) - } - - /// Parse either a method or a header-first field/property starting with `name`. - /// Updates `methods`/`fields` and `last_method_name` as appropriate. - fn box_try_method_or_field( - &mut self, - name: String, - is_override: bool, - methods: &mut HashMap, - fields: &mut Vec, - birth_once_props: &Vec, - last_method_name: &mut Option, - ) -> Result { - if let Some(method) = crate::parser::declarations::box_def::members::methods::try_parse_method( - self, - name.clone(), - is_override, - birth_once_props, - )? { - *last_method_name = Some(name.clone()); - methods.insert(name, method); - return Ok(true); - } - // Fallback: header-first field/property (computed/once/birth_once handled inside) - crate::parser::declarations::box_def::members::fields::try_parse_header_first_field_or_property( - self, - name, - methods, - fields, - ) - } - // parse_unified_member_block_first moved to members::properties - - // parse_method_postfix_after_last_method moved to members::postfix - - /// box宣言をパース: box Name { fields... methods... } - pub fn parse_box_declaration(&mut self) -> Result { - // Accept either 'box' or 'flow' (flow is syntactic sugar for static box) - if !self.match_token(&TokenType::BOX) && !self.match_token(&TokenType::FLOW) { - return Err(ParseError::UnexpectedToken { - found: self.current_token().token_type.clone(), - expected: "'box' or 'flow'".to_string(), - line: self.current_token().line, - }); - } - self.advance(); // consume BOX or FLOW - let (name, type_parameters, extends, implements) = - box_header::parse_header(self)?; - - self.consume(TokenType::LBRACE)?; - - let mut fields = Vec::new(); - let mut methods = HashMap::new(); - let mut public_fields: Vec = Vec::new(); - let mut private_fields: Vec = Vec::new(); - let mut constructors = HashMap::new(); - let mut init_fields = Vec::new(); - let mut weak_fields = Vec::new(); // 🔗 Track weak fields - // Track birth_once properties to inject eager init into birth() - let mut birth_once_props: Vec = Vec::new(); - - let mut last_method_name: Option = None; - while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() { - // 分類(段階移行用の観測): 将来の分岐移譲のための前処理 - if crate::config::env::parser_stage3() { - if let Ok(kind) = crate::parser::declarations::box_def::members::common::classify_member(self) { - let _ = kind; // 現段階では観測のみ(無副作用) - } - } - - // nyashモード(block-first): { body } as (once|birth_once)? name : Type - if self.box_try_block_first_property(&mut methods, &mut birth_once_props)? { continue; } - - // Fallback: method-level postfix catch/cleanup after a method (non-static box) - if self.box_try_method_postfix_after_last(&mut methods, &last_method_name)? { continue; } - - // RBRACEに到達していればループを抜ける - if self.match_token(&TokenType::RBRACE) { - break; - } - - // initブロックの処理(initメソッドではない場合のみ) - if self.box_try_init_block(&mut init_fields, &mut weak_fields)? { continue; } - - // overrideキーワードをチェック - let mut is_override = false; - if self.match_token(&TokenType::OVERRIDE) { - is_override = true; - self.advance(); - } - - // constructor parsing moved to members::constructors - if self.box_try_constructor(is_override, &mut constructors)? { continue; } - - // 🚨 birth()統一システム: Box名コンストラクタ無効化 - crate::parser::declarations::box_def::validators::forbid_box_named_constructor(self, &name)?; - - // 通常のフィールド名またはメソッド名、または unified members の先頭キーワードを読み取り - if let TokenType::IDENTIFIER(field_or_method) = &self.current_token().token_type { - let field_or_method = field_or_method.clone(); - self.advance(); - - // 可視性: public/private ブロック/単行 - if self.box_try_visibility( - &field_or_method, - &mut methods, - &mut fields, - &mut public_fields, - &mut private_fields, - &mut last_method_name, - )? { continue; } - - // Unified Members (header-first) gate: support once/birth_once via members::properties - if crate::config::env::unified_members() && (field_or_method == "once" || field_or_method == "birth_once") { - if crate::parser::declarations::box_def::members::properties::try_parse_unified_property( - self, - &field_or_method, - &mut methods, - &mut birth_once_props, - )? { - last_method_name = None; // do not attach method-level postfix here - continue; - } - } - - // メソッド or フィールド(委譲) - if self.box_try_method_or_field( - field_or_method, - is_override, - &mut methods, - &mut fields, - &birth_once_props, - &mut last_method_name, - )? { continue; } - } else { - return Err(ParseError::UnexpectedToken { - expected: "method or field name".to_string(), - found: self.current_token().token_type.clone(), - line: self.current_token().line, - }); - } - } - - self.consume(TokenType::RBRACE)?; - // 🚫 Disallow method named same as the box (constructor-like confusion) - crate::parser::declarations::box_def::validators::validate_no_ctor_like_name(self, &name, &methods)?; - - // 🔥 Override validation - for parent in &extends { - self.validate_override_methods(&name, parent, &methods)?; - } - - // birth_once 相互依存の簡易検出(宣言間の循環) - crate::parser::declarations::box_def::validators::validate_birth_once_cycles(self, &methods)?; - - Ok(ASTNode::BoxDeclaration { - name, - fields, - public_fields, - private_fields, - methods, - constructors, - init_fields, - weak_fields, // 🔗 Add weak fields to AST - is_interface: false, - extends, - implements, - type_parameters, - is_static: false, // 通常のboxはnon-static - static_init: None, // 通常のboxはstatic初期化ブロックなし - span: Span::unknown(), - }) - } - - /// interface box宣言をパース: interface box Name { methods... } - pub fn parse_interface_box_declaration(&mut self) -> Result { - crate::parser::declarations::box_def::interface::parse_interface_box(self) - } -} - -// ast_collect_me_fields moved into box_def::validators (private helper) diff --git a/src/parser/declarations/mod.rs b/src/parser/declarations/mod.rs index 6195203e..cfc1ff93 100644 --- a/src/parser/declarations/mod.rs +++ b/src/parser/declarations/mod.rs @@ -5,10 +5,8 @@ * Box定義、関数定義、use文などの宣言を処理 */ -pub mod box_definition; pub mod box_def; pub mod dependency_helpers; -pub mod static_box; pub mod static_def; // Re-export commonly used items diff --git a/src/parser/declarations/static_box.rs b/src/parser/declarations/static_box.rs deleted file mode 100644 index 1abf4ad9..00000000 --- a/src/parser/declarations/static_box.rs +++ /dev/null @@ -1,180 +0,0 @@ -/*! - * Static Box Definition Parser - * - * static box宣言と関連ヘルパー関数 - */ - -use crate::ast::{ASTNode, Span}; -use crate::parser::common::ParserUtils; -use crate::parser::{NyashParser, ParseError}; -use crate::tokenizer::TokenType; -use std::collections::HashMap; - -impl NyashParser { - /// static box宣言をパース: static box Name { ... } - pub fn parse_static_box(&mut self) -> Result { - self.consume(TokenType::BOX)?; - let (name, type_parameters, extends, implements) = - crate::parser::declarations::static_def::header::parse_static_header(self)?; - - self.consume(TokenType::LBRACE)?; - - let mut fields = Vec::new(); - let mut methods = HashMap::new(); - let constructors = HashMap::new(); - let mut init_fields = Vec::new(); - let mut weak_fields = Vec::new(); // 🔗 Track weak fields for static box - let mut static_init: Option> = None; - - // Track last inserted method name to allow postfix catch/cleanup fallback parsing - let mut last_method_name: Option = None; - while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() { - // Tolerate blank lines between members - while self.match_token(&TokenType::NEWLINE) { self.advance(); } - let trace = std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1"); - if trace { - eprintln!( - "[parser][static-box] loop token={:?}", - self.current_token().token_type - ); - } - - // Fallback: method-level postfix catch/cleanup immediately following a method - if crate::parser::declarations::box_def::members::postfix::try_parse_method_postfix_after_last_method( - self, &mut methods, &last_method_name, - )? { continue; } - - // RBRACEに到達していればループを抜ける - if self.match_token(&TokenType::RBRACE) { - break; - } - - // 🔥 static 初期化子の処理(厳密ゲート互換) - if let Some(body) = crate::parser::declarations::static_def::members::parse_static_initializer_if_any(self)? { - static_init = Some(body); - continue; - } else if self.match_token(&TokenType::STATIC) { - // 互換用の暫定ガード(既定OFF): using テキスト結合の継ぎ目で誤って 'static' が入った場合に - // ループを抜けて外側の '}' 消費に委ねる。既定では無効化し、文脈エラーとして扱う。 - if std::env::var("NYASH_PARSER_SEAM_BREAK_ON_STATIC").ok().as_deref() == Some("1") { - if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { - eprintln!("[parser][static-box][seam] encountered 'static' inside static box; breaking (compat shim)"); - } - break; - } - } - - // initブロックの処理(共通ヘルパに委譲) - if crate::parser::declarations::box_def::members::fields::parse_init_block_if_any( - self, &mut init_fields, &mut weak_fields, - )? { continue; } - - // 🔧 Safety valve: if we encounter statement keywords (LOCAL, RETURN, etc.) at member level, - // it means we've likely exited a method body prematurely. Break to close the static box. - match self.current_token().token_type { - TokenType::LOCAL | TokenType::RETURN | TokenType::IF | TokenType::LOOP | - TokenType::BREAK | TokenType::CONTINUE | TokenType::PRINT => { - if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { - eprintln!("[parser][static-box][safety] encountered statement keyword {:?} at member level (line {}); assuming premature method body exit", - self.current_token().token_type, self.current_token().line); - } - break; - } - _ => {} - } - - // Seam/robustness: tolerate stray tokens between members (text-merge or prelude seams) - // NYASH_PARSER_SEAM_TOLERANT=1 (dev/ci既定): ASSIGN を継ぎ目として箱を閉じる(break) - // NYASH_PARSER_SEAM_TOLERANT=0 (prod既定): ASSIGN でエラー(Fail-Fast) - match &self.current_token().token_type { - TokenType::SEMICOLON | TokenType::NEWLINE => { self.advance(); continue; } - // If we encounter a bare '=' at member level, treat as seam boundary (gated by flag) - // Resynchronize by advancing to the closing '}' so outer logic can consume it. - TokenType::ASSIGN => { - let seam_tolerant = std::env::var("NYASH_PARSER_SEAM_TOLERANT") - .ok() - .as_deref() == Some("1"); - if seam_tolerant { - if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { - eprintln!( - "[parser][static-box][seam] encountered ASSIGN at member level (line {}); treating as seam boundary (closing box)", - self.current_token().line - ); - } - // advance until '}' or EOF - while !self.is_at_end() && !self.match_token(&TokenType::RBRACE) { - self.advance(); - } - // do not consume RBRACE here; let trailing logic handle it - break; // 継ぎ目として箱を閉じる - } else { - // Prod: strict mode, fail fast on unexpected ASSIGN - return Err(ParseError::UnexpectedToken { - expected: "method or field name".to_string(), - found: self.current_token().token_type.clone(), - line: self.current_token().line, - }); - } - } - TokenType::IDENTIFIER(field_or_method) => { - let field_or_method = field_or_method.clone(); - self.advance(); - crate::parser::declarations::static_def::members::try_parse_method_or_field( - self, field_or_method, &mut methods, &mut fields, &mut last_method_name, - )?; - } - _ => { - return Err(ParseError::UnexpectedToken { - expected: "method or field name".to_string(), - found: self.current_token().token_type.clone(), - line: self.current_token().line, - }); - } - } - } - - // Tolerate trailing NEWLINE(s) before the closing '}' of the static box - while self.match_token(&TokenType::NEWLINE) { self.advance(); } - if std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1") { - eprintln!( - "[parser][static-box] closing '}}' at token={:?}", - self.current_token().token_type - ); - } - - // Consume the closing RBRACE of the static box - self.consume(TokenType::RBRACE)?; - - if std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1") { - eprintln!("[parser][static-box] successfully closed static box '{}'", name); - } - - // 🔥 Static初期化ブロックから依存関係を抽出 - if let Some(ref init_stmts) = static_init { - let dependencies = self.extract_dependencies_from_statements(init_stmts); - self.static_box_dependencies - .insert(name.clone(), dependencies); - } else { - self.static_box_dependencies - .insert(name.clone(), std::collections::HashSet::new()); - } - - Ok(ASTNode::BoxDeclaration { - name, - fields, - public_fields: vec![], - private_fields: vec![], - methods, - constructors, - init_fields, - weak_fields, // 🔗 Add weak fields to static box construction - is_interface: false, - extends, - implements, - type_parameters, - is_static: true, // 🔥 static boxフラグを設定 - static_init, // 🔥 static初期化ブロック - span: Span::unknown(), - }) - } -} diff --git a/src/parser/declarations/static_def/mod.rs b/src/parser/declarations/static_def/mod.rs index b07fcc5b..431a7aa3 100644 --- a/src/parser/declarations/static_def/mod.rs +++ b/src/parser/declarations/static_def/mod.rs @@ -1,23 +1,179 @@ //! Static Box Definition (staged split) #![allow(dead_code)] -use crate::ast::ASTNode; +use crate::ast::{ASTNode, Span}; use crate::parser::{NyashParser, ParseError}; +use crate::parser::common::ParserUtils; +use crate::tokenizer::TokenType; +use std::collections::HashMap; pub mod header; pub mod members; pub mod validators; -/// Facade placeholder for static box parsing (to be wired gradually). -pub(crate) struct StaticDefFacade; +/// Parse static box declaration: static box Name { ... } +pub fn parse_static_box(p: &mut NyashParser) -> Result { + p.consume(TokenType::BOX)?; + let (name, type_parameters, extends, implements) = + header::parse_static_header(p)?; -impl StaticDefFacade { - pub(crate) fn parse_box(_p: &mut NyashParser) -> Result { - Err(ParseError::UnexpectedToken { - found: crate::tokenizer::TokenType::EOF, - expected: "static box declaration (facade not wired)".to_string(), - line: 0, - }) + p.consume(TokenType::LBRACE)?; + + let mut fields = Vec::new(); + let mut methods = HashMap::new(); + let constructors = HashMap::new(); + let mut init_fields = Vec::new(); + let mut weak_fields = Vec::new(); // 🔗 Track weak fields for static box + let mut static_init: Option> = None; + + // Track last inserted method name to allow postfix catch/cleanup fallback parsing + let mut last_method_name: Option = None; + while !p.match_token(&TokenType::RBRACE) && !p.is_at_end() { + // Tolerate blank lines between members + while p.match_token(&TokenType::NEWLINE) { p.advance(); } + let trace = std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1"); + if trace { + eprintln!( + "[parser][static-box] loop token={:?}", + p.current_token().token_type + ); + } + + // Fallback: method-level postfix catch/cleanup immediately following a method + if crate::parser::declarations::box_def::members::postfix::try_parse_method_postfix_after_last_method( + p, &mut methods, &last_method_name, + )? { continue; } + + // RBRACEに到達していればループを抜ける + if p.match_token(&TokenType::RBRACE) { + break; + } + + // 🔥 static 初期化子の処理(厳密ゲート互換) + if let Some(body) = members::parse_static_initializer_if_any(p)? { + static_init = Some(body); + continue; + } else if p.match_token(&TokenType::STATIC) { + // 互換用の暫定ガード(既定OFF): using テキスト結合の継ぎ目で誤って 'static' が入った場合に + // ループを抜けて外側の '}' 消費に委ねる。既定では無効化し、文脈エラーとして扱う。 + if std::env::var("NYASH_PARSER_SEAM_BREAK_ON_STATIC").ok().as_deref() == Some("1") { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[parser][static-box][seam] encountered 'static' inside static box; breaking (compat shim)"); + } + break; + } + } + + // initブロックの処理(共通ヘルパに委譲) + if crate::parser::declarations::box_def::members::fields::parse_init_block_if_any( + p, &mut init_fields, &mut weak_fields, + )? { continue; } + + // 🔧 Safety valve: if we encounter statement keywords (LOCAL, RETURN, etc.) at member level, + // it means we've likely exited a method body prematurely. Break to close the static box. + match p.current_token().token_type { + TokenType::LOCAL | TokenType::RETURN | TokenType::IF | TokenType::LOOP | + TokenType::BREAK | TokenType::CONTINUE | TokenType::PRINT => { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!("[parser][static-box][safety] encountered statement keyword {:?} at member level (line {}); assuming premature method body exit", + p.current_token().token_type, p.current_token().line); + } + break; + } + _ => {} + } + + // Seam/robustness: tolerate stray tokens between members (text-merge or prelude seams) + // NYASH_PARSER_SEAM_TOLERANT=1 (dev/ci既定): ASSIGN を継ぎ目として箱を閉じる(break) + // NYASH_PARSER_SEAM_TOLERANT=0 (prod既定): ASSIGN でエラー(Fail-Fast) + match &p.current_token().token_type { + TokenType::SEMICOLON | TokenType::NEWLINE => { p.advance(); continue; } + // If we encounter a bare '=' at member level, treat as seam boundary (gated by flag) + // Resynchronize by advancing to the closing '}' so outer logic can consume it. + TokenType::ASSIGN => { + let seam_tolerant = std::env::var("NYASH_PARSER_SEAM_TOLERANT") + .ok() + .as_deref() == Some("1"); + if seam_tolerant { + if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { + eprintln!( + "[parser][static-box][seam] encountered ASSIGN at member level (line {}); treating as seam boundary (closing box)", + p.current_token().line + ); + } + // advance until '}' or EOF + while !p.is_at_end() && !p.match_token(&TokenType::RBRACE) { + p.advance(); + } + // do not consume RBRACE here; let trailing logic handle it + break; // 継ぎ目として箱を閉じる + } else { + // Prod: strict mode, fail fast on unexpected ASSIGN + return Err(ParseError::UnexpectedToken { + expected: "method or field name".to_string(), + found: p.current_token().token_type.clone(), + line: p.current_token().line, + }); + } + } + TokenType::IDENTIFIER(field_or_method) => { + let field_or_method = field_or_method.clone(); + p.advance(); + members::try_parse_method_or_field( + p, field_or_method, &mut methods, &mut fields, &mut last_method_name, + )?; + } + _ => { + return Err(ParseError::UnexpectedToken { + expected: "method or field name".to_string(), + found: p.current_token().token_type.clone(), + line: p.current_token().line, + }); + } + } } -} + // Tolerate trailing NEWLINE(s) before the closing '}' of the static box + while p.match_token(&TokenType::NEWLINE) { p.advance(); } + if std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1") { + eprintln!( + "[parser][static-box] closing '}}' at token={:?}", + p.current_token().token_type + ); + } + + // Consume the closing RBRACE of the static box + p.consume(TokenType::RBRACE)?; + + if std::env::var("NYASH_PARSER_TRACE_STATIC").ok().as_deref() == Some("1") { + eprintln!("[parser][static-box] successfully closed static box '{}'", name); + } + + // 🔥 Static初期化ブロックから依存関係を抽出 + if let Some(ref init_stmts) = static_init { + let dependencies = p.extract_dependencies_from_statements(init_stmts); + p.static_box_dependencies + .insert(name.clone(), dependencies); + } else { + p.static_box_dependencies + .insert(name.clone(), std::collections::HashSet::new()); + } + + Ok(ASTNode::BoxDeclaration { + name, + fields, + public_fields: vec![], + private_fields: vec![], + methods, + constructors, + init_fields, + weak_fields, // 🔗 Add weak fields to static box construction + is_interface: false, + extends, + implements, + type_parameters, + is_static: true, // 🔥 static boxフラグを設定 + static_init, // 🔥 static初期化ブロック + span: Span::unknown(), + }) +} diff --git a/src/parser/items/static_items.rs b/src/parser/items/static_items.rs index 03ccbb92..3f990c5b 100644 --- a/src/parser/items/static_items.rs +++ b/src/parser/items/static_items.rs @@ -17,7 +17,7 @@ impl NyashParser { // 次のトークンで分岐: function か box か match &self.current_token().token_type { TokenType::FUNCTION => self.parse_static_function(), - TokenType::BOX => self.parse_static_box(), + TokenType::BOX => crate::parser::declarations::static_def::parse_static_box(self), _ => { let line = self.current_token().line; Err(ParseError::UnexpectedToken { diff --git a/src/parser/statements/declarations.rs b/src/parser/statements/declarations.rs index dc534ab2..b355f5f2 100644 --- a/src/parser/statements/declarations.rs +++ b/src/parser/statements/declarations.rs @@ -14,10 +14,10 @@ impl NyashParser { /// Parse declaration statement dispatch pub(super) fn parse_declaration_statement(&mut self) -> Result { match &self.current_token().token_type { - TokenType::BOX => self.parse_box_declaration(), - TokenType::FLOW => self.parse_box_declaration(), // flow is syntactic sugar for static box + TokenType::BOX => crate::parser::declarations::box_def::parse_box_declaration(self), + TokenType::FLOW => crate::parser::declarations::box_def::parse_box_declaration(self), // flow is syntactic sugar for static box TokenType::IMPORT => self.parse_import(), - TokenType::INTERFACE => self.parse_interface_box_declaration(), + TokenType::INTERFACE => crate::parser::declarations::box_def::parse_interface_box_declaration(self), TokenType::GLOBAL => self.parse_global_var(), TokenType::FUNCTION => self.parse_function_declaration(), TokenType::STATIC => self.parse_static_declaration(), diff --git a/src/parser/statements/modules.rs b/src/parser/statements/modules.rs index 8794fee4..affda408 100644 --- a/src/parser/statements/modules.rs +++ b/src/parser/statements/modules.rs @@ -73,17 +73,26 @@ impl NyashParser { let mut parts = vec![first.clone()]; self.advance(); while let TokenType::DOT = self.current_token().token_type { - // consume '.' and the following IDENTIFIER + // consume '.' and the following IDENTIFIER-like segment self.advance(); - if let TokenType::IDENTIFIER(seg) = &self.current_token().token_type { - parts.push(seg.clone()); - self.advance(); - } else { - return Err(ParseError::UnexpectedToken { - found: self.current_token().token_type.clone(), - expected: "identifier after '.'".to_string(), - line: self.current_token().line, - }); + match &self.current_token().token_type { + TokenType::IDENTIFIER(seg) => { + parts.push(seg.clone()); + self.advance(); + } + // Allow `box` as a namespace segment (e.g. lang.compiler.parser.box) + // even though it is a keyword at the statement level. + TokenType::BOX => { + parts.push("box".to_string()); + self.advance(); + } + other => { + return Err(ParseError::UnexpectedToken { + found: other.clone(), + expected: "identifier after '.'".to_string(), + line: self.current_token().line, + }); + } } } parts.join(".") diff --git a/src/runner/json_v0_bridge/lowering/loop_.rs b/src/runner/json_v0_bridge/lowering/loop_.rs index cf7c964e..76151918 100644 --- a/src/runner/json_v0_bridge/lowering/loop_.rs +++ b/src/runner/json_v0_bridge/lowering/loop_.rs @@ -13,6 +13,14 @@ pub(super) fn lower_loop_stmt( loop_stack: &mut Vec, env: &BridgeEnv, ) -> Result { + // DEBUG: Track loop lowering calls (Stage-B / JSON v0) — dev用トレース + if std::env::var("HAKO_LOOP_PHI_TRACE").ok().as_deref() == Some("1") { + eprintln!( + "[loop-phi/enter] lower_loop_stmt called, fn={}, base_vars={}", + f.signature.name, + vars.len() + ); + } // Unification toggle (default ON). For now legacy path is removed; when OFF, warn and proceed unified. let unify_on = std::env::var("NYASH_MIR_UNIFY_LOOPFORM") .ok() diff --git a/src/tests/mir_pure_e2e_vm.rs b/src/tests/mir_pure_e2e_vm.rs index 98b7fe93..d786c1d4 100644 --- a/src/tests/mir_pure_e2e_vm.rs +++ b/src/tests/mir_pure_e2e_vm.rs @@ -1,7 +1,8 @@ #[cfg(test)] mod tests { - use crate::parser::NyashParser; use crate::backend::VM; + use crate::parser::NyashParser; + use crate::mir::{MirCompiler, MirPrinter, MirVerifier}; #[test] fn vm_exec_new_string_length_under_pure_mode() { @@ -26,5 +27,38 @@ return (new StringBox("Hello")).length() // Cleanup std::env::remove_var("NYASH_MIR_CORE13_PURE"); } -} + /// Minimal smoke for MirInstruction::DebugLog + #[test] + fn mir_debuglog_minimal_printer_and_verifier() { + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("NYASH_PARSER_ALLOW_SEMICOLON", "1"); + std::env::set_var("NYASH_MIR_DEBUG_LOG", "1"); + + let src = r#" +static box Main { + method main(args) { + local x = 1 + local y = 2 + // DebugLog 自体の存在が Verifier/Printer で問題にならないことの確認用。 + return x + y + } +} +"#; + + let ast = NyashParser::parse_from_string(src).expect("parse ok"); + let mut mc = MirCompiler::with_options(false); + let cr = mc.compile(ast).expect("compile ok"); + + let mut verifier = MirVerifier::new(); + if let Err(errors) = verifier.verify_module(&cr.module) { + for e in &errors { + eprintln!("[mir-debuglog] {}", e); + } + panic!("MIR verification failed for DebugLog minimal case"); + } + + let dump = MirPrinter::new().print_module(&cr.module); + eprintln!("----- MIR DUMP (DebugLog.min) -----\n{}", dump); + } +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index bc46ac75..b90e9cdd 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -14,6 +14,7 @@ pub mod mir_loopform_exit_phi; pub mod mir_breakfinder_ssa; pub mod mir_vm_poc; pub mod nyash_abi_basic; +pub mod parser_static_box_members; pub mod plugin_hygiene; pub mod policy_mutdeny; pub mod sugar_basic_test; diff --git a/stageb_funcscan_demo.hako b/stageb_funcscan_demo.hako new file mode 100644 index 00000000..13befeb5 --- /dev/null +++ b/stageb_funcscan_demo.hako @@ -0,0 +1,19 @@ +using lang.compiler.entry.compiler_stageb as StageBMod + +static box Main { + method main(args) { + local src = "\n\nstatic box TestBox {\nmethod fib(n) {\nlocal i = 0\nlocal a = 0\nlocal b = 1\nloop(i < n) {\nlocal t = a + b\na = b\nb = t\ni = i + 1\n}\nreturn b\n}\n}\n\nstatic box Main {\nmethod main(args) {\nlocal t = new TestBox()\nreturn t.fib(6)\n}\n}\n" + + local defs = StageBMod.StageBFuncScannerBox.scan_all_boxes(src) + print("[funcscan/demo] defs.len=" + ("" + defs.length())) + local i = 0 + local n = defs.length() + loop(i < n) { + local d = defs.get(i) + print("[funcscan/demo] def box=" + ("" + d.get("box")) + " name=" + ("" + d.get("name"))) + print("[funcscan/demo] body_json=" + ("" + d.get("body_json"))) + i = i + 1 + } + return 0 + } +} diff --git a/temp_fib.hako b/temp_fib.hako new file mode 100644 index 00000000..82b451dc --- /dev/null +++ b/temp_fib.hako @@ -0,0 +1,21 @@ +static box TestBox { + method fib(n) { + local i = 0 + local a = 0 + local b = 1 + loop(i < n) { + local t = a + b + a = b + b = t + i = i + 1 + } + return b + } +} + +static box Main { + method main(args) { + local t = new TestBox() + return t.fib(6) + } +}